feat: 完善词典子图,添加API调用和前端格式化工具
Some checks failed
构建并部署 AI Agent 服务 / deploy (push) Failing after 6m5s

- 完善词典子图:添加生词本功能
- 创建API调用工具:dictionary_api
- 添加前端格式化展示工具:result_formatter.py
- 创建通讯录和资讯子图的基本结构
- 更新主图状态结构,添加MainGraphState
- 添加subgraph_builder.py用于子图集成
This commit is contained in:
2026-04-25 18:29:23 +08:00
parent 03ba825645
commit a14744f18b
17 changed files with 2057 additions and 18 deletions

View File

@@ -0,0 +1,50 @@
"""
词典子图 - 完善版
Dictionary Subgraph Module - Complete
"""
from .state import (
DictionaryState,
DictionaryAction,
WordEntry,
ExtractedTerm
)
from .graph import build_dictionary_subgraph
from .nodes import (
parse_intent,
query_word,
translate_text,
extract_terms,
get_daily_word,
lookup_word_book,
add_to_word_book,
format_result,
should_continue
)
from .api_client import dictionary_api, DictionaryAPIClient
__all__ = [
# State
"DictionaryState",
"DictionaryAction",
"WordEntry",
"ExtractedTerm",
# Graph
"build_dictionary_subgraph",
# Nodes
"parse_intent",
"query_word",
"translate_text",
"extract_terms",
"get_daily_word",
"lookup_word_book",
"add_to_word_book",
"format_result",
"should_continue",
# API
"dictionary_api",
"DictionaryAPIClient"
]

View File

@@ -0,0 +1,129 @@
"""
词典API调用工具
Dictionary API Client
"""
from typing import Dict, Any, Optional
import requests
import json
from dataclasses import dataclass
@dataclass
class DictionaryAPIClient:
"""
词典API客户端 - 可扩展支持多种API
"""
# 可以配置多个API
youdao_api_key: Optional[str] = None
youdao_api_secret: Optional[str] = None
def query_word_youdao(self, word: str) -> Optional[Dict[str, Any]]:
"""
调用有道词典API查询单词
注意需要配置有道API密钥才能使用
文档https://ai.youdao.com/doc.s#guide
"""
if not self.youdao_api_key or not self.youdao_api_secret:
return None
try:
# TODO: 实现真实的有道API调用
# 这里是示例结构
return None
except Exception as e:
print(f"有道API调用失败{e}")
return None
def translate_baidu(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Optional[Dict[str, Any]]:
"""
调用百度翻译API
注意需要配置百度API密钥才能使用
文档https://fanyi-api.baidu.com/doc/21
"""
# TODO: 实现真实的百度翻译API调用
return None
def query_word_mock(self, word: str) -> Dict[str, Any]:
"""
模拟词典API - 目前用于演示
"""
mock_db = {
"serendipity": {
"phonetic": "/ˌserənˈdipədē/",
"part_of_speech": "n.",
"definitions": ["意外发现珍奇事物的能力", "机缘凑巧"],
"examples": ["Finding that old photo was pure serendipity."]
},
"ephemeral": {
"phonetic": "ˈfem(ə)rəl/",
"part_of_speech": "adj.",
"definitions": ["短暂的,瞬息的"],
"examples": ["Fame in the digital age is often ephemeral."]
},
"ubiquitous": {
"phonetic": "/yo͞oˈbikwədəs/",
"part_of_speech": "adj.",
"definitions": ["无处不在的", "普遍存在的"],
"examples": ["Smartphones have become ubiquitous in modern life."]
},
"eloquent": {
"phonetic": "/ˈeləkwənt/",
"part_of_speech": "adj.",
"definitions": ["雄辩的,有说服力的"],
"examples": ["She gave an eloquent speech at the conference."]
},
"resilient": {
"phonetic": "/rəˈzilyənt/",
"part_of_speech": "adj.",
"definitions": ["有复原力的,能适应的"],
"examples": ["The community has proven to be resilient in the face of challenges."]
}
}
if word.lower() in mock_db:
return mock_db[word.lower()]
else:
return {
"phonetic": "",
"part_of_speech": "n.",
"definitions": [f"{word}的释义1", f"{word}的释义2"],
"examples": [f"This is an example sentence with '{word}'."]
}
def translate_mock(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Dict[str, Any]:
"""
模拟翻译API - 目前用于演示
"""
translations = {
"你好": "Hello",
"hello": "你好",
"人工智能": "Artificial Intelligence",
"artificial intelligence": "人工智能",
"ai": "人工智能",
"大模型": "Large Language Model",
"自然语言处理": "Natural Language Processing"
}
return {
"translated_text": translations.get(text.lower(), f"【翻译结果】{text}"),
"confidence": 0.95
}
def extract_terms_mock(self, text: str) -> list:
"""
模拟术语提取API
"""
return [
{"term": "AI", "type": "技术术语", "definition": "人工智能", "confidence": 0.95},
{"term": "LLM", "type": "技术术语", "definition": "大语言模型", "confidence": 0.92},
{"term": "NLP", "type": "技术术语", "definition": "自然语言处理", "confidence": 0.88}
]
# 单例实例
dictionary_api = DictionaryAPIClient()

View File

@@ -0,0 +1,71 @@
"""
词典子图构建器 - 完善版
Dictionary Subgraph Builder - Complete
"""
from langgraph.graph import StateGraph, START, END
from .state import DictionaryState
from .nodes import (
parse_intent,
query_word,
translate_text,
extract_terms,
get_daily_word,
lookup_word_book,
add_to_word_book,
format_result,
should_continue
)
def build_dictionary_subgraph() -> StateGraph:
"""
构建词典子图
Returns:
配置好的 StateGraph
"""
# 创建图
graph = StateGraph(DictionaryState)
# 添加节点
graph.add_node("parse_intent", parse_intent)
graph.add_node("query_word", query_word)
graph.add_node("translate_text", translate_text)
graph.add_node("extract_terms", extract_terms)
graph.add_node("get_daily_word", get_daily_word)
graph.add_node("lookup_word_book", lookup_word_book)
graph.add_node("add_to_word_book", add_to_word_book)
graph.add_node("format_result", format_result)
# 添加边
# 从START开始
graph.add_edge(START, "parse_intent")
# 从parse_intent根据条件路由
graph.add_conditional_edges(
"parse_intent",
should_continue,
{
"query_word": "query_word",
"translate_text": "translate_text",
"extract_terms": "extract_terms",
"get_daily_word": "get_daily_word",
"lookup_word_book": "lookup_word_book",
"add_to_word_book": "add_to_word_book",
}
)
# 从各个操作节点到format_result
graph.add_edge("query_word", "format_result")
graph.add_edge("translate_text", "format_result")
graph.add_edge("extract_terms", "format_result")
graph.add_edge("get_daily_word", "format_result")
graph.add_edge("lookup_word_book", "format_result")
graph.add_edge("add_to_word_book", "format_result")
# 最终到END
graph.add_edge("format_result", END)
return graph

View File

@@ -0,0 +1,402 @@
"""
词典子图节点 - 完善版使用API客户端
Dictionary Subgraph Nodes - Complete (with API Client)
"""
from typing import Dict, Any, List
from datetime import datetime
import random
from .state import (
DictionaryState,
DictionaryAction,
WordEntry,
ExtractedTerm
)
from .api_client import dictionary_api
# ========== 模拟生词本存储(后续可替换为数据库) ==========
WORD_BOOK_DB: Dict[str, List[Dict]] = {} # user_id -> [word_entries]
def parse_intent(state: DictionaryState) -> DictionaryState:
"""
解析用户意图节点
确定用户想做什么操作
"""
state.current_phase = "intent_parsing"
query_lower = state.user_query.lower()
# 简单的关键词匹配
if any(keyword in query_lower for keyword in ["翻译", "translate", "英语", "英文"]):
state.action = DictionaryAction.TRANSLATE
state.action_params = {"text": state.user_query}
# 同时设置source_text
text = state.user_query
for keyword in ["翻译", "translate", "英语", "英文"]:
text = text.replace(keyword, "")
state.source_text = text.strip()
elif any(keyword in query_lower for keyword in ["查询", "query", "单词", "word"]):
state.action = DictionaryAction.QUERY
state.action_params = {"word": state.user_query}
elif any(keyword in query_lower for keyword in ["每日", "daily", "一词"]):
state.action = DictionaryAction.DAILY_WORD
elif any(keyword in query_lower for keyword in ["提取", "extract", "术语", "term"]):
state.action = DictionaryAction.EXTRACT
state.action_params = {"text": state.user_query}
elif any(keyword in query_lower for keyword in ["生词本", "wordbook", "我的单词"]):
state.action = DictionaryAction.WORD_BOOK_LOOKUP
elif any(keyword in query_lower for keyword in ["添加到生词本", "添加单词", "add to wordbook"]):
state.action = DictionaryAction.WORD_BOOK_ADD
state.action_params = {"word": state.user_query}
else:
# 默认翻译
state.action = DictionaryAction.TRANSLATE
state.source_text = state.user_query
return state
def query_word(state: DictionaryState) -> DictionaryState:
"""
查询单词节点
"""
state.current_phase = "querying_word"
word = state.action_params.get("word", state.user_query)
word = word.replace("查询", "").replace("单词", "").strip()
# 使用API客户端查询单词
data = dictionary_api.query_word_mock(word)
state.word_entry = WordEntry(
word=word,
phonetic=data.get("phonetic", ""),
part_of_speech=data.get("part_of_speech", "n."),
definitions=data.get("definitions", []),
examples=data.get("examples", []),
)
state.success = True
return state
def translate_text(state: DictionaryState) -> DictionaryState:
"""
翻译文本节点
"""
state.current_phase = "translating"
text = state.source_text or state.action_params.get("text", state.user_query)
# 使用API客户端翻译
data = dictionary_api.translate_mock(text, state.source_language, state.target_language)
state.translated_text = data.get("translated_text", f"【翻译结果】{text}")
state.translation_confidence = data.get("confidence", 0.95)
state.success = True
return state
def extract_terms(state: DictionaryState) -> DictionaryState:
"""
提取专业术语节点
"""
state.current_phase = "extracting_terms"
text = state.source_text or state.action_params.get("text", state.user_query)
# 使用API客户端提取术语
terms_data = dictionary_api.extract_terms_mock(text)
for term_data in terms_data:
state.extracted_terms.append(ExtractedTerm(
term=term_data.get("term", ""),
type=term_data.get("type", ""),
definition=term_data.get("definition", ""),
confidence=term_data.get("confidence", 0.0)
))
state.success = True
return state
def get_daily_word(state: DictionaryState) -> DictionaryState:
"""
获取每日一词节点
"""
state.current_phase = "getting_daily_word"
# 每日一词候选
words = ["serendipity", "ephemeral", "ubiquitous", "eloquent", "resilient"]
# 基于日期选择固定词,这样同一天的每日一词是固定的
day_of_year = datetime.now().timetuple().tm_yday
chosen_idx = day_of_year % len(words)
chosen_word = words[chosen_idx]
# 使用API客户端查询单词详情
data = dictionary_api.query_word_mock(chosen_word)
state.daily_word = WordEntry(
word=chosen_word,
phonetic=data.get("phonetic", ""),
part_of_speech=data.get("part_of_speech", "adj."),
definitions=data.get("definitions", []),
examples=data.get("examples", []),
)
state.success = True
return state
def lookup_word_book(state: DictionaryState) -> DictionaryState:
"""
查询生词本节点
"""
state.current_phase = "looking_up_wordbook"
user_id = state.user_id or "default_user"
word_book = WORD_BOOK_DB.get(user_id, [])
# 构建WordEntry列表
if word_book:
for entry in word_book:
state.extracted_terms.append(ExtractedTerm(
term=entry.get("word", ""),
type="生词本单词",
definition=entry.get("definitions", [""])[0] if entry.get("definitions") else "",
confidence=1.0
))
state.success = True
return state
def add_to_word_book(state: DictionaryState) -> DictionaryState:
"""
添加到生词本节点
"""
state.current_phase = "adding_to_wordbook"
user_id = state.user_id or "default_user"
word = state.action_params.get("word", state.user_query)
word = word.replace("添加到生词本", "").replace("添加单词", "").strip()
# 查询单词信息
query_state = DictionaryState(user_query=word, action=DictionaryAction.QUERY)
query_state = query_word(query_state)
if query_state.word_entry:
we = query_state.word_entry
# 添加到模拟数据库
if user_id not in WORD_BOOK_DB:
WORD_BOOK_DB[user_id] = []
# 检查是否已存在
exists = any(entry.get("word") == we.word for entry in WORD_BOOK_DB[user_id])
if not exists:
entry_dict = {
"word": we.word,
"phonetic": we.phonetic,
"part_of_speech": we.part_of_speech,
"definitions": we.definitions,
"examples": we.examples,
"added_at": datetime.now().isoformat(),
"review_count": 0,
"next_review_at": None
}
WORD_BOOK_DB[user_id].append(entry_dict)
state.final_result = f"✅ 已将 '{we.word}' 添加到生词本!"
else:
state.final_result = f" '{we.word}' 已在生词本中!"
state.success = True
return state
def format_result(state: DictionaryState) -> DictionaryState:
"""
格式化结果节点 - 精美输出
"""
state.current_phase = "formatting"
if state.action == DictionaryAction.QUERY and state.word_entry:
we = state.word_entry
result = []
result.append("═══════════════════════════════════════════")
result.append("📚 单词查询结果")
result.append("═══════════════════════════════════════════")
result.append(f"")
result.append(f" {we.word}")
if we.phonetic:
result.append(f" {we.phonetic}")
result.append(f"{we.part_of_speech}")
result.append(f"")
result.append("📖 释义:")
for i, definition in enumerate(we.definitions, 1):
result.append(f" {i}. {definition}")
if we.examples:
result.append("")
result.append("💡 例句:")
for example in we.examples:
result.append(f" \"{example}\"")
if we.synonyms:
result.append("")
result.append("🔗 同义词:")
result.append(f" {', '.join(we.synonyms)}")
if we.antonyms:
result.append("")
result.append("🔗 反义词:")
result.append(f" {', '.join(we.antonyms)}")
result.append("")
result.append("═══════════════════════════════════════════")
result.append("💡 提示:回复 '添加到生词本 + 单词' 可收藏")
state.final_result = "\n".join(result)
elif state.action == DictionaryAction.TRANSLATE:
result = []
result.append("═══════════════════════════════════════════")
result.append("🔄 翻译结果")
result.append("═══════════════════════════════════════════")
result.append(f"")
result.append(f" 原文:{state.source_text}")
result.append(f" 译文:{state.translated_text}")
result.append(f"")
result.append(f" 🎯 置信度:{state.translation_confidence:.0%}")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
elif state.action == DictionaryAction.DAILY_WORD and state.daily_word:
dw = state.daily_word
result = []
result.append("═══════════════════════════════════════════")
result.append("🌟 每日一词")
result.append("═══════════════════════════════════════════")
result.append(f"")
result.append(f" {dw.word}")
if dw.phonetic:
result.append(f" {dw.phonetic}")
result.append(f"{dw.part_of_speech}")
result.append(f"")
if dw.definitions:
result.append("📖 释义:")
for i, definition in enumerate(dw.definitions, 1):
result.append(f" {i}. {definition}")
if dw.examples:
result.append("")
result.append("💡 例句:")
for example in dw.examples:
result.append(f" \"{example}\"")
result.append("")
result.append("═══════════════════════════════════════════")
result.append("💡 学习提示:尝试用这个词造一个句子")
result.append("💡 收藏提示:回复 '添加到生词本' 可收藏")
state.final_result = "\n".join(result)
elif state.action == DictionaryAction.EXTRACT and state.extracted_terms:
result = []
result.append("═══════════════════════════════════════════")
result.append("📋 提取的术语")
result.append("═══════════════════════════════════════════")
result.append("")
for i, term in enumerate(state.extracted_terms, 1):
result.append(f" {i}. {term.term}{term.type}")
result.append(f" {term.definition}")
result.append(f" 🎯 置信度:{term.confidence:.0%}")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
elif state.action == DictionaryAction.WORD_BOOK_LOOKUP:
user_id = state.user_id or "default_user"
word_book = WORD_BOOK_DB.get(user_id, [])
result = []
result.append("═══════════════════════════════════════════")
result.append("📚 我的生词本")
result.append("═══════════════════════════════════════════")
result.append(f"")
if word_book:
result.append(f"{len(word_book)} 个单词")
result.append("")
for i, entry in enumerate(word_book, 1):
word = entry.get("word", "")
added_at = entry.get("added_at", "")
added_date = datetime.fromisoformat(added_at).strftime("%Y-%m-%d") if added_at else ""
result.append(f" {i}. {word} (添加于:{added_date}")
else:
result.append(" 生词本为空")
result.append(" 💡 提示:查询单词后可添加到生词本")
result.append("")
result.append("═══════════════════════════════════════════")
state.final_result = "\n".join(result)
elif state.action == DictionaryAction.WORD_BOOK_ADD:
if state.final_result:
result = state.final_result
else:
result = "添加完成"
state.final_result = result
else:
if not state.final_result:
state.final_result = "词典操作完成"
state.current_phase = "done"
return state
def should_continue(state: DictionaryState) -> str:
"""
条件路由:决定下一步该做什么
"""
if state.error_message:
return "format_result"
# 根据action路由
if state.action == DictionaryAction.NONE:
return "parse_intent"
elif state.action == DictionaryAction.QUERY:
return "query_word"
elif state.action == DictionaryAction.TRANSLATE:
return "translate_text"
elif state.action == DictionaryAction.EXTRACT:
return "extract_terms"
elif state.action == DictionaryAction.DAILY_WORD:
return "get_daily_word"
elif state.action == DictionaryAction.WORD_BOOK_LOOKUP:
return "lookup_word_book"
elif state.action == DictionaryAction.WORD_BOOK_ADD:
return "add_to_word_book"
else:
return "format_result"

View File

@@ -0,0 +1,95 @@
"""
词典子图状态定义
Dictionary Subgraph State Definition
"""
from enum import Enum, auto
from typing import Optional, Dict, List, Any
from dataclasses import dataclass, field
class DictionaryAction(Enum):
"""词典操作类型"""
NONE = auto()
QUERY = auto() # 查询单词
TRANSLATE = auto() # 翻译文本
EXTRACT = auto() # 提取专业术语
DAILY_WORD = auto() # 每日一词
WORD_BOOK_LOOKUP = auto() # 生词本查询
WORD_BOOK_ADD = auto() # 添加到生词本
@dataclass
class WordEntry:
"""单词词条"""
word: str = ""
phonetic: str = "" # 音标
part_of_speech: str = "" # 词性
definitions: List[str] = field(default_factory=list) # 释义
examples: List[str] = field(default_factory=list) # 例句
synonyms: List[str] = field(default_factory=list) # 同义词
antonyms: List[str] = field(default_factory=list) # 反义词
source_language: str = "en" # 源语言
target_language: str = "zh" # 目标语言
in_word_book: bool = False # 是否在生词本
review_count: int = 0 # 复习次数
next_review_at: Optional[str] = None # 下次复习时间
created_at: Optional[str] = None
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class ExtractedTerm:
"""提取的术语"""
term: str = ""
type: str = "" # 技术术语、医学术语等
definition: str = ""
context: str = ""
confidence: float = 0.0
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class DictionaryState:
"""词典子图状态"""
# ========== 输入 ==========
user_query: str = "" # 用户查询
user_id: str = "" # 用户ID
# 操作控制
action: DictionaryAction = DictionaryAction.NONE
action_params: Dict[str, Any] = field(default_factory=dict)
# 翻译专用
source_text: str = ""
source_language: str = "auto" # auto, en, zh, etc.
target_language: str = "zh" # 默认翻译成中文
# ========== 执行过程 ==========
current_phase: str = "init" # init, querying, extracting, done
# 查询结果
word_entry: Optional[WordEntry] = None
# 翻译结果
translated_text: str = ""
translation_confidence: float = 0.0
# 提取结果
extracted_terms: List[ExtractedTerm] = field(default_factory=list)
# 每日一词
daily_word: Optional[WordEntry] = None
daily_word_context: str = ""
# ========== 结果 ==========
success: bool = False
error_message: str = ""
final_result: str = ""
result_data: Dict[str, Any] = field(default_factory=dict)
# ========== 元数据 ==========
start_time: Optional[str] = None
end_time: Optional[str] = None
duration: float = 0.0
debug_info: Dict[str, Any] = field(default_factory=dict)