从零实现DeepSeek Agent工具调用:原生Function Calling全流程解析

不依赖任何框架,手写原生 DeepSeek Agent 工具调用(Function Calling)详解

一、为什么需要原生实现Function Calling?

在Agent开发场景中,工具调用(Function Calling)能力是连接LLM与外部系统的关键桥梁。当前主流方案依赖LangChain、LlamaIndex等框架,但这些框架存在以下问题:

  1. 黑箱效应:框架封装了底层协议,开发者难以理解工具调用的完整流程
  2. 性能损耗:多层抽象导致额外的序列化/反序列化开销
  3. 定制限制:框架的预设模式难以满足复杂业务场景的定制需求

原生实现的优势在于:

  • 完全掌控消息协议和执行流程
  • 减少不必要的中间层损耗
  • 支持高度定制化的工具注册机制
  • 便于与现有系统深度集成

二、Function Calling核心机制解析

1. 消息协议设计

工具调用的核心是LLM与Agent之间的结构化消息交互。关键字段包括:

  1. {
  2. "messages": [
  3. {
  4. "role": "user",
  5. "content": "查询北京今天的天气"
  6. },
  7. {
  8. "role": "assistant",
  9. "function_call": {
  10. "name": "get_weather",
  11. "arguments": {
  12. "city": "北京",
  13. "date": "2023-07-20"
  14. }
  15. }
  16. }
  17. ]
  18. }

协议设计要点:

  • 明确区分用户消息与工具调用消息
  • 工具参数需符合JSON Schema规范
  • 支持嵌套参数结构(如address.city

2. 工具注册系统实现

原生工具注册需要实现三个核心组件:

  1. class ToolRegistry:
  2. def __init__(self):
  3. self.tools = {}
  4. self.schemas = {}
  5. def register(self, name, func, schema):
  6. """注册工具函数及其参数schema"""
  7. self.tools[name] = func
  8. self.schemas[name] = schema
  9. def get_tool(self, name):
  10. """获取工具函数"""
  11. return self.tools.get(name)
  12. def validate_args(self, name, args):
  13. """参数校验"""
  14. schema = self.schemas.get(name)
  15. # 实现JSON Schema验证逻辑
  16. return True # 简化示例

工具Schema设计示例:

  1. {
  2. "get_weather": {
  3. "type": "object",
  4. "properties": {
  5. "city": {"type": "string"},
  6. "date": {"type": "string", "format": "date"}
  7. },
  8. "required": ["city"]
  9. }
  10. }

3. 工具调用执行流程

完整执行流程包含6个关键步骤:

  1. 消息解析:从LLM输出中提取function_call字段
  2. 参数校验:根据工具Schema验证参数有效性
  3. 工具查找:在注册表中定位对应的工具函数
  4. 参数转换:将JSON参数转换为函数调用参数
  5. 执行调用:同步/异步执行工具函数
  6. 结果包装:将执行结果转为LLM可理解的格式

关键代码实现:

  1. def execute_function_call(registry, message):
  2. if "function_call" not in message:
  3. return None
  4. call = message["function_call"]
  5. tool_name = call["name"]
  6. tool_args = call["arguments"]
  7. # 参数校验
  8. if not registry.validate_args(tool_name, tool_args):
  9. raise ValueError("Invalid arguments")
  10. # 获取并执行工具
  11. tool = registry.get_tool(tool_name)
  12. if not tool:
  13. raise ValueError(f"Tool {tool_name} not found")
  14. # 参数转换(示例简化)
  15. args = convert_args(tool_args) # 实现参数类型转换
  16. result = tool(*args)
  17. # 结果包装
  18. return {
  19. "role": "function",
  20. "name": tool_name,
  21. "content": str(result)
  22. }

三、高级功能实现

1. 异步工具支持

对于耗时操作(如API调用、数据库查询),需要实现异步工具调用:

  1. import asyncio
  2. class AsyncToolRegistry(ToolRegistry):
  3. async def execute_async(self, call):
  4. tool = self.get_tool(call["name"])
  5. if asyncio.iscoroutinefunction(tool):
  6. args = convert_args(call["arguments"])
  7. result = await tool(*args)
  8. return self._package_result(call["name"], result)
  9. return super().execute_function_call(call)

2. 工具调用链管理

复杂场景需要支持工具调用链:

  1. class ToolChain:
  2. def __init__(self, registry):
  3. self.registry = registry
  4. self.history = []
  5. async def execute_chain(self, initial_input):
  6. current_input = initial_input
  7. while True:
  8. # 模拟LLM生成调用决策
  9. function_call = self._generate_call(current_input)
  10. if not function_call:
  11. break
  12. result = await self.registry.execute_async(function_call)
  13. self.history.append((function_call, result))
  14. current_input = result["content"]
  15. return self._aggregate_results()

3. 错误处理机制

完善的错误处理应包含:

  • 工具不存在错误
  • 参数校验错误
  • 工具执行异常
  • 结果解析错误

实现示例:

  1. class ToolExecutionError(Exception):
  2. pass
  3. def safe_execute(registry, call):
  4. try:
  5. return execute_function_call(registry, call)
  6. except KeyError as e:
  7. raise ToolExecutionError(f"Tool not found: {str(e)}")
  8. except ValueError as e:
  9. raise ToolExecutionError(f"Invalid arguments: {str(e)}")
  10. except Exception as e:
  11. raise ToolExecutionError(f"Tool execution failed: {str(e)}")

四、性能优化实践

1. 工具缓存策略

对于高频调用工具,实现结果缓存:

  1. from functools import lru_cache
  2. def register_cached_tool(registry, name, func, schema, cache_size=100):
  3. @lru_cache(maxsize=cache_size)
  4. def cached_func(*args):
  5. return func(*args)
  6. registry.register(name, cached_func, schema)

2. 批量工具调用

支持一次性调用多个工具:

  1. async def batch_execute(registry, calls):
  2. tasks = []
  3. for call in calls:
  4. task = asyncio.create_task(registry.execute_async(call))
  5. tasks.append(task)
  6. return await asyncio.gather(*tasks)

3. 参数预处理优化

对常见参数进行预处理:

  1. def preprocess_args(args):
  2. # 日期格式标准化
  3. if "date" in args:
  4. try:
  5. args["date"] = datetime.fromisoformat(args["date"]).date()
  6. except ValueError:
  7. pass
  8. # 其他预处理逻辑...
  9. return args

五、完整实现示例

  1. import json
  2. from datetime import datetime
  3. class NativeDeepSeekAgent:
  4. def __init__(self):
  5. self.registry = ToolRegistry()
  6. self.message_history = []
  7. def register_tool(self, name, func, schema):
  8. self.registry.register(name, func, schema)
  9. async def call_function(self, function_call):
  10. try:
  11. result = await self.registry.execute_async(function_call)
  12. self.message_history.append(result)
  13. return result
  14. except ToolExecutionError as e:
  15. error_msg = {
  16. "role": "error",
  17. "content": str(e)
  18. }
  19. self.message_history.append(error_msg)
  20. return error_msg
  21. def get_context(self):
  22. return {
  23. "messages": self.message_history.copy(),
  24. "tools": list(self.registry.tools.keys())
  25. }
  26. # 示例工具实现
  27. async def get_weather(city, date=None):
  28. # 实际实现应调用天气API
  29. return {
  30. "city": city,
  31. "date": date or datetime.now().date(),
  32. "temperature": "25°C",
  33. "condition": "Sunny"
  34. }
  35. # 使用示例
  36. if __name__ == "__main__":
  37. agent = NativeDeepSeekAgent()
  38. # 注册工具
  39. weather_schema = {
  40. "type": "object",
  41. "properties": {
  42. "city": {"type": "string"},
  43. "date": {"type": "string", "format": "date"}
  44. },
  45. "required": ["city"]
  46. }
  47. agent.register_tool("get_weather", get_weather, weather_schema)
  48. # 模拟LLM输出
  49. function_call = {
  50. "role": "assistant",
  51. "function_call": {
  52. "name": "get_weather",
  53. "arguments": {
  54. "city": "北京",
  55. "date": "2023-07-20"
  56. }
  57. }
  58. }
  59. # 执行调用
  60. import asyncio
  61. result = asyncio.run(agent.call_function(function_call))
  62. print(json.dumps(result, indent=2))

六、最佳实践建议

  1. 工具设计原则

    • 每个工具应专注单一职责
    • 参数设计遵循最小化原则
    • 工具名称应具有明确语义
  2. 错误处理策略

    • 为关键工具实现重试机制
    • 对第三方API调用设置超时
    • 实现降级方案(如缓存结果)
  3. 性能优化方向

    • 对高频工具实现缓存
    • 考虑工具执行结果的持久化
    • 实现工具调用结果的增量更新
  4. 安全考虑

    • 实现严格的参数校验
    • 对敏感工具添加权限控制
    • 记录完整的工具调用日志

七、总结与展望

原生实现DeepSeek Agent工具调用系统,虽然需要更多的开发工作量,但能带来更高的灵活性和性能优势。通过本文介绍的协议设计、工具注册、执行流程等核心机制,开发者可以构建出完全符合业务需求的Agent系统。

未来发展方向包括:

  1. 更智能的工具调用决策机制
  2. 跨Agent的工具共享与复用
  3. 基于强化学习的工具链优化
  4. 多模态工具调用支持

这种原生实现方式特别适合对性能、定制化有极高要求的场景,以及需要深度集成现有系统的企业级应用。通过掌握这些核心原理,开发者可以摆脱框架的限制,构建出真正符合业务需求的智能Agent系统。