feat: 添加项目文档和配置更新

docs: 完善README文档结构和内容
refactor: 更新配置类注释和文档字符串
style: 统一代码格式和命名规范
fix: 修正MCP服务器配置和依赖项
This commit is contained in:
2025-11-07 15:50:22 +08:00
parent 6a9f28bac0
commit bb91a68c78
13 changed files with 5022 additions and 54 deletions

View File

@@ -0,0 +1,218 @@
# LangChain Agent 项目编写规则
## 1. 项目概述
本项目是一个基于LangChain的AI助手系统专注于茶饮场景的智能对话和工具调用。项目采用模块化设计支持多种LLM后端和MCP工具集成。
## 2. 编码风格规范
### 2.1 基本原则
- 代码简洁明了,避免过度设计
- 保持一致的命名约定和代码结构
- 注重可维护性和可扩展性
- 遵循Python PEP 8编码规范
### 2.2 命名约定
- 类名PascalCase (例:`ToolManager`, `PipelineConfig`)
- 函数/变量名snake_case (例:`get_tools`, `tool_manager`)
- 常量UPPER_SNAKE_CASE (例:`DEFAULT_HOST`, `MAX_RETRIES`)
- 私有成员:单下划线前缀 (例:`_internal_method`)
- 特殊方法:双下划线 (例:`__init__`, `__post_init__`)
### 2.3 注释规范
- 类和公共方法必须有文档字符串
- 注释采用中英文混合,核心概念使用中文解释
- 复杂逻辑必须添加行内注释
- 文档字符串格式:
```python
def method_name(param1: str, param2: int) -> bool:
"""
方法功能简述
Args:
param1: 参数1说明
param2: 参数2说明
Returns:
返回值说明
"""
```
## 3. 配置管理规范
### 3.1 配置类设计
- 使用dataclass或pydantic模型定义配置类
- 所有配置类必须继承自`InstantiateConfig``PydanticBaseModel`
- 配置类必须包含`_target`字段,指向对应的实现类
- 配置类必须实现`setup()`方法,用于实例化目标对象
### 3.2 配置类示例
```python
@tyro.conf.configure(tyro.conf.SuppressFixed)
@dataclass
class ExampleConfig(InstantiateConfig):
_target: Type = field(default_factory=lambda: ExampleClass)
param1: str = "default_value"
"""参数1说明"""
param2: int = 10
"""参数2说明"""
def setup(self) -> ExampleClass:
return self._target(self)
```
### 3.3 配置文件管理
- 使用YAML格式存储配置文件
- 配置文件放在`configs/`目录下
- 敏感信息(如API密钥)通过环境变量获取
- 支持配置文件覆盖和合并
## 4. Pydantic集成规范
### 4.1 使用场景
- API请求/响应模型定义
- 数据验证和序列化
- 配置管理(与dataclass并存)
- 复杂数据结构定义
### 4.2 Pydantic模型设计
- 继承自`PydanticBaseModel`基类
- 使用类型注解明确字段类型
- 提供默认值和验证规则
- 使用Field类添加元数据
### 4.3 Pydantic模型示例
```python
class ExampleModel(PydanticBaseModel):
name: str = Field(..., description="名称")
count: int = Field(default=0, ge=0, description="计数")
tags: List[str] = Field(default_factory=list, description="标签列表")
class Config:
extra = "forbid" # 禁止额外字段
```
## 5. 工具开发规范
### 5.1 工具基类
- 所有工具必须继承自`LangToolBase`
- 实现`get_tool_fnc()`方法,返回工具函数列表
- 工具函数必须包含类型注解和文档字符串
### 5.2 工具函数规范
- 函数签名必须包含类型注解
- 参数必须有默认值和说明
- 返回值必须明确类型
- 异步函数必须标记为async
### 5.3 工具注册
- 工具通过配置类注册到`ToolManager`
- 配置类名称必须以`Config`结尾
- 工具配置必须包含`use_tool`布尔标志
## 6. 日志和错误处理
### 6.1 日志规范
- 使用loguru库进行日志记录
- 日志级别DEBUG, INFO, WARNING, ERROR
- 关键操作必须记录INFO级别日志
- 异常必须记录ERROR级别日志
### 6.2 错误处理
- 使用try-except捕获异常
- 异常信息必须包含上下文
- 关键路径必须有异常处理
- 避免捕获过于宽泛的异常
## 7. 测试规范
### 7.1 测试结构
- 单元测试放在对应模块的`tests/`目录
- 集成测试放在项目根目录的`tests/`目录
- 测试文件名以`test_`开头
### 7.2 测试要求
- 所有公共方法必须有单元测试
- 关键业务逻辑必须有集成测试
- 测试覆盖率不低于80%
- 使用pytest框架进行测试
## 8. 文档规范
### 8.1 代码文档
- 所有模块必须有模块级文档字符串
- 公共类和方法必须有文档字符串
- 复杂算法必须有详细注释
### 8.2 项目文档
- README.md包含项目介绍和快速开始指南
- API文档放在`docs/api/`目录
- 架构文档放在`docs/architecture/`目录
## 9. 版本控制规范
### 9.1 Git提交
- 使用Conventional Commits规范
- 提交格式:`type(scope): description`
- 类型feat, fix, docs, style, refactor, test, chore
### 9.2 分支管理
- 主分支main
- 开发分支develop
- 功能分支feature/功能名
- 修复分支fix/问题描述
## 10. 依赖管理
### 10.1 依赖规范
- 使用pyproject.toml管理项目依赖
- 生产依赖放在dependencies列表
- 开发依赖放在dev-dependencies列表
- 定期更新依赖版本
### 10.2 环境管理
- 使用虚拟环境隔离依赖
- 敏感配置通过环境变量管理
- 使用.env文件存储本地配置
## 11. 性能和安全
### 11.1 性能考虑
- 避免不必要的计算和IO操作
- 使用缓存减少重复计算
- 异步处理耗时操作
- 监控关键性能指标
### 11.2 安全规范
- 敏感信息不得硬编码
- API密钥通过环境变量获取
- 输入数据必须验证
- 遵循最小权限原则
## 12. 云端MCP集成
### 12.1 MCP配置
- 默认使用云端MCP服务`https://xiaoliang.quant-speed.com/api/mcp/`
- 支持本地MCP服务配置覆盖
- 使用streamable-http传输协议
### 12.2 工具调用
- 通过ClientToolManager管理MCP工具
- 支持工具动态加载和调用
- 异常处理和重试机制
## 13. 代码审查
### 13.1 审查要点
- 代码风格一致性
- 功能实现正确性
- 性能和安全考虑
- 测试覆盖率
### 13.2 审查流程
- 所有代码必须经过审查
- 使用Pull Request进行审查
- 至少一人审查通过才能合并
- 自动化检查必须通过

256
README.md
View File

@@ -1,51 +1,253 @@
# langchain-agent
## 项目概述
# Install
1. Install `xiaoliang-catering` for carttool support; otherwise, comment out in `lang_agent/tool_manager.py`
这是一个基于LangChain和LangGraph构建的智能代理系统集成了RAG(检索增强生成)、工具调用和WebSocket通信功能。项目主要用于茶饮场景的智能对话和订单处理支持多种工具调用和远程MCP(Model Context Protocol)服务器集成。
# Environs
Need these:
```bash
export ALI_API_KEY=REDACTED
export ALI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
export MCP_ENDPOINT=REDACTED
export LANGSMITH_API_KEY=REDACTED
## 项目结构
```
langchain-agent/
├── lang_agent/ # 核心模块目录
│ ├── base.py # 基础抽象类定义
│ ├── config.py # 配置管理系统
│ ├── pipeline.py # 主流程控制模块
│ ├── mcp_server.py # MCP服务器实现
│ ├── tool_manager.py # 工具管理器
│ ├── client_tool_manager.py # 客户端工具管理器
│ ├── graphs/ # 图工作流模块
│ │ ├── routing.py # 路由图实现
│ │ └── react.py # ReAct图实现
│ ├── rag/ # RAG模块
│ │ ├── simple.py # 简单RAG实现
│ │ └── emb.py # 文本嵌入模型
│ ├── dummy/ # 示例工具
│ │ └── calculator.py # 计算器工具
│ └── eval/ # 评估模块
│ ├── evaluator.py # 评估器
│ └── validator.py # 验证器
├── scripts/ # 可执行脚本目录
│ ├── run_agent_server.py # 启动代理服务器
│ ├── start_mcp_server.py # 启动MCP服务器
│ ├── ws_start_register_tools.py # WebSocket工具注册
│ ├── ws_start_all_mcps.py # 启动所有MCP服务
│ ├── demo_chat.py # 聊天演示
│ ├── make_rag_database.py # 创建RAG数据库
│ ├── make_eval_dataset.py # 创建评估数据集
│ └── eval.py # 评估脚本
├── configs/ # 配置文件目录
│ ├── mcp_config.json # MCP配置
│ ├── ws_mcp_config.json # WebSocket MCP配置
│ └── route_sys_prompts/ # 路由系统提示
│ ├── chat_prompt.txt # 聊天提示
│ ├── route_prompt.txt # 路由提示
│ └── tool_prompt.txt # 工具提示
└── fastapi_server/ # FastAPI服务器模块
├── server.py # 服务器实现
├── server_dashscope.py # DashScope服务器
└── test_*.py # 测试客户端
```
# Install
need to install: `xiaoliang-catering `
## 核心模块功能
### 1. 基础模块 (lang_agent/base.py)
定义了两个抽象基类:
- `LangToolBase`: 所有工具的基类,要求实现`get_tool_fnc`方法返回工具函数列表
- `GraphBase`: 所有图工作流的基类,要求实现`invoke`方法执行工作流
### 2. 配置管理 (lang_agent/config.py)
实现了灵活的配置系统:
- `PrintableConfig`: 提供配置打印和敏感信息脱敏功能
- `InstantiateConfig`: 支持通过`_target`属性动态实例化类
- `KeyConfig`: 管理API密钥从环境变量加载
- `ToolConfig`: 控制工具使用开关
- 提供配置加载、合并和MCP配置转换等工具函数
### 3. 图工作流模块 (lang_agent/graphs/)
#### 路由图 (routing.py)
实现了基于决策的智能路由系统:
- `RoutingConfig`: 配置LLM参数和路由选项
- `RoutingGraph`: 构建状态图工作流,包含路由节点、聊天模型节点和工具模型节点
- 工作流程通过LLM决策将请求路由到"chat"或"order"路径,分别调用不同的工具集
#### ReAct图 (react.py)
实现了ReAct(Reason-Act)模式的智能体:
- `ReactGraphConfig`: 配置ReAct图的参数
- `ReactGraph`: 使用LangChain的create_agent创建智能体支持流式输出
- 集成ToolManager管理工具调用
### 4. 工具管理系统
#### 工具管理器 (lang_agent/tool_manager.py)
核心工具管理模块:
- `ToolManagerConfig`: 配置各种工具(RAG、计算器等)
- `ToolManager`: 统一管理工具实例化与调用
- 支持本地工具和MCP客户端工具的整合
- 将工具转换为LangChain的StructuredTool格式
#### 客户端工具管理器 (lang_agent/client_tool_manager.py)
管理MCP客户端工具
- `ClientToolManagerConfig`: 管理MCP配置文件路径
- `ClientToolManager`: 通过MultiServerMCPClient从MCP服务器获取工具
### 5. RAG模块 (lang_agent/rag/)
#### 简单RAG (simple.py)
基础检索实现:
- `SimpleRagConfig`: 配置嵌入模型和数据库路径
- `SimpleRag`: 使用QwenEmbeddings和FAISS向量存储实现检索功能
- 支持相似度搜索和结果序列化
#### 嵌入模型 (emb.py)
文本嵌入实现:
- `QwenEmbeddings`: 继承LangChain的Embeddings基类
- 使用DashScope API实现文本嵌入
- 支持批量和单文本嵌入、同步和异步处理
- 包含速率限制、并发控制和错误处理机制
### 6. MCP服务器 (lang_agent/mcp_server.py)
实现MCP服务器功能
- `MCPServerConfig`: 配置服务器名称、主机、端口和传输方式
- `MCPServer`: 使用FastMCP创建服务器注册工具管理器中的工具
- 支持CORS配置和多种传输方式(stdio, sse, streamable-http)
### 7. 主流程控制 (lang_agent/pipeline.py)
项目的核心流程控制:
- `PipelineConfig`: 配置LLM参数、服务器设置和图配置
- `Pipeline`: 实现模块初始化、图工作流调用、WebSocket服务器和聊天接口
- 集成RoutingGraph或ReactGraph作为工作流引擎
- 提供带角色设定的对话逻辑
## 系统工作流程
1. **初始化阶段**:
- 加载配置和环境变量
- 初始化LLM、嵌入模型和工具管理器
- 构建图工作流(RoutingGraph或ReactGraph)
2. **请求处理阶段**:
- 接收用户输入
- 通过图工作流进行路由决策或ReAct推理
- 调用相应工具(RAG检索、计算器、远程MCP工具等)
- 生成响应并返回
3. **扩展功能**:
- WebSocket服务器支持实时通信
- MCP协议支持远程工具调用
- 评估系统支持模型性能测试
## 安装与配置
### 1. 安装依赖
```bash
# 安装xiaoliang-catering以支持购物车工具否则请在lang_agent/tool_manager.py中注释掉
pip install xiaoliang-catering
# for developement
# 开发模式安装
python -m pip install -e .
# for production
python -m pip install .
```
# Runables
all runnables are under scripts
### 2. 环境变量配置
需要设置以下环境变量:
# Start all mcps to websocket
1. Source all env variable
2. run the below
```bash
export ALI_API_KEY=你的阿里云API密钥
export ALI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
export MCP_ENDPOINT=你的MCP端点
export LANGSMITH_API_KEY=你的LangSmith API密钥
```
## 运行指南
### 1. 启动MCP服务器
```bash
# 1. 确保所有环境变量已设置
# 2. 启动MCP服务器
python scripts/start_mcp_server.py
# update configs/ws_mcp_config.json with link from the command above
# 3. 使用上述命令中的链接更新configs/ws_mcp_config.json
# 4. 启动WebSocket工具注册
python scripts/ws_start_register_tools.py
```
# Eval Dataset Format
see `scripts/make_eval_dataset.py` for example. Specific meaning of each entry:
### 2. 运行代理服务器
```bash
python scripts/run_agent_server.py
```
### 3. 演示聊天
```bash
python scripts/demo_chat.py
```
### 4. 创建RAG数据库
```bash
python scripts/make_rag_database.py
```
### 5. 运行评估
```bash
# 创建评估数据集
python scripts/make_eval_dataset.py
# 运行评估
python scripts/eval.py
```
## 评估数据集格式
评估数据集格式如下,详见`scripts/make_eval_dataset.py`
```json
[
{
"inputs": {"text": "用retrieve查询光予尘然后介绍"}, // model input; use list for conversation
"outputs": {"answer": "光予尘茉莉绿茶为底", // reference answer
"tool_use": ["retrieve"]} // tool uses; assume model need to use all tools if more than 1 provided
"inputs": {"text": "用retrieve查询光予尘然后介绍"}, // 模型输入;使用列表进行对话
"outputs": {"answer": "光予尘茉莉绿茶为底", // 参考答案
"tool_use": ["retrieve"]} // 工具使用;如果提供了多个工具,假设模型需要使用所有工具
}
]
```
```
## 技术栈
- **核心框架**: LangChain, LangGraph
- **嵌入模型**: QwenEmbeddings (DashScope API)
- **向量存储**: FAISS
- **Web框架**: FastAPI, WebSocket
- **协议支持**: MCP (Model Context Protocol)
- **配置管理**: Tyro, YAML
- **日志系统**: Loguru
## 扩展指南
### 添加新工具
1.`lang_agent/dummy/`或其他适当目录创建新工具类,继承`LangToolBase`
2. 实现`get_tool_fnc`方法返回工具函数列表
3.`lang_agent/tool_manager.py`中注册新工具
4. 更新相关配置文件
### 添加新图工作流
1.`lang_agent/graphs/`目录创建新图类,继承`GraphBase`
2. 实现`invoke`方法执行工作流
3.`lang_agent/pipeline.py`中添加新图类型支持
4. 更新配置文件以支持新图类型
### 添加新评估指标
1.`lang_agent/eval/`目录扩展评估器或验证器
2.`scripts/eval.py`中集成新指标
3. 更新评估数据集格式以支持新指标

View File

@@ -1,6 +1,6 @@
{
"calculator": {
"url": "http://6.6.6.78:50051/mcp",
"transport": "streamable_http"
"url": "https://xiaoliang.quant-speed.com/api/mcp/",
"transport": "streamable_https"
}
}

View File

@@ -1,8 +1,8 @@
{
"mcpServers": {
"remote-http-server": {
"type": "http",
"url": "http://6.6.6.78:50051/mcp"
"type": "https",
"url": "https://xiaoliang.quant-speed.com/api/mcp/"
}
}
}

10
lang_agent/__init__.py Normal file
View File

@@ -0,0 +1,10 @@
"""
LangChain Agent - 智能代理系统
这是一个基于LangChain和LangGraph构建的智能代理系统集成了RAG(检索增强生成)、
工具调用和WebSocket通信功能。项目主要用于茶饮场景的智能对话和订单处理
支持多种工具调用和远程MCP(Model Context Protocol)服务器集成。
"""
__version__ = "0.1.0"
__author__ = "LangChain Agent Team"

View File

@@ -12,7 +12,11 @@ load_dotenv()
## NOTE: base classes taken from nerfstudio
class PrintableConfig:
"""Printable Config defining str function"""
"""
Printable Config defining str function
定义 __str__ 方法的可打印配置类
"""
def __str__(self):
lines = [self.__class__.__name__ + ":"]
@@ -43,25 +47,52 @@ class PrintableConfig:
# Base instantiate configs
@dataclass
class InstantiateConfig(PrintableConfig):
"""Config class for instantiating an the class specified in the _target attribute."""
"""
Config class for instantiating an the class specified in the _target attribute.
用于实例化 _target 属性指定的类的配置类
"""
_target: Type
def setup(self, **kwargs) -> Any:
"""Returns the instantiated object using the config."""
"""
Returns the instantiated object using the config.
使用配置返回实例化的对象
"""
return self._target(self, **kwargs)
def save_config(self, filename: str) -> None:
"""Save the config to a YAML file."""
"""
Save the config to a YAML file.
将配置保存到 YAML 文件
"""
def mask_value(key, value):
# Apply masking if key is secret-like
"""
Apply masking if key is secret-like
如果键是敏感的,应用掩码
检查键是否敏感(如包含 "secret""api_key"),如果是,则对值进行掩码处理
"""
if isinstance(value, str) and self.is_secrete(key):
sval = str(value)
return sval[:3] + "*" * (len(sval) - 6) + sval[-3:]
return value
def to_masked_serializable(obj):
# Recursively convert dataclasses and containers to serializable with masked secrets
"""
Recursively convert dataclasses and containers to serializable with masked secrets
递归地将数据类和容器转换为可序列化的格式,同时对敏感信息进行掩码处理
"""
if is_dataclass(obj):
out = {}
for k, v in vars(obj).items():
@@ -115,10 +146,19 @@ class KeyConfig(InstantiateConfig):
@dataclass
class ToolConfig(InstantiateConfig):
use_tool:bool = True
"""specify to use tool or not"""
"""
specify to use tool or not
指定是否使用工具
"""
def load_tyro_conf(filename: str, inp_conf = None) -> InstantiateConfig:
"""load and overwrite config from file"""
"""
load and overwrite config from file
从文件加载并覆盖配置
"""
config = yaml.load(Path(filename).read_text(), Loader=yaml.Loader)
config = ovewrite_config(config, inp_conf) if inp_conf is not None else config
@@ -127,36 +167,84 @@ def load_tyro_conf(filename: str, inp_conf = None) -> InstantiateConfig:
def is_default(instance, field_):
"""
Check if the value of a field in a dataclass instance is the default value.
检查数据类实例中字段的值是否为默认值
"""
value = getattr(instance, field_.name)
if field_.default is not MISSING:
# Compare with default value
# Compare with default value
"""
与默认值进行比较
如果字段有默认值,则将当前值与默认值进行比较
"""
return value == field_.default
elif field_.default_factory is not MISSING:
# Compare with value generated by the default factory
"""
与默认工厂生成的值进行比较
如果字段有默认工厂,则将当前值与默认工厂生成的值进行比较
"""
return value == field_.default_factory()
else:
# No default value specified
return False
def ovewrite_config(loaded_conf, inp_conf):
"""for non-default values in inp_conf, overwrite the corresponding values in loaded_conf"""
"""
for non-default values in inp_conf, overwrite the corresponding values in loaded_conf
对于 inp_conf 中的非默认值,覆盖 loaded_conf 中对应的配置
"""
if not (is_dataclass(loaded_conf) and is_dataclass(inp_conf)):
return loaded_conf
for field_ in fields(loaded_conf):
field_name = field_.name
# if field_name in inp_conf:
"""
if field_name in inp_conf:
如果字段名在 inp_conf 中,则进行覆盖
"""
current_value = getattr(inp_conf, field_name)
new_value = getattr(inp_conf, field_name) #inp_conf[field_name]
new_value = getattr(inp_conf, field_name)
"""
inp_conf[field_name]
从 inp_conf 中获取字段值
如果字段名在 inp_conf 中,则获取其值
"""
if is_dataclass(current_value):
# Recurse for nested dataclasses
"""
Recurse for nested dataclasses
递归处理嵌套的数据类
如果当前值是数据类,则递归调用 ovewrite_config 进行合并
"""
merged_value = ovewrite_config(current_value, new_value)
setattr(loaded_conf, field_name, merged_value)
elif not is_default(inp_conf, field_):
# Overwrite only if the current value is not default
"""
Overwrite only if the current value is not default
仅在当前值不是默认值时进行覆盖
如果 inp_conf 中的字段值不是默认值,则覆盖 loaded_conf 中的对应值
"""
setattr(loaded_conf, field_name, new_value)
return loaded_conf

View File

@@ -0,0 +1,9 @@
"""
示例工具模块
该模块包含各种示例工具的实现,用于演示代理系统的工具调用能力。
"""
from .calculator import Calculator
__all__ = ["Calculator"]

View File

@@ -13,7 +13,7 @@ from lang_agent.config import InstantiateConfig, ToolConfig
from lang_agent.dummy.calculator import Calculator, CalculatorConfig
from lang_agent.tool_manager import ToolManager, ToolManagerConfig
from catering_end.lang_tool import CartToolConfig, CartTool
# from catering_end.lang_tool import CartToolConfig, CartTool
@tyro.conf.configure(tyro.conf.SuppressFixed)
@dataclass
@@ -22,7 +22,7 @@ class MCPServerConfig(InstantiateConfig):
server_name:str = "langserver"
host: str = "6.6.6.78"
host: str = "127.0.0.1"
"""host of server"""
port: int = 50051

View File

@@ -0,0 +1,14 @@
"""
RAG (Retrieval Augmented Generation) 模块
该模块提供了检索增强生成的功能,包括:
- 嵌入向量生成和存储
- 相似度搜索和文档检索
- 基于FAISS的向量数据库支持
- 阿里云DashScope嵌入服务集成
"""
from .emb import QwenEmbeddings
from .simple import SimpleRag
__all__ = ["QwenEmbeddings", "SimpleRag"]

View File

@@ -7,18 +7,13 @@ import asyncio
import os.path as osp
from loguru import logger
from fastmcp.tools.tool import Tool
from lang_agent.config import InstantiateConfig, ToolConfig
from lang_agent.base import LangToolBase
from lang_agent.rag.simple import SimpleRagConfig
from lang_agent.dummy.calculator import CalculatorConfig
# from catering_end.lang_tool import CartToolConfig, CartTool
from langchain_core.tools.structured import StructuredTool
from lang_agent.client_tool_manager import ClientToolManager
import jax
@tyro.conf.configure(tyro.conf.SuppressFixed)
@dataclass
class ToolManagerConfig(InstantiateConfig):

View File

@@ -1,4 +1,5 @@
[project]
requires-python=">=3.8"
name = "lang_agent"
version = "0.1"
dependencies = [

View File

@@ -4,10 +4,10 @@ import os
from lang_agent.rag.emb import QwenEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain_text_splitters import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.schema import Document
from langchain_core.documents import Document
def main(save_path = "assets/xiaozhan_emb"):
cat_f = "assets/xiaozhan_data/catering_end_category.csv"

4431
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff