LLM Tool Proxy

1. 概述 (Overview)

本项目是一个基于 FastAPI 实现的智能 LLM大语言模型代理服务。其核心功能是拦截发往 LLM 的 API 请求,动态地将客户端定义的 tools工具信息注入到提示词Prompt然后将 LLM 返回的结果进行解析将其中可能包含的工具调用Tool Call指令提取出来最后以结构化的格式返回给调用者。

这使得即使底层 LLM 原生不支持工具调用参数,我们也能通过提示工程的方式赋予其使用工具的能力。

2. 设计原则 (Design Principles)

本程序在设计上严格遵循了以下原则:

  • 高内聚 (High Cohesion): 业务逻辑被集中在服务层 (app/services.py) 中,与 API 路由和数据模型分离。
  • 低耦合 (Low Coupling):
    • API 层 (app/main.py) 只负责路由和请求校验,不关心业务实现细节。
    • 通过依赖注入 (Depends) 获取配置,避免了全局状态。
    • LLM 调用被抽象为独立的函数,方便未来切换不同的 LLM 后端或在测试中使用模拟Mock实现。
  • 可测试性 (Testability): 项目包含了完整的单元测试和集成测试 (tests/),以及功能测试脚本,确保每个模块的正确性和整体流程的稳定性。

3. 项目结构 (Project Structure)

.
├── 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. 消息历史转换 (Message History Conversion)

新增功能 - 这是本次更新的核心功能之一。

  • 实现函数: app.services.convert_tool_calls_to_content
  • 策略:
    1. 遍历消息历史,识别 roleassistant 且包含 tool_calls 的消息。
    2. 将这些消息中的 tool_calls 转换为 LLM 可理解的 XML 格式 {"name": "tool_name", "arguments": {...}}
    3. 保留消息原有的 content 字段(如果存在)。
    4. 支持多个 tool_calls 的转换,用换行符连接。
  • 目的: 确保消息历史中的工具调用能够被底层 LLM 理解,保持对话上下文的连贯性。

转换示例:

# 转换前
{
  "role": "assistant",
  "tool_calls": [
    {"function": {"name": "get_weather", "arguments": '{"location": "北京"}'}}
  ]
}

# 转换后(发送给 LLM
{
  "role": "assistant",
  "content": "<invoke>{\"name\": \"get_weather\", \"arguments\": {\"location\": \"北京\"}}</invoke>"
}

4.2. 提示词注入 (Prompt Injection)

  • 实现函数: app.services.inject_tools_into_prompt
  • 策略:
    1. 将客户端请求中 tools 列表JSON数组序列化为格式化的 JSON 字符串。
    2. 创建一个新的、rolesystem 的独立消息。
    3. 此消息包含明确的指令,告诉 LLM 它拥有哪些工具以及如何通过特定的格式来调用它们。
    4. 调用格式约定: 指示 LLM 在需要调用工具时,必须输出一个 {"name": "tool_name", "arguments": {...}} 的 XML 标签。
    5. 这个系统消息被插入到消息列表的开头。
  • 目的: 对调用者透明,将工具使用的"契约"通过上下文传递给 LLM。

4.3. 响应解析 (Response Parsing)

  • 实现类: app.response_parser.ResponseParser
  • 策略:
    1. 使用非贪婪正则表达式在 LLM 返回的文本响应中查找所有 ... 标签。
    2. 支持同时解析多个 tool_calls
    3. 提取工具调用前后的文本内容,合并到 content 字段。
    4. 如果找到工具调用,将 tool_calls 字段填充为结构化的 ToolCall 对象列表。
    5. 如果未找到标签,则将 LLM 的全部响应视为常规的文本内容。
  • 新特性:
    • 支持多个 tool_calls 同时解析 - 使用非贪婪匹配和 finditer
    • 支持 content 和 tool_calls 同时存在 - 符合 OpenAI API 规范
    • 支持文本在前、在后或前后都有文本的场景
  • 目的: 将 LLM 的非结构化输出转换为标准的 OpenAI 格式响应。

响应示例:

{
  "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 函数通过依赖注入的方式在每次请求时加载环境变量。
  • 必需变量:
    • REAL_LLM_API_URL: 真实 LLM 后端的地址
    • REAL_LLM_API_KEY: 用于访问真实 LLM 的 API 密钥

6. 如何运行与测试 (Usage)

6.1. 环境设置

# 创建虚拟环境
python -m venv .venv

# 激活虚拟环境
source .venv/bin/activate  # Linux/Mac
# 或
.venv\Scripts\activate     # Windows

# 安装依赖
pip install fastapi uvicorn httpx pytest python-dotenv

6.2. 配置环境变量

创建 .env 文件:

REAL_LLM_API_URL="https://api.example.com/v1/chat/completions"
REAL_LLM_API_KEY="your-api-key"

6.3. 运行开发服务器

uvicorn app.main:app --reload

服务将运行在 http://127.0.0.1:8000

6.4. 运行测试

# 运行所有单元测试
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

请求示例 (带工具):

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?"}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Get weather for a city",
        "parameters": {
          "type": "object",
          "properties": {
            "location": {"type": "string"}
          }
        }
      }
    }
  ]
}'

7.2. 带消息历史的请求

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 输出:
<invoke>{"name": "get_weather", "arguments": {"location": "北京"}}</invoke>
<invoke>{"name": "get_weather", "arguments": {"location": "上海"}}</invoke>

解析为:
{
  "tool_calls": [
    {"function": {"name": "get_weather", ...}},
    {"function": {"name": "get_weather", ...}}
  ]
}

8.2. 内容和工具调用混合

支持同时返回文本内容和工具调用:

LLM 输出:
好的,我来帮你查询。
<invoke>{"name": "search", "arguments": {"query": "..."}}</invoke>
请稍等片刻。

解析为:
{
  "content": "好的,我来帮你查询。 请稍等片刻。",
  "tool_calls": [...]
}

8.3. OpenAI 兼容性

  • 完全兼容 OpenAI Chat Completions API 格式
  • 支持流式和非流式响应
  • 支持工具调用定义和执行
  • ⚠️ 注意:虽然 OpenAI 的 GPT-4o 等模型通常只返回 contenttool_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 接口
  • 流式和非流式响应支持
Description
No description provided
Readme 129 KiB
Languages
Python 99.7%
Dockerfile 0.3%