refactor: 重构目录结构 - 简化层级
Some checks failed
构建并部署 AI Agent 服务 / deploy (push) Has been cancelled

This commit is contained in:
2026-04-29 12:52:41 +08:00
parent 223d1c9afd
commit ef5113bffb
54 changed files with 42 additions and 1819 deletions

View File

@@ -0,0 +1,428 @@
# 智能词典子图 (Dictionary Subgraph)
该子图负责处理翻译、查词、生词本管理等功能,基于 LangGraph 状态机编排多阶段学习流程支持联想记忆法、艾宾浩斯遗忘曲线复习、Anki 导出等核心能力。子图设计遵循"高效学习、科学复习、持久记忆"原则。
> **使用公共工具**意图理解、格式化输出、检查点持久化、条件路由、LLM 调用、数据库工具、状态基类
---
## 🎯 核心架构
### 技术栈
| 层级 | 组件 | 说明 |
|:-----|:-----|:-----|
| **编排框架** | LangGraph StateGraph | 状态机驱动的子图工作流编排 |
| **LLM 服务** | 智谱 AI / DeepSeek API | 翻译、释义生成、联想记忆、专业名词提炼(使用公共 LLM 工具) |
| **翻译服务** | DeepL / 有道 API | 高质量机器翻译 |
| **关系存储** | PostgreSQL | 生词本、复习记录持久化(使用公共数据库工具) |
| **导出工具** | csv / Anki APKG | 生词本导出格式 |
### 子图分层架构
```
┌─────────────────────────────────────────────────────────────────┐
│ 主图 (Main Graph) │
└──────────────────────────────┬──────────────────────────────────┘
│ 状态映射 / 结果聚合
┌─────────────────────────────────────────────────────────────────┐
│ 智能词典子图接口层 │
│ - 状态转换:主状态 ↔ 子图状态(使用公共状态基类) │
│ - 错误传播与优雅降级 │
└──────────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 工作流编排层 │
│ - 节点调度与条件路由(使用公共路由工具) │
│ - 复习计划计算 │
│ - 状态持久化与检查点(使用公共检查点工具) │
└──────────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 节点层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ │
│ │意图理解 │ │翻译节点 │ │查词节点 │ │每日一词 │ │专业提炼│ │
│ │(公共工具)│ │ │ │ │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └────────┘ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │生词管理 │ │复习计划 │ │联想记忆 │ │格式输出 │ │
│ │ │ │ │ │ │ │(公共) │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 工具层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │翻译API │ │词典API │ │数据库工具│ │艾宾浩斯 │ │
│ │ │ │ │ │(公共) │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 数据流总览
智能词典子图根据学习意图分支执行,支持查词、翻译、复习等多种学习模式。
```
用户请求
┌─────────────┐
│ 意图理解 │ ← 使用公共意图理解工具
└──────┬──────┘
├──────────┬──────────┬──────────┬──────────┬──────────┐
▼ ▼ ▼ ▼ ▼ ▼
翻译 查词 每日一词 专业提炼 生词管理 复习计划
│ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼
翻译API 词典API 每日推荐 术语提取 增删改查 复习计算
│ │ │ │ │ │
│ ▼ │ │ │ │
│ 联想记忆 │ │ │ │
│ │ │ │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┘
格式输出 ← 使用公共格式化工具
├──────────┐
│ │
▼ ▼
保存生词 Anki导出
│ │
└──────────┘
返回主图
```
---
## 📂 模块与文件结构
```
app/dictionary/
├── __init__.py
├── graph.py # 子图构建入口,定义状态图与路由
├── state.py # 子图状态定义(继承公共状态基类)
├── nodes/ # 节点实现
│ ├── __init__.py
│ ├── translate.py # 翻译节点
│ ├── lookup.py # 查词节点
│ ├── daily_word.py # 每日一词节点
│ ├── extract_terms.py # 专业名词提炼节点
│ ├── vocab.py # 生词本管理节点
│ ├── review.py # 复习计划节点
│ ├── association.py # 联想记忆节点
│ └── export.py # Anki导出节点
├── tools/ # 子图特有工具集
│ ├── translate_api.py # 翻译API工具
│ ├── dictionary_api.py # 词典API工具
│ ├── ebinghaus.py # 艾宾浩斯遗忘曲线工具
│ └── anki.py # Anki导出工具
└── persistence/ # (使用公共检查点工具,无需单独实现)
```
> **注意**:以下模块使用公共工具,无需单独实现:
> - 意图理解节点 → 使用 `agent_subgraphs.common.intent`
> - 格式输出节点 → 使用 `agent_subgraphs.common.format`
> - 检查点持久化 → 使用 `agent_subgraphs.common.checkpoint`
> - 条件路由 → 使用 `agent_subgraphs.common.routing`
> - LLM 调用 → 使用 `agent_subgraphs.common.llm`
> - 数据库操作 → 使用 `agent_subgraphs.common.db`
---
## 🎯 演进路线与核心机制
### Level 1基础翻译与查词
**核心机制**:调用翻译/词典 API展示基础释义。
- 支持中↔英、中↔日等多语言互译。
- 提供单词词性、释义、例句。
- 基础生词本功能(添加、查询)。
**适用场景**:快速翻译、单词查询。
**实现指引**:意图理解节点识别翻译/查词意图,路由到对应节点。
### Level 2联想记忆法
**核心机制**:利用 LLM 生成联想记忆法,帮助记忆单词。
- 词根词缀分析。
- 词源故事/文化背景。
- 趣味联想(谐音、画面感)。
- 同根词/近义词/反义词扩展。
**适用场景**:深度单词学习、高效记忆。
**实现指引**:查词后自动生成联想记忆内容,可选是否保存到生词本。
### Level 3艾宾浩斯遗忘曲线复习
**核心机制**:基于遗忘曲线科学安排复习时间。
- 首次学习后按 1天、2天、4天、7天、15天、30天 间隔复习。
- 根据用户记忆反馈动态调整复习间隔。
- 每日复习提醒。
**适用场景**:长期词汇积累、抗遗忘学习。
**实现指引**:复习计划节点计算下次复习时间,存入数据库。
### Level 4专业名词提炼与管理
**核心机制**:从文本中自动提取专业名词,建立术语库。
- 支持从任意文本中提取专业术语。
- 自动生成术语释义。
- 按领域分类管理术语库。
**适用场景**:专业文档阅读、学术学习。
**实现指引**:使用 LLM 进行 NER 和术语识别。
### Level 5智能词汇教练
**核心机制**:个性化学习路径、多模态记忆、学习进度追踪。
- 根据用户水平推荐学习内容。
- 图片、音频等多模态记忆辅助。
- 学习统计与进度可视化。
- 自适应难度调整。
**适用场景**:系统化语言学习、个性化辅导。
---
## 🔧 核心组件详解
### 1. 意图理解节点
**职责**:接收用户请求,区分翻译、查词、每日一词、专业提炼、生词管理、复习等意图。
**输入**:用户自然语言请求。
**输出**
- `intent_type`意图类别translate / lookup / daily / extract / vocab / review / export
- `target_word`:目标单词/文本。
- `source_lang`:源语言。
- `target_lang`:目标语言。
**实现要点**
- 使用 LLM 分类意图,输出结构化 JSON。
- 关键词匹配兜底(如"翻译"、"查一下")。
### 2. 翻译节点
**职责**:调用翻译 API返回高质量翻译结果。
**输入**:待翻译文本、源语言、目标语言。
**输出**
- `translation`:翻译结果。
- `alternative_translations`:备选翻译(如有)。
**实现要点**
- 优先使用 DeepL降级到有道或 LLM 翻译。
- 支持长文本分段翻译。
### 3. 查词节点
**职责**:查询单词详细释义、词性、例句等信息。
**输入**:目标单词。
**输出**
- `word_info`:单词信息(词性、释义、音标)。
- `examples`:例句列表。
**实现要点**
- 调用词典 API缺失时使用 LLM 生成。
- 支持英英、英汉双解。
### 4. 每日一词节点
**职责**:根据用户水平和历史,推荐今日学习单词。
**输入**:用户学习偏好。
**输出**
- `daily_word`:今日推荐单词。
- `word_detail`:单词详情。
- `learning_tip`:学习建议。
**实现要点**
- 结合用户历史生词和复习进度推荐。
- 难度适中递进。
### 5. 专业名词提炼节点
**职责**:从文本中提取专业名词,生成释义。
**输入**:待分析文本、领域(可选)。
**输出**
- `extracted_terms`:提取的专业名词列表。
- `term_definitions`:名词释义。
**实现要点**
- 使用 LLM 进行术语识别和定义生成。
- 支持按领域过滤。
### 6. 生词本管理节点
**职责**:生词本的增删改查操作。
**输入**:操作类型、生词数据。
**输出**
- `operation_result`:操作结果。
- `vocab_list`:更新后的生词列表。
**实现要点**
- 支持批量添加。
- 按标签/难度/复习时间筛选。
### 7. 复习计划节点
**职责**:基于艾宾浩斯遗忘曲线计算复习计划。
**输入**:生词 ID、上次复习时间、记忆强度。
**输出**
- `next_review`:下次复习时间。
- `review_schedule`:完整复习计划。
**实现要点**
- 使用标准艾宾浩斯间隔1天、2天、4天、7天、15天、30天
- 根据用户反馈动态调整。
### 8. 联想记忆节点
**职责**:为单词生成联想记忆法,帮助记忆。
**输入**:目标单词。
**输出**
- `root_analysis`:词根词缀分析。
- `etymology`:词源故事。
- `association`:趣味联想。
- `word_family`:同根词/近义词/反义词。
**实现要点**
- 使用 LLM 生成富有创意的记忆法。
- 支持用户自定义联想。
### 9. Anki 导出节点
**职责**:导出生词本为 Anki 可导入格式。
**输入**:导出范围(全部/按标签/按时间)。
**输出**
- `export_file`:导出文件路径。
- `export_count`:导出单词数量。
**实现要点**
- 支持 CSV 和 APKG 两种格式。
- 包含联想记忆内容。
---
## 🔀 条件路由详解
### 入口路由:意图分支
- **位置**:意图理解节点之后。
- **条件**
- `intent_type == "translate"` → 翻译节点。
- `intent_type == "lookup"` → 查词节点。
- `intent_type == "daily"` → 每日一词节点。
- `intent_type == "extract"` → 专业提炼节点。
- `intent_type == "vocab"` → 生词管理节点。
- `intent_type == "review"` → 复习计划节点。
- `intent_type == "export"` → Anki导出节点。
### 查词后续路由
- **位置**:查词节点之后。
- **条件**
- 用户询问"怎么记" → 联想记忆节点。
- 用户说"保存" → 生词管理节点(添加)。
- 无后续 → 格式输出。
---
## 📊 状态设计
### 状态结构概览
| 分组 | 字段 | 类型 | 说明 |
|:-----|:-----|:-----|:-----|
| **输入** | `user_input` | `str` | 用户原始请求 |
| **意图** | `intent_type` | `str` | 意图类别 |
| | `target_word` | `str` | 目标单词/文本 |
| | `source_lang` | `str` | 源语言 |
| | `target_lang` | `str` | 目标语言 |
| **翻译** | `translation` | `str` | 翻译结果 |
| | `alternative_translations` | `list[str]` | 备选翻译 |
| **查词** | `word_info` | `dict` | 单词信息 |
| | `examples` | `list[str]` | 例句 |
| **联想** | `root_analysis` | `str` | 词根分析 |
| | `etymology` | `str` | 词源 |
| | `association` | `str` | 联想记忆 |
| | `word_family` | `list[str]` | 词族 |
| **每日一词** | `daily_word` | `str` | 今日单词 |
| | `word_detail` | `dict` | 单词详情 |
| | `learning_tip` | `str` | 学习建议 |
| **专业提炼** | `extracted_terms` | `list[dict]` | 提取的术语 |
| | `term_definitions` | `dict` | 术语释义 |
| **生词本** | `vocab_list` | `list[dict]` | 生词列表 |
| | `operation_result` | `str` | 操作结果 |
| **复习** | `next_review` | `datetime` | 下次复习时间 |
| | `review_schedule` | `list[dict]` | 复习计划 |
| **导出** | `export_file` | `str` | 导出文件路径 |
| | `export_count` | `int` | 导出数量 |
| **控制流** | `current_phase` | `str` | 当前阶段 |
| | `next_node` | `str` | 下一节点 |
| **输出** | `final_result` | `str` | 最终结果 |
---
## 🔄 工作流程
### 查词+联想记忆流程
| 步骤 | 节点 | 说明 |
|:-----|:-----|:-----|
| 1 | 意图理解 | 识别查词意图 |
| 2 | 查词 | 查询单词释义 |
| 3 | 联想记忆 | 生成记忆法 |
| 4 | 询问保存 | 可选保存到生词本 |
| 5 | 格式输出 | 展示结果 |
### 复习流程
| 步骤 | 节点 | 说明 |
|:-----|:-----|:-----|
| 1 | 意图理解 | 识别复习意图 |
| 2 | 复习计划 | 获取今日需复习单词 |
| 3 | 复习交互 | 逐个复习,记录记忆强度 |
| 4 | 更新计划 | 计算下次复习时间 |
| 5 | 格式输出 | 展示复习结果 |
### Anki导出流程
| 步骤 | 节点 | 说明 |
|:-----|:-----|:-----|
| 1 | 意图理解 | 识别导出意图 |
| 2 | Anki导出 | 生成导出文件 |
| 3 | 格式输出 | 提供下载链接 |

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,192 @@
"""
词典API调用工具
Dictionary API Client
支持 async 和真实数据库缓存
"""
from typing import Dict, Any, Optional
from dataclasses import dataclass
@dataclass
class DictionaryAPIClient:
"""
词典API客户端 - 可扩展支持多种API和数据库缓存
"""
# 可以配置多个API
youdao_api_key: Optional[str] = None
youdao_api_secret: Optional[str] = None
# 数据库 Repository可选用于缓存单词查询
word_repository: Optional[Any] = None
def __post_init__(self):
"""初始化后,如果有 repository 则支持 async"""
pass
async def query_word_db(self, user_id: str, word: str) -> Optional[Dict[str, Any]]:
"""从数据库缓存查询单词"""
if not self.word_repository:
return None
try:
entity = await self.word_repository.search_by_word(user_id, word)
if entity:
return {
"phonetic": entity.phonetic,
"part_of_speech": entity.part_of_speech,
"definitions": [entity.definition] if entity.definition else [],
"examples": [entity.examples] if entity.examples else []
}
except Exception as e:
print(f"从数据库查询单词失败:{e}")
return None
async def cache_word_db(self, user_id: str, word: str, data: Dict[str, Any]):
"""把单词查询结果缓存到数据库"""
if not self.word_repository:
return
try:
from ...db.models import WordEntity
entity = WordEntity(
user_id=user_id,
word=word,
phonetic=data.get("phonetic", ""),
part_of_speech=data.get("part_of_speech", ""),
definition=data.get("definitions", [""])[0] if data.get("definitions") else "",
examples=data.get("examples", [""])[0] if data.get("examples") else ""
)
await self.word_repository.insert(entity)
except Exception as e:
print(f"缓存单词到数据库失败:{e}")
async def query_word_youdao(self, word: str) -> Optional[Dict[str, Any]]:
"""
调用有道词典API查询单词async 版本)
注意需要配置有道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调用用 httpx 或 aiohttp
# 这里是示例结构
return None
except Exception as e:
print(f"有道API调用失败{e}")
return None
async def translate_baidu(self, text: str, from_lang: str = "auto", to_lang: str = "zh") -> Optional[Dict[str, Any]]:
"""
调用百度翻译APIasync 版本)
注意需要配置百度API密钥才能使用
文档https://fanyi-api.baidu.com/doc/21
"""
# TODO: 实现真实的百度翻译API调用用 httpx 或 aiohttp
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}
]
# ========== 统一入口(优先查缓存) ==========
async def query_word(self, user_id: str = "default", word: str = "", use_cache: bool = True) -> Dict[str, Any]:
"""
查询单词(统一入口,优先查数据库缓存)
"""
# 1. 先查数据库缓存
if use_cache:
cached = await self.query_word_db(user_id, word)
if cached:
return cached
# 2. 查第三方 API暂未实现
api_result = await self.query_word_youdao(word)
if api_result:
if use_cache:
await self.cache_word_db(user_id, word, api_result)
return api_result
# 3. 用模拟数据(兜底)
mock_result = self.query_word_mock(word)
if use_cache:
await self.cache_word_db(user_id, word, mock_result)
return mock_result
# 单例实例(模拟模式,保持向后兼容)
dictionary_api = DictionaryAPIClient()

View File

@@ -0,0 +1,71 @@
"""
词典子图构建器 - 完善版
Dictionary Subgraph Builder - Complete
"""
from app.main_graph.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,266 @@
"""
词典子图节点 - 使用公共工具版本
Dictionary Subgraph Nodes - Using Common Tools
"""
from typing import Dict, Any, List
from datetime import datetime
import random
# 公共工具
from ..common import (
MarkdownFormatter
)
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:
"""
解析用户意图节点(使用规则匹配)
确定用户想做什么操作
"""
# 子图特定的意图解析
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}
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}
else:
state.action = DictionaryAction.QUERY
state.action_params = {"word": state.user_query}
return state
def query_word(state: DictionaryState) -> DictionaryState:
"""
查询单词节点
"""
state.current_phase = "executing"
# 提取要查询的词
word = state.action_params.get("word", state.user_query)
# 清理关键词
for keyword in ["查询", "query", "单词", "word", "翻译", "translate", "英语", "英文"]:
word = word.replace(keyword, "").strip()
if not word:
word = "hello"
# 使用 API 客户端
word_entry = dictionary_api.query_word(word)
state.word_entry = word_entry
return state
def translate_text(state: DictionaryState) -> DictionaryState:
"""
翻译文本节点
"""
state.current_phase = "executing"
text = state.source_text or state.user_query
if not text:
# 清理关键词
for keyword in ["翻译", "translate", "英语", "英文"]:
text = text.replace(keyword, "").strip()
if not text:
text = "你好,世界!"
# 使用 API 客户端
translated = dictionary_api.translate(text)
state.source_text = text
state.translated_text = translated
return state
def extract_terms(state: DictionaryState) -> DictionaryState:
"""
提取术语节点
"""
state.current_phase = "executing"
text = state.action_params.get("text", state.user_query)
for keyword in ["提取", "extract", "术语", "term"]:
text = text.replace(keyword, "").strip()
if not text:
text = "Python is a great programming language for machine learning and data analysis."
# 使用 API 客户端
terms = dictionary_api.extract_terms(text)
state.extracted_terms = terms
return state
def get_daily_word(state: DictionaryState) -> DictionaryState:
"""
获取每日一词节点
"""
state.current_phase = "executing"
# 使用 API 客户端
word_entry = dictionary_api.get_daily_word()
state.daily_word = word_entry
return state
def lookup_word_book(state: DictionaryState) -> DictionaryState:
"""
查生词本节点
"""
state.current_phase = "executing"
user_id = state.user_id or "default"
if user_id not in WORD_BOOK_DB:
WORD_BOOK_DB[user_id] = []
state.word_book = WORD_BOOK_DB[user_id]
return state
def add_to_word_book(state: DictionaryState) -> DictionaryState:
"""
添加到生词本节点
"""
state.current_phase = "executing"
user_id = state.user_id or "default"
if user_id not in WORD_BOOK_DB:
WORD_BOOK_DB[user_id] = []
if state.word_entry:
entry_dict = {
"word": state.word_entry.word,
"phonetic": state.word_entry.phonetic,
"definition": state.word_entry.definition,
"added_at": datetime.now().isoformat()
}
WORD_BOOK_DB[user_id].append(entry_dict)
return state
def format_result(state: DictionaryState) -> DictionaryState:
"""
格式化结果节点(使用公共工具)
生成友好的 Markdown 输出
"""
state.current_phase = "formatting"
md = MarkdownFormatter()
output_lines = []
# 标题
output_lines.append("┌───────────────────────────────────┐")
output_lines.append("│ 📚 词典助手 │")
output_lines.append("└───────────────────────────────────┘")
output_lines.append("")
if state.action == DictionaryAction.QUERY and state.word_entry:
we = state.word_entry
output_lines.append(md.heading(f"📖 {we.word}", 2))
if we.phonetic:
output_lines.append(f"> 🔊 {we.phonetic}")
output_lines.append("")
output_lines.append(md.heading("释义", 3))
output_lines.append(md.bullet_list(we.definition))
if we.example_sentence:
output_lines.append("")
output_lines.append(md.heading("例句", 3))
output_lines.append(f"> {we.example_sentence}")
elif state.action == DictionaryAction.TRANSLATE and state.translated_text:
output_lines.append(md.heading("🌐 翻译结果", 2))
output_lines.append("")
output_lines.append(md.heading("原文", 3))
output_lines.append(f"> {state.source_text}")
output_lines.append("")
output_lines.append(md.heading("译文", 3))
output_lines.append(f"> {state.translated_text}")
elif state.action == DictionaryAction.EXTRACT and state.extracted_terms:
output_lines.append(md.heading("📝 提取的术语", 2))
output_lines.append("")
terms_data = [
{"术语": t.term, "释义": t.definition, "分类": t.category}
for t in state.extracted_terms
]
output_lines.append(md.table(terms_data))
elif state.action == DictionaryAction.DAILY_WORD and state.daily_word:
dw = state.daily_word
output_lines.append(md.heading("🌟 每日一词", 2))
output_lines.append("")
output_lines.append(md.heading(f"{dw.word}", 3))
if dw.phonetic:
output_lines.append(f"> 🔊 {dw.phonetic}")
output_lines.append("")
output_lines.append(md.bullet_list(dw.definition))
else:
output_lines.append(md.heading("✨ 操作完成", 2))
output_lines.append("您的请求已处理。")
# 页脚提示
output_lines.append("")
output_lines.append("---")
output_lines.append("💡 提示:您可以继续查询其他单词、翻译文本,或者提取术语!")
state.final_result = "\n".join(output_lines)
state.success = True
state.current_phase = "completed"
return state
def should_continue(state: DictionaryState) -> str:
"""
条件路由函数:根据 action 决定下一个节点
"""
action = state.action
if action == DictionaryAction.QUERY:
return "query_word"
elif action == DictionaryAction.TRANSLATE:
return "translate_text"
elif action == DictionaryAction.EXTRACT:
return "extract_terms"
elif action == DictionaryAction.DAILY_WORD:
return "get_daily_word"
elif action == DictionaryAction.LOOKUP_WORD_BOOK:
return "lookup_word_book"
elif action == DictionaryAction.ADD_TO_WORD_BOOK:
return "add_to_word_book"
else:
return "format_result"
return state

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)