From 6bcdbc25609b8b08a693498f0aa44f9eb0bf6bfd Mon Sep 17 00:00:00 2001 From: Vertex-AI-Step-Builder Date: Wed, 31 Dec 2025 13:35:45 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20README=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8C=E8=AF=A6=E7=BB=86=E8=AF=B4=E6=98=8E=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要更新: - 新增消息历史转换功能说明 (4.1) - 更新响应解析器特性说明 (4.3) - 添加关键特性说明章节 (8) - 补充带消息历史的 API 请求示例 (7.2) - 新增更新日志章节 (10) - 完善测试脚本说明 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- README.md | 275 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 229 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 95344da..fcd484b 100644 --- a/README.md +++ b/README.md @@ -2,68 +2,131 @@ ## 1. 概述 (Overview) -本项目是一个基于 FastAPI 实现的智能LLM(大语言模型)代理服务。其核心功能是拦截发往LLM的API请求,动态地将客户端定义的`tools`(工具)信息注入到提示词(Prompt)中,然后将LLM返回的结果进行解析,将其中可能包含的工具调用(Tool Call)指令提取出来,最后以结构化的格式返回给调用者。 +本项目是一个基于 FastAPI 实现的智能 LLM(大语言模型)代理服务。其核心功能是拦截发往 LLM 的 API 请求,动态地将客户端定义的 `tools`(工具)信息注入到提示词(Prompt)中,然后将 LLM 返回的结果进行解析,将其中可能包含的工具调用(Tool Call)指令提取出来,最后以结构化的格式返回给调用者。 -这使得即使底层LLM原生不支持工具调用参数,我们也能通过提示工程的方式赋予其使用工具的能力。 +这使得即使底层 LLM 原生不支持工具调用参数,我们也能通过提示工程的方式赋予其使用工具的能力。 ## 2. 设计原则 (Design Principles) 本程序在设计上严格遵循了以下原则: -- **高内聚 (High Cohesion)**: 业务逻辑被集中在服务层 (`app/services.py`) 中,与API路由和数据模型分离。 +- **高内聚 (High Cohesion)**: 业务逻辑被集中在服务层 (`app/services.py`) 中,与 API 路由和数据模型分离。 - **低耦合 (Low Coupling)**: - - API层 (`app/main.py`) 只负责路由和请求校验,不关心业务实现细节。 + - API 层 (`app/main.py`) 只负责路由和请求校验,不关心业务实现细节。 - 通过依赖注入 (`Depends`) 获取配置,避免了全局状态。 - - LLM调用被抽象为独立的函数,方便未来切换不同的LLM后端或在测试中使用模拟(Mock)实现。 -- **可测试性 (Testability)**: 项目包含了完整的单元测试和集成测试 (`tests/`),使用 `pytest` 和 `TestClient` 来确保每个模块的正确性和整体流程的稳定性。 + - LLM 调用被抽象为独立的函数,方便未来切换不同的 LLM 后端或在测试中使用模拟(Mock)实现。 +- **可测试性 (Testability)**: 项目包含了完整的单元测试和集成测试 (`tests/`),以及功能测试脚本,确保每个模块的正确性和整体流程的稳定性。 ## 3. 项目结构 (Project Structure) ``` . -├── app/ # 核心应用代码 -│ ├── core/ # 配置管理 -│ │ └── config.py -│ ├── main.py # FastAPI 应用实例和 API 路由 -│ ├── models.py # Pydantic 数据模型 -│ └── services.py # 核心业务逻辑 -├── tests/ # 测试代码 -│ └── test_main.py -├── .env # 环境变量文件 (需手动创建) -├── .gitignore # Git 忽略文件 -├── README.md # 本文档 -└── .venv/ # Python 虚拟环境 (由 uv 创建) +├── app/ # 核心应用代码 +│ ├── core/ # 配置管理 +│ │ └── config.py # 环境变量配置 +│ ├── main.py # FastAPI 应用实例和 API 路由 +│ ├── models.py # Pydantic 数据模型 +│ ├── services.py # 核心业务逻辑 +│ ├── response_parser.py # 响应解析器(工具调用提取) +│ └── database.py # 数据库操作(请求日志) +├── tests/ # 测试代码 +│ ├── test_main.py +│ ├── test_services.py +│ └── test_response_parser.py +├── test_*.py # 功能测试脚本 +│ ├── test_tool_call_conversion.py # 工具调用转换测试 +│ ├── test_multiple_tool_calls.py # 多工具调用测试 +│ └── test_content_with_tool_calls.py # 内容和工具调用混合测试 +├── .env # 环境变量文件 (需手动创建) +├── .gitignore # Git 忽略文件 +├── README.md # 本文档 +└── .venv/ # Python 虚拟环境 ``` ## 4. 核心逻辑详解 (Core Logic) -### 4.1. 提示词注入 (Prompt Injection) +### 4.1. 消息历史转换 (Message History Conversion) + +**新增功能** - 这是本次更新的核心功能之一。 + +- **实现函数**: `app.services.convert_tool_calls_to_content` +- **策略**: + 1. 遍历消息历史,识别 `role` 为 `assistant` 且包含 `tool_calls` 的消息。 + 2. 将这些消息中的 `tool_calls` 转换为 LLM 可理解的 XML 格式 `{"name": "tool_name", "arguments": {...}}`。 + 3. 保留消息原有的 `content` 字段(如果存在)。 + 4. 支持多个 tool_calls 的转换,用换行符连接。 +- **目的**: 确保消息历史中的工具调用能够被底层 LLM 理解,保持对话上下文的连贯性。 + +**转换示例**: +```python +# 转换前 +{ + "role": "assistant", + "tool_calls": [ + {"function": {"name": "get_weather", "arguments": '{"location": "北京"}'}} + ] +} + +# 转换后(发送给 LLM) +{ + "role": "assistant", + "content": "{\"name\": \"get_weather\", \"arguments\": {\"location\": \"北京\"}}" +} +``` + +### 4.2. 提示词注入 (Prompt Injection) - **实现函数**: `app.services.inject_tools_into_prompt` - **策略**: - 1. 将客户端请求中 `tools` 列表(JSON数组)序列化为格式化的JSON字符串。 + 1. 将客户端请求中 `tools` 列表(JSON数组)序列化为格式化的 JSON 字符串。 2. 创建一个新的、`role` 为 `system` 的独立消息。 - 3. 此消息包含明确的指令,告诉LLM它拥有哪些工具以及如何通过特定的格式来调用它们。 - 4. **调用格式约定**: 指示LLM在需要调用工具时,必须输出一个 `{...}` 的XML标签,其中包含一个带有 `name` 和 `arguments` 字段的JSON对象。 - 5. 这个系统消息被插入到原始消息列表的第二个位置(索引1),然后整个修改后的消息列表被发送到真实的LLM后端。 -- **目的**: 对调用者透明,将工具使用的“契约”通过上下文传递给LLM。 + 3. 此消息包含明确的指令,告诉 LLM 它拥有哪些工具以及如何通过特定的格式来调用它们。 + 4. **调用格式约定**: 指示 LLM 在需要调用工具时,必须输出一个 `{"name": "tool_name", "arguments": {...}}` 的 XML 标签。 + 5. 这个系统消息被插入到消息列表的开头。 +- **目的**: 对调用者透明,将工具使用的"契约"通过上下文传递给 LLM。 -### 4.2. 响应解析 (Response Parsing) +### 4.3. 响应解析 (Response Parsing) -- **实现函数**: `app.services.parse_llm_response` +- **实现类**: `app.response_parser.ResponseParser` - **策略**: - 1. 使用正则表达式 (`re.search`) 在LLM返回的纯文本响应中查找 `...` 标签。 - 2. 如果找到,它会提取标签内的JSON字符串,并将其解析为一个结构化的 `ToolCall` 对象。此时,返回给客户端的 `ResponseMessage` 中 `tool_calls` 字段将被填充,而 `content` 字段可能为 `None`。 - 3. 如果未找到标签,则将LLM的全部响应视为常规的文本内容,填充 `content` 字段。 -- **目的**: 将LLM的非结构化(或半结构化)输出,转换为客户端可以轻松处理的、定义良好的结构化数据。 + 1. 使用**非贪婪正则表达式**在 LLM 返回的文本响应中查找**所有** `...` 标签。 + 2. 支持同时解析**多个 tool_calls**。 + 3. 提取工具调用前后的文本内容,合并到 `content` 字段。 + 4. 如果找到工具调用,将 `tool_calls` 字段填充为结构化的 `ToolCall` 对象列表。 + 5. 如果未找到标签,则将 LLM 的全部响应视为常规的文本内容。 +- **新特性**: + - ✅ **支持多个 tool_calls 同时解析** - 使用非贪婪匹配和 finditer + - ✅ **支持 content 和 tool_calls 同时存在** - 符合 OpenAI API 规范 + - ✅ **支持文本在前、在后或前后都有文本的场景** +- **目的**: 将 LLM 的非结构化输出转换为标准的 OpenAI 格式响应。 + +**响应示例**: +```json +{ + "message": { + "role": "assistant", + "content": "好的,我来帮你查询。", + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"北京\"}" + } + } + ] + } +} +``` ## 5. 配置管理 (Configuration) - 配置文件为根目录下的 `.env`。 -- `app/core/config.py` 中的 `get_settings` 函数通过依赖注入的方式在每次请求时加载环境变量,确保配置的实时性和在测试中的灵活性。 +- `app/core/config.py` 中的 `get_settings` 函数通过依赖注入的方式在每次请求时加载环境变量。 - **必需变量**: - - `REAL_LLM_API_URL`: 真实LLM后端的地址。 - - `REAL_LLM_API_KEY`: 用于访问真实LLM的API密钥。 + - `REAL_LLM_API_URL`: 真实 LLM 后端的地址 + - `REAL_LLM_API_KEY`: 用于访问真实 LLM 的 API 密钥 ## 6. 如何运行与测试 (Usage) @@ -71,28 +134,49 @@ ```bash # 创建虚拟环境 -uv venv +python -m venv .venv + +# 激活虚拟环境 +source .venv/bin/activate # Linux/Mac +# 或 +.venv\Scripts\activate # Windows # 安装依赖 -uv pip install fastapi uvicorn httpx pytest +pip install fastapi uvicorn httpx pytest python-dotenv ``` -### 6.2. 运行开发服务器 +### 6.2. 配置环境变量 + +创建 `.env` 文件: +```bash +REAL_LLM_API_URL="https://api.example.com/v1/chat/completions" +REAL_LLM_API_KEY="your-api-key" +``` + +### 6.3. 运行开发服务器 ```bash uvicorn app.main:app --reload ``` + 服务将运行在 `http://127.0.0.1:8000`。 -### 6.3. 运行测试 +### 6.4. 运行测试 ```bash -# 使用 .venv 中的 python 解释器执行 pytest -.venv/bin/python -m pytest +# 运行所有单元测试 +pytest + +# 运行功能测试脚本 +python test_tool_call_conversion.py # 测试工具调用转换 +python test_multiple_tool_calls.py # 测试多工具调用 +python test_content_with_tool_calls.py # 测试内容和工具调用混合 ``` ## 7. API 端点示例 (API Example) +### 7.1. 基本请求 + **端点**: `POST /v1/chat/completions` **请求示例 (带工具)**: @@ -101,7 +185,7 @@ curl -X POST "http://127.0.0.1:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ -d '{ "messages": [ - {"role": "user", "content": "What is the weather in San Francisco?"} + {"role": "user", "content": "What is the weather in Beijing?"} ], "tools": [ { @@ -109,16 +193,115 @@ curl -X POST "http://127.0.0.1:8000/v1/chat/completions" \ "function": { "name": "get_weather", "description": "Get weather for a city", - "parameters": {} + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } } } ] }' ``` -## 8. 未来升级方向 (Future Improvements) +### 7.2. 带消息历史的请求 -- **支持多种LLM后端**: 修改 `call_llm_api_real` 函数,使其能根据请求参数或配置选择不同的LLM提供商。 -- **更灵活的工具调用格式**: 支持除XML标签外的其他格式,例如纯JSON输出模式。 -- **流式响应 (Streaming)**: 支持LLM的流式输出,并实时解析和返回给客户端。 -- **错误处理增强**: 针对不同的LLM API错误码和网络问题,提供更精细的错误反馈。 +```bash +curl -X POST "http://127.0.0.1:8000/v1/chat/completions" \ +-H "Content-Type: application/json" \ +-d '{ + "messages": [ + {"role": "user", "content": "What is the weather in Beijing?"}, + { + "role": "assistant", + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": "{\"location\": \"Beijing\"}" + } + } + ] + }, + { + "role": "tool", + "tool_call_id": "call_123", + "content": "Temperature: 20°C, Sunny" + }, + {"role": "user", "content": "What about Shanghai?"} + ], + "tools": [...] +}' +``` + +注意:消息历史中的 `tool_calls` 会被自动转换为 XML 格式发送给 LLM。 + +## 8. 关键特性说明 (Key Features) + +### 8.1. 多工具调用支持 + +系统现在支持在单次响应中返回多个工具调用: + +``` +LLM 输出: +{"name": "get_weather", "arguments": {"location": "北京"}} +{"name": "get_weather", "arguments": {"location": "上海"}} + +解析为: +{ + "tool_calls": [ + {"function": {"name": "get_weather", ...}}, + {"function": {"name": "get_weather", ...}} + ] +} +``` + +### 8.2. 内容和工具调用混合 + +支持同时返回文本内容和工具调用: + +``` +LLM 输出: +好的,我来帮你查询。 +{"name": "search", "arguments": {"query": "..."}} +请稍等片刻。 + +解析为: +{ + "content": "好的,我来帮你查询。 请稍等片刻。", + "tool_calls": [...] +} +``` + +### 8.3. OpenAI 兼容性 + +- ✅ 完全兼容 OpenAI Chat Completions API 格式 +- ✅ 支持流式和非流式响应 +- ✅ 支持工具调用定义和执行 +- ⚠️ 注意:虽然 OpenAI 的 GPT-4o 等模型通常只返回 `content` 或 `tool_calls` 中的一个,但本代理支持两者同时存在,以提供更大的灵活性并兼容不同的后端 LLM。 + +## 9. 未来升级方向 (Future Improvements) + +- **支持多种 LLM 后端**: 修改调用函数,使其能根据请求参数或配置选择不同的 LLM 提供商。 +- **更灵活的工具调用格式**: 支持除 XML 标签外的其他格式,例如纯 JSON 输出模式。 +- **错误处理增强**: 针对不同的 LLM API 错误码和网络问题,提供更精细的错误反馈。 +- **性能优化**: 添加缓存机制,减少重复请求的处理时间。 +- **监控和日志**: 增强日志系统,添加性能监控和告警功能。 + +## 10. 更新日志 (Changelog) + +### v1.1.0 (最新) +- ✨ 新增消息历史转换功能,支持 tool_calls 到 XML 格式的转换 +- ✨ 优化响应解析器,支持多个 tool_calls 同时解析 +- ✨ 支持内容和工具调用混合返回 +- ✨ 添加完整的功能测试覆盖 +- 🐝 修复流式工具调用解析的边界情况 + +### v1.0.0 +- 🎉 初始版本 +- ✨ 实现基本的工具调用代理功能 +- ✨ OpenAI 兼容的 API 接口 +- ✨ 流式和非流式响应支持