Mini Agent

一个完全用于学习的最小 Python Agent

Python 3.10+ OpenAI Chat Completions Agent Tool Calling DDGS Web Search MIT License

项目不使用 LangChain、AutoGen 等 Agent 框架,只使用 OpenAI Python SDK 展示 Agent 最核心的工作方式: ```text 用户问题 -> 调用 OpenAI -> 模型决定是否调用工具 -> Python 执行工具 -> 将工具结果回填给模型 -> 再次调用 OpenAI -> 模型返回最终答案 ``` ## 设计目标 - 只支持 OpenAI Chat Completions 风格接口。 - 一次运行只回答一个问题,不提供 REPL 交互模式。 - 核心代码集中在 `agent.py`,便于从上到下完整阅读。 - 记录每一步请求、OpenAI 原始响应、工具调用和工具结果。 - 保留最大执行步数,防止模型无限调用工具。 - 工具实现刻意保持简单,让注意力集中在 Agent 主循环。 ## 项目结构 ```text mini-agent/ ├── agent.py # Agent 循环、OpenAI 调用和工具实现 ├── requirements.txt # Python 依赖 ├── .env.example # 环境变量示例,仅作参考 ├── .gitignore # 排除本地缓存和密钥文件 └── README.md ``` ## 环境要求 - Python 3.10 或更高版本 - OpenAI API Key ## 安装 ```sh python -m pip install -r requirements.txt ``` ## 配置 运行前需要通过当前操作系统或 Shell 的环境变量设置方式配置: | 环境变量 | 必填 | 默认值 | 说明 | |---|---|---|---| | `OPENAI_API_KEY` | 是 | 无 | OpenAI API Key | | `OPENAI_MODEL` | 否 | `gpt-4o-mini` | 默认模型 | | `OPENAI_BASE_URL` | 否 | OpenAI 官方地址 | OpenAI 兼容接口地址 | ## 运行 每次命令只执行一个问题: ```sh python agent.py "现在几点?" python agent.py "请计算 (123 + 456) * 2" python agent.py "搜索 Python 3.14 的主要变化" ``` 指定模型: ```sh python agent.py --model gpt-4o-mini "计算 2 ** 10" ``` 限制最大 Agent 步数: ```sh python agent.py --max-steps 4 "现在几点,再计算 25 * 16" ``` 关闭过程日志,只显示最终答案: ```sh python agent.py --quiet "计算 100 / 4" ``` ## 日志说明 默认情况下,每条日志都会带上源码文件名、行号和函数名,例如: ```text 2026-07-27 15:00:00,000 | INFO | agent.py:164 | run_agent() | OpenAI raw response: ``` 程序会输出以下可观测信息: 1. 当前 Agent 步数。 2. 本次发送给 OpenAI 的完整 `messages`。 3. OpenAI SDK 返回的原始响应。 4. 模型要求调用的工具名和参数。 5. Python 工具的执行结果。 6. Agent 是否已经生成最终答案。 示意流程: ```text Agent step 1 OpenAI request messages: user question OpenAI raw response: assistant requests calculate(...) Tool call: calculate Tool result: 1694 Agent step 2 OpenAI request messages: question + tool call + tool result OpenAI raw response: assistant final answer Answer: 结果是 1694 ``` 日志不会输出 `OPENAI_API_KEY`,但会包含用户问题、模型回答和工具结果。处理敏感数据时应使用 `--quiet`,或者进一步调整日志策略。 ## 核心实现解析 ### 1. 工具 Schema `TOOL_SCHEMAS` 使用 OpenAI function calling 格式向模型描述工具。模型只能看到工具名称、说明和参数结构,看不到 Python 函数源码。 本项目提供三个工具: - `get_current_time()`:返回当前本地时间。 - `calculate(expression)`:使用简洁的 `eval()` 计算表达式,仅供教学。 - `web_search(query)`:通过 `ddgs` 执行真实网页搜索,返回最多 5 条结果。 ### 2. 工具注册表 `TOOL_FUNCTIONS` 建立工具名到 Python 函数的映射: ```python TOOL_FUNCTIONS = { "get_current_time": get_current_time, "calculate": calculate, } ``` 模型返回 `calculate` 时,程序通过这个字典找到并执行真正的 Python 函数。 ### 3. Agent Loop `run_agent()` 是项目核心。它维护一个 `messages` 列表,并在每一步调用: ```python client.chat.completions.create( model=model, messages=messages, tools=TOOL_SCHEMAS, tool_choice="auto", ) ``` 如果响应中没有 `tool_calls`,模型的文本就是最终答案。如果存在 `tool_calls`,程序执行工具,并追加一条 `role="tool"` 的消息: ```python { "role": "tool", "tool_call_id": tool_call.id, "content": result, } ``` 下一次请求会携带完整历史,因此模型能看到工具执行结果并继续回答。 ### 4. 为什么需要 `tool_call_id` 一次响应可能包含多个工具调用。`tool_call_id` 用于准确关联模型发出的工具请求和 Python 返回的工具结果。 ### 5. 为什么需要 `max_steps` 模型可能持续或重复调用工具。`max_steps` 给循环设置明确上限,避免程序无限运行。默认最多执行 8 轮。 ## 如何添加新工具 以新增天气工具为例: 1. 在 `agent.py` 中实现 Python 函数: ```python def get_weather(city: str) -> str: return "这里调用真实天气 API" ``` 2. 注册函数: ```python TOOL_FUNCTIONS["get_weather"] = get_weather ``` 3. 在 `TOOL_SCHEMAS` 中增加对应 JSON Schema。 Schema 名称、注册表名称和函数参数必须保持一致。 ## 与生产级 Agent 的差距 本项目刻意保持教学性质,没有实现: 1. 流式输出 2. 多轮对话历史 3. 上下文压缩 4. 并行工具调用 这些能力很重要,但不属于理解 Agent 最小循环所必需的部分。 ## 安全说明 - 不要将 API Key 写入 `agent.py` 或提交到 Git。 - `calculate()` 为了教学简洁性使用了受限命名空间的 `eval()`,仍不应处理不可信输入或用于生产环境。 - 接入文件、Shell、数据库或网络工具时,需要额外增加权限控制、参数验证、超时和隔离机制。 - OpenAI API 调用可能产生费用,请关注所选模型和账户用量。 ## 开源协议 本项目基于 [MIT License](LICENSE) 开源。你可以自由使用、复制、修改、合并、发布和分发本项目,但需要保留原始版权声明和许可证文本。