DB-GPT Agent 工具调用(Tool Use)完整实战:从 `@tool` 装饰器到 `ToolAssistantAgent`
2026/9/13 5:48:32 网站建设 项目流程

DB-GPT Agent 工具调用(Tool Use)完整实战:从@tool装饰器到ToolAssistantAgent

【免费下载链接】DB-GPTopen-source agentic AI data assistant for the next generation of AI + Data products.项目地址: https://gitcode.com/GitHub_Trending/db/DB-GPT

本指南以 DB-GPT Agent 的 Tool Use 能力为主线,完整讲解如何用@tool装饰器把普通 Python 函数封装成工具、如何用ToolPack统一管理多工具、以及如何通过ToolAssistantAgent让 Agent 自动选工具并执行。读完你将能够编写自己的自定义工具,并将其接入 DB-GPT Agent 对话链路,同时理解工具从"函数"到"资源"再到"Agent 执行"的底层实现原理。仓库中提供了可直接运行的参考示例 examples/agents/custom_tool_agent_example.py。

为什么 Agent 需要工具

LLM 虽然能完成大量任务,但在以下两类场景中表现并不理想:

  1. 需要全面专家知识的领域:模型训练数据之外的行业知识、实时信息,模型自身无法可靠给出。
  2. 幻觉问题:LLM 可能在计算、精确统计等任务上"一本正经地胡说八道",且很难靠自身纠正。

因此,DB-GPT 引入了**工具(Tool)**机制:让 LLM 把不擅长的工作"外包"给确定性的代码函数,由工具给出精确、可验证的结果,再交还模型组织成最终答案。

模型兼容性说明:在 DB-GPT Agent 中,只要模型自身能力不太弱,绝大多数 LLM 都支持工具调用。官方文档给出的参考模型包括glm-4-9b-chatYi-1.5-34B-ChatQwen2-72B-Instruct等。能力过弱的模型可能在"选择正确工具"和"生成合法 JSON 参数"上表现不佳。

@tool编写第一个工具

LLM 有时无法直接完成计算类任务,此时可以编写一个简单的计算器工具来辅助它。

from dbgpt.agent.resource import tool @tool def simple_calculator(first_number: int, second_number: int, operator: str) -> float: """Simple calculator tool. Just support +, -, *, /.""" if isinstance(first_number, str): first_number = int(first_number) if isinstance(second_number, str): second_number = int(second_number) if operator == "+": return first_number + second_number elif operator == "-": return first_number - second_number elif operator == "*": return first_number * second_number elif operator == "/": return first_number / second_number else: raise ValueError(f"Invalid operator: {operator}")

这段代码的关键点:

  • @tool装饰器:来自dbgpt.agent.resource,作用是把普通函数包装成一个FunctionTool对象(详见下文源码分析)。
  • 函数签名即工具签名first_number: intsecond_number: intoperator: str会被自动解析为工具的入参定义,并注入到 Agent 的 Prompt 中,让 LLM 知道该传什么参数。
  • docstring 即工具描述"""Simple calculator tool. Just support +, -, *, /."""会被自动提取为工具的描述信息,LLM 依靠它判断"何时该用这个工具"。

为参数补充说明:AnnotatedDoc

为了让 LLM 更准确地填参数,可以给参数附加说明。再写一个统计目录文件数量的工具:

import os from typing_extensions import Annotated, Doc @tool def count_directory_files(path: Annotated[str, Doc("The directory path")]) -> int: """Count the number of files in a directory.""" if not os.path.isdir(path): raise ValueError(f"Invalid directory path: {path}") return len(os.listdir(path))

这里用Annotated[str, Doc("The directory path")]path参数提供了语义描述。从源码看,DB-GPT 在解析函数签名时正是通过dbgpt.util.function_utils.parse_param_description读取这类注解描述(见 base.py 中的_parse_args)。工具内部还做了防御性校验:目录不存在时抛出ValueError,错误信息会回传给 Agent 作为执行失败的反馈。

ToolPack打包多个工具

实际场景中你通常有多个工具,可以统一打包成ToolPackToolPack是工具的集合,用于统一管理,Agent 会根据任务需求从包中挑选合适的工具执行。

from dbgpt.agent.resource import ToolPack tools = ToolPack([simple_calculator, count_directory_files])

源码层面的理解(见 pack.py):

  • ToolPack继承自ResourcePack,构造时通过_to_tool_list把输入统一归一化为BaseTool列表。它既能接收BaseTool对象,也能接收被@tool装饰后的函数(通过DB_GPT_TOOL_IDENTIFIER属性识别,即dbgpt_tool标记)。
  • 除了execute/async_execute统一执行入口外,ToolPack.add_command还兼容 Auto-GPT 旧插件体系的命令注册方式。
  • parse_execute_args支持在工具执行前,由特定解析函数把 LLM 输出的原始字符串解析成结构化的(args, kwargs)

在 Agent 中使用工具

下面把两个工具接入一个完整的 Agent 对话流程:

import asyncio import os from dbgpt.agent import AgentContext, AgentMemory, LLMConfig, UserProxyAgent from dbgpt.agent.expand.tool_assistant_agent import ToolAssistantAgent from dbgpt.model.proxy import OpenAILLMClient async def main(): llm_client = OpenAILLMClient( model_alias="gpt-3.5-turbo", # or other models, eg. "gpt-4o" api_base=os.getenv("OPENAI_API_BASE"), api_key=os.getenv("OPENAI_API_KEY"), ) context: AgentContext = AgentContext( conv_id="test123", language="en", temperature=0.5, max_new_tokens=2048 ) agent_memory = AgentMemory() agent_memory.gpts_memory.init(conv_id="test123") user_proxy = await UserProxyAgent().bind(agent_memory).bind(context).build() tool_man = ( await ToolAssistantAgent() .bind(context) .bind(LLMConfig(llm_client=llm_client)) .bind(agent_memory) .bind(tools) .build() ) await user_proxy.initiate_chat( recipient=tool_man, reviewer=user_proxy, message="Calculate the product of 10 and 99", ) await user_proxy.initiate_chat( recipient=tool_man, reviewer=user_proxy, message="Count the number of files in /tmp", ) # dbgpt-vis message infos print(await agent_memory.gpts_memory.app_link_chat_message("test123")) if __name__ == "__main__": asyncio.run(main())

各组件职责说明:

组件作用
LLMClient模型接入层,示例使用OpenAILLMClientmodel_alias可替换为gpt-4o等),也可替换为其他模型客户端
AgentContext会话上下文,包括conv_id(会话 ID)、languagetemperature(采样温度)、max_new_tokens(最大生成长度)
AgentMemoryAgent 记忆,gpts_memory.init(conv_id=...)初始化指定会话的存储
UserProxyAgent代表用户的代理角色,负责发起对话与接收最终答复
ToolAssistantAgent工具助手 Agent,负责读取工具信息、选择合适工具并组装参数执行
ToolPack(tools)通过.bind(tools)把工具资源注入 Agent

运行后将得到类似下面的输出:

-------------------------------------------------------------------------------- User (to LuBan)-[]: "Calculate the product of 10 and 99" -------------------------------------------------------------------------------- un_stream ai response: { "thought": "To calculate the product of 10 and 99, we need to use a tool that can perform multiplication operation.", "tool_name": "simple_calculator", "args": { "first_number": 10, "second_number": 99, "operator": "*" } } -------------------------------------------------------------------------------- LuBan (to User)-[gpt-3.5-turbo]: "{\n \"thought\": \"To calculate the product of 10 and 99, we need to use a tool that can perform multiplication operation.\",\n \"tool_name\": \"simple_calculator\",\n \"args\": {\n \"first_number\": 10,\n \"second_number\": 99,\n \"operator\": \"*\"\n }\n}" >>>>>>>>LuBan Review info: Pass(None) >>>>>>>>LuBan Action report: execution succeeded, 990 -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- User (to LuBan)-[]: "Count the number of files in /tmp" -------------------------------------------------------------------------------- un_stream ai response: { "thought": "To count the number of files in /tmp directory, we should use a tool that can perform this operation.", "tool_name": "count_directory_files", "args": { "path": "/tmp" } } -------------------------------------------------------------------------------- LuBan (to User)-[gpt-3.5-turbo]: "{\n \"thought\": \"To count the number of files in /tmp directory, we should use a tool that can perform this operation.\",\n \"tool_name\": \"count_directory_files\",\n \"args\": {\n \"path\": \"/tmp\"\n }\n}" >>>>>>>>LuBan Review info: Pass(None) >>>>>>>>LuBan Action report: execution succeeded, 19 --------------------------------------------------------------------------------

从执行轨迹可以看到完整链路:用户消息 → LLM 生成"选哪个工具 + 传什么参数"的结构化决策(tool_name+args)→ Agent 调用ToolPack执行 → 返回精确结果(99019。整个过程中,LLM 只负责"决策",而计算与文件统计由确定性代码完成,从而规避幻觉。

工具调用背后的执行机制

上面代码中的ToolAssistantAgent实际上完成了"选择并调用合适工具"的职责(见 tool_assistant_agent.py)。从其 Profile 配置可以看出它的系统提示设计:

  • 角色(role)ToolExpert
  • 目标(goal):阅读资源中给定的工具信息,理解每个工具的能力与用法,选择正确工具达成用户目标;
  • 约束(constraints):仔细阅读工具参数定义,从用户目标中提取执行工具所需的具体参数,并按要求以 JSON 格式输出所选工具名与参数信息。

它底层绑定的是ToolAction(见 tool_action.py)。ToolAction定义了输出模型ToolInput

{ "thought": "Summary of thoughts to the user", "tool_name": "The name of a tool that can be used to answer the current question or solve the current task.", "args": { "arg name1": "arg value1", "arg name2": "arg value2" } }

ToolAction.run把 LLM 输出解析为ToolInput后调用run_toolrun_tool内部通过ToolPack.from_resource(resource)还原工具包,再以resource_name(工具名)+args调用tool_pack.async_execute完成执行。值得一提的是:

  • 工具执行结果会以StatusRUNNING/COMPLETE/FAILED)标记状态;执行失败时异常信息会包装为ToolExecutionException类错误返回;
  • 对于超大工具结果,run_tool会借助当前存储(get_current_storage())做持久化,把大段内容替换为<persisted-output>预览与文件路径,避免撑爆上下文(见 tool_action.py 中maybe_persist逻辑);
  • 如果 LLM 未产出严格的 JSON Schema,ToolAction还内置了两级容错回退:纯数字字符串直接作为结果接受;从反引号中提取工具名与expression再执行。

深入源码:@tool装饰器到底做了什么

文档中提到:@tool装饰器会把函数包装成FunctionTool对象,而FunctionToolBaseTool的子类,BaseTool是所有工具的基类。这条链条可在 tool/base.py 中完整印证。

@tool支持多种用法(见tool()函数的重载分支):

# 用法 1:直接用函数名作为工具名 @tool def my_func(...): ... # 用法 2:显式指定工具名 @tool("google_search") def search(...): ... # 用法 3:指定工具名并覆盖描述 @tool("google_search", description="Search on Google") def search(...): ... # 用法 4:不传参,使用函数名 @tool() def my_func(...): ...

FunctionTool的初始化流程

  1. 若未显式传description,则从函数 docstring 提取(_parse_docstring),提取不到会直接抛ValueError——因此每个工具必须写清 docstring
  2. 通过_parse_args解析参数:优先使用显式传入的argsToolParameter字典或普通 dict),其次使用args_schema(Pydantic 模型),兜底用inspect.signature反射函数签名;
  3. 每个参数被规范化为ToolParameter模型,包含nametitletypedescriptionrequireddefault等字段(见ToolParameter定义);
  4. 通过asyncio.iscoroutinefunction(func)自动识别异步函数,并分别提供execute(同步)与async_execute(异步)两个执行入口。

BaseTool是所有工具的抽象基类,它继承自Resource(见 resource/base.py),其type()返回ResourceType.Tool。这印证了文档的核心论断:在 DB-GPT 中,工具是资源(Resource)的一种特殊形式,与数据库、知识库、API、文件、第三方插件等并列(详见 Resource 介绍)。BaseTool.get_prompt会根据prompt_type生成两种形式的工具描述:openai类型输出 JSON Schema 格式(type/properties/required),default 类型输出参数列表格式,两者都会注入到 Agent 的提示词中。

更多扩展:从 ToolPack 到 MCP 工具

ToolPack的继承体系进一步展示了工具生态的扩展性(见 pack.py):

  • AutoGPTPluginToolPack:兼容 Auto-GPT 旧插件体系,从插件路径扫描并注册命令式工具;
  • MCPToolPack:把 MCP(Model Context Protocol)SSE 服务器上的工具包装为本地工具包,支持按服务器配置headersssl_verify,传输方式支持ssestreamable_http

这意味着一套ToolPack接口可以统一管理"手写函数工具、AutoGPT 插件工具、MCP 远端工具"等多种来源,Agent 的选工具逻辑无需区分来源差异。工具包(ResourcePack)体系的整体设计可进一步参考 pack.md。

总结

本文完整走通了 DB-GPT Agent 工具调用的全链路:@tool定义工具 → 用ToolPack打包 → 用ToolAssistantAgent接入对话 → 观察 LLM 决策与工具执行的完整轨迹,并从源码层面解释了@toolFunctionToolBaseToolResource的封装层级,以及ToolAction对工具执行的统一调度与容错处理。核心代码与真实示例分别在 tool/base.py、tool/pack.py、tool_action.py 与 custom_tool_agent_example.py 中,可对照阅读、直接运行验证。

【免费下载链接】DB-GPTopen-source agentic AI data assistant for the next generation of AI + Data products.项目地址: https://gitcode.com/GitHub_Trending/db/DB-GPT

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询