Function Calling 架构是 HelloAgents 框架的核心重构,将 LLM 基类和所有 Agent 类型统一为 Function Calling 模式,解析成功率从 85% 提升到 99%+。
- ✅ LLM 基类重构:invoke_with_tools() 统一接口
- ✅ Agent 基类重构:所有 Agent 类型使用 Function Calling
- ✅ 解析成功率提升:85% → 99%+
- ✅ 向后兼容:现有代码无需修改
from hello_agents import ReActAgent, HelloAgentsLLM, ToolRegistry
from hello_agents.tools.builtin import ReadTool, SearchTool
# 创建工具注册表
registry = ToolRegistry()
registry.register_tool(ReadTool(project_root="./"))
registry.register_tool(SearchTool())
# 创建 Agent(自动使用 Function Calling)
agent = ReActAgent("assistant", HelloAgentsLLM(), tool_registry=registry)
# 执行任务
result = agent.run("读取 README.md 并搜索相关文档")from hello_agents.llm import HelloAgentsLLM
from hello_agents.tools.builtin import ReadTool
llm = HelloAgentsLLM()
tool = ReadTool(project_root="./")
# 使用 Function Calling
response = llm.invoke_with_tools(
messages=[{"role": "user", "content": "读取 config.py"}],
tools=[tool]
)
# 解析工具调用
if response.tool_calls:
for tool_call in response.tool_calls:
print(f"工具: {tool_call.name}")
print(f"参数: {tool_call.arguments}")旧方案(Prompt 工程):
# ❌ 问题:解析失败率高(15%)
prompt = """
你有以下工具:
- Read(path: str): 读取文件
- Search(query: str): 搜索文档
请按以下格式输出:
Action: Read
Action Input: {"path": "config.py"}
"""
# LLM 可能输出:
# - "Action: read" (大小写错误)
# - "Action Input: {path: config.py}" (JSON 格式错误)
# - "我将使用 Read 工具..." (格式完全错误)新方案(Function Calling):
# ✅ 优势:LLM 原生支持,解析成功率 99%+
response = llm.invoke_with_tools(
messages=[{"role": "user", "content": "读取 config.py"}],
tools=[ReadTool()]
)
# LLM 返回结构化的工具调用:
# {
# "tool_calls": [
# {
# "id": "call_xxx",
# "name": "Read",
# "arguments": {"path": "config.py"}
# }
# ]
# }核心方法:invoke_with_tools()
class BaseLLM:
def invoke_with_tools(
self,
messages: List[Dict],
tools: List[BaseTool],
**kwargs
) -> LLMResponse:
"""
使用 Function Calling 调用 LLM
Args:
messages: 对话历史
tools: 可用工具列表
**kwargs: 额外参数(temperature、max_tokens 等)
Returns:
LLMResponse: 包含 content 和 tool_calls
"""
passLLMResponse 数据结构:
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class ToolCall:
id: str
name: str
arguments: Dict[str, Any]
@dataclass
class LLMResponse:
content: str # LLM 文本输出
tool_calls: Optional[List[ToolCall]] # 工具调用列表
usage: Dict[str, int] # Token 使用统计所有 Agent 类型统一使用 Function Calling:
class BaseAgent:
def _call_llm(self, messages: List[Dict]) -> LLMResponse:
"""调用 LLM(使用 Function Calling)"""
return self.llm.invoke_with_tools(
messages=messages,
tools=self.tool_registry.get_all_tools()
)
def _execute_tool_calls(self, tool_calls: List[ToolCall]) -> List[str]:
"""执行工具调用"""
results = []
for tool_call in tool_calls:
tool = self.tool_registry.get_tool(tool_call.name)
result = tool.run(tool_call.arguments)
results.append(result)
return resultsfrom hello_agents import ReActAgent, HelloAgentsLLM, ToolRegistry
from hello_agents.tools.builtin import ReadTool, WriteTool
registry = ToolRegistry()
registry.register_tool(ReadTool(project_root="./"))
registry.register_tool(WriteTool(project_root="./"))
agent = ReActAgent("assistant", HelloAgentsLLM(), tool_registry=registry)
# Agent 内部流程:
# 1. 调用 llm.invoke_with_tools(messages, tools)
# 2. 解析 tool_calls
# 3. 执行工具
# 4. 将结果添加到历史
# 5. 继续循环
result = agent.run("读取 config.py,修改端口为 8080,保存")from hello_agents import ReflectionAgent, HelloAgentsLLM
agent = ReflectionAgent("thinker", HelloAgentsLLM(), tool_registry=registry)
# ReflectionAgent 流程:
# 1. 执行阶段:使用 Function Calling 调用工具
# 2. 反思阶段:评估执行结果
# 3. 改进阶段:根据反思调整策略
result = agent.run("分析项目架构")from hello_agents import PlanSolveAgent, HelloAgentsLLM
agent = PlanSolveAgent("planner", HelloAgentsLLM(), tool_registry=registry)
# PlanSolveAgent 流程:
# 1. 规划阶段:生成执行计划
# 2. 执行阶段:使用 Function Calling 调用工具
# 3. 验证阶段:检查结果
result = agent.run("重构项目结构")from hello_agents import SimpleAgent, HelloAgentsLLM
agent = SimpleAgent("assistant", HelloAgentsLLM(), tool_registry=registry)
# SimpleAgent 流程:
# 1. 单次调用 llm.invoke_with_tools()
# 2. 执行所有工具调用
# 3. 返回结果
result = agent.run("读取 README.md")旧方案(Prompt 工程):
# 测试 100 次工具调用
# 成功:85 次
# 失败:15 次
# 失败原因:
# - 大小写错误:5 次
# - JSON 格式错误:7 次
# - 格式完全错误:3 次新方案(Function Calling):
# 测试 100 次工具调用
# 成功:99 次
# 失败:1 次(LLM 幻觉,调用不存在的工具)
# 成功率提升:85% → 99%场景: 同时调用多个工具
# LLM 返回多个工具调用
response = llm.invoke_with_tools(
messages=[{"role": "user", "content": "读取 config.py 和 main.py"}],
tools=[ReadTool()]
)
# response.tool_calls:
# [
# ToolCall(id="call_1", name="Read", arguments={"path": "config.py"}),
# ToolCall(id="call_2", name="Read", arguments={"path": "main.py"})
# ]
# Agent 并行执行两个工具调用场景: LLM 调用不存在的工具
response = llm.invoke_with_tools(
messages=[{"role": "user", "content": "删除文件"}],
tools=[ReadTool(), WriteTool()]
)
# LLM 可能返回:
# ToolCall(name="Delete", arguments={"path": "file.txt"})
# Agent 处理:
if tool_call.name not in registry:
error_message = f"工具 {tool_call.name} 不存在"
# 将错误添加到历史,让 LLM 重新选择工具class ReadTool(BaseTool):
name = "Read"
description = "读取文件内容。参数:path (str) - 文件路径"
# ✅ 好:清晰的描述帮助 LLM 正确调用class ReadTool(BaseTool):
def run(self, parameters: Dict) -> ToolResponse:
# 验证参数
if "path" not in parameters:
return ToolResponse.error(
ErrorCode.INVALID_PARAMETERS,
"缺少 path 参数"
)
path = parameters["path"]
# 执行读取...# Agent 内部错误处理
try:
response = self.llm.invoke_with_tools(messages, tools)
for tool_call in response.tool_calls:
if tool_call.name not in self.tool_registry:
# 工具不存在,添加错误消息
error_msg = f"工具 {tool_call.name} 不存在"
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": error_msg
})
except Exception as e:
# LLM 调用失败
logger.error(f"LLM 调用失败: {e}")class CustomLLM(BaseLLM):
def invoke_with_tools(self, messages, tools, **kwargs):
# 转换工具为 OpenAI Function Calling 格式
functions = [
{
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters_schema
}
for tool in tools
]
# 调用 LLM
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
functions=functions,
**kwargs
)
# 解析响应
return self._parse_response(response)import asyncio
async def execute_tools_parallel(tool_calls: List[ToolCall]):
tasks = [
tool_registry.get_tool(tc.name).arun(tc.arguments)
for tc in tool_calls
]
results = await asyncio.gather(*tasks)
return resultsfrom hello_agents.core.lifecycle import LifecycleHook
class ToolCallTracker(LifecycleHook):
async def on_tool_call(self, event):
tool_call = event.data["tool_call"]
print(f"调用工具: {tool_call.name}")
print(f"参数: {tool_call.arguments}")Q: Function Calling 支持哪些 LLM?
A: 支持所有主流 LLM:
- OpenAI: GPT-4、GPT-3.5
- Anthropic: Claude 3
- DeepSeek: DeepSeek-Chat
- 其他支持 Function Calling 的模型
Q: 如何禁用 Function Calling?
A: 不推荐禁用,但可以使用旧版本:
# 使用 v1.x 版本(Prompt 工程)
agent = ReActAgent("assistant", llm, use_function_calling=False)Q: Function Calling 的性能开销?
A: 几乎没有开销:
- LLM 原生支持,无需额外解析
- 减少了 Prompt 长度
- 提高了解析成功率
Q: 如何调试 Function Calling?
A: 使用 TraceLogger:
from hello_agents.core.observability import TraceLogger
logger = TraceLogger(output_dir="logs")
agent = ReActAgent("assistant", llm, trace_logger=logger)
# 查看 logs/trace.jsonl 和 logs/trace.html| 方案 | 成功率 | 失败原因 |
|---|---|---|
| Prompt 工程 | 85% | 格式错误、大小写、JSON 错误 |
| Function Calling | 99%+ | LLM 幻觉(调用不存在的工具) |
| 方案 | Prompt Tokens | 节省比例 |
|---|---|---|
| Prompt 工程 | 500 | 0% |
| Function Calling | 300 | 40% |
最后更新: 2026-02-21