摘要
工具调用(Tool Calling)是现代大语言模型应用的重要特性之一,它允许模型在需要时调用外部工具或函数来获取信息或执行操作。LangBot通过其工具管理器和相关组件,提供了完整的工具调用机制,使得聊天机器人能够扩展其能力并执行复杂的任务。本文将深入解析LangBot中的工具调用机制,包括工具的定义、注册、调用流程以及与大语言模型的集成,帮助开发者更好地理解和使用这一重要功能。
正文
1. 工具调用机制概述
工具调用机制允许大语言模型在生成回复时,识别需要调用外部工具的情况,并生成相应的工具调用请求。LangBot的工具调用机制具有以下特点:
- 统一接口:提供统一的工具定义和调用接口
- 插件集成:与插件系统深度集成,支持插件提供的工具
- 动态注册:支持运行时动态注册和注销工具
- 参数验证:对工具调用参数进行验证
- 结果处理:处理工具调用结果并将其整合到对话中
2. 系统架构
LangBot工具调用机制的架构如下图所示:
3. 核心组件
3.1 工具管理器(ToolManager)
工具管理器是工具调用系统的核心组件,负责工具的注册、查找和管理:
class ToolManager:
"""工具管理器"""
def __init__(self, ap: app.Application):
self.ap = ap
self.tools: dict[str, LLMTool] = {}
self.tool_groups: dict[str, list[str]] = {}
async def initialize(self):
"""初始化工具管理器"""
# 注册内置工具
await self._register_builtin_tools()
# 注册插件工具
await self._register_plugin_tools()
async def _register_builtin_tools(self):
"""注册内置工具"""
# 注册系统工具
system_tools = [
GetCurrentTimeTool(),
GetWeatherTool(),
SearchWebTool(),
# 其他内置工具...
]
for tool in system_tools:
await self.register_tool(tool)
async def _register_plugin_tools(self):
"""注册插件工具"""
# 从插件系统获取工具
plugin_tools = await self.ap.plugin_connector.list_tools()
for tool_data in plugin_tools:
tool = LLMTool.model_validate(tool_data)
await self.register_tool(tool)
async def register_tool(self, tool: LLMTool):
"""
注册工具
Args:
tool: 工具对象
"""
self.tools[tool.name] = tool
# 按插件分组
plugin_name = tool.metadata.get("plugin", "builtin")
if plugin_name not in self.tool_groups:
self.tool_groups[plugin_name] = []
self.tool_groups[plugin_name].append(tool.name)
async def get_tool(self, tool_name: str) -> LLMTool | None:
"""
获取工具
Args:
tool_name: 工具名称
Returns:
工具对象或None
"""
return self.tools.get(tool_name)
async def list_tools(self, plugin_name: str = None) -> list[LLMTool]:
"""
列出工具
Args:
plugin_name: 插件名称(可选)
Returns:
工具列表
"""
if plugin_name:
tool_names = self.tool_groups.get(plugin_name, [])
return [self.tools[name] for name in tool_names if name in self.tools]
else:
return list(self.tools.values())
3.2 工具定义(LLMTool)
工具定义描述了工具的名称、描述、参数等信息:
class LLMTool(pydantic.BaseModel):
"""LLM工具定义"""
name: str
"""工具名称"""
description: str
"""工具描述"""
parameters: dict
"""工具参数定义(JSON Schema格式)"""
metadata: dict = {}
"""工具元数据"""
async def execute(self, parameters: dict) -> dict:
"""
执行工具
Args:
parameters: 工具参数
Returns:
执行结果
"""
# 参数验证
if not self._validate_parameters(parameters):
raise ValueError("参数验证失败")
# 执行具体逻辑
result = await self._execute_impl(parameters)
return result
def _validate_parameters(self, parameters: dict) -> bool:
"""
验证参数
Args:
parameters: 参数字典
Returns:
验证是否通过
"""
try:
import jsonschema
jsonschema.validate(parameters, self.parameters)
return True
except jsonschema.ValidationError:
return False
async def _execute_impl(self, parameters: dict) -> dict:
"""
工具执行实现
Args:
parameters: 参数字典
Returns:
执行结果
"""
# 子类需要实现具体逻辑
raise NotImplementedError
4. 工具类型和示例
4.1 内置工具
LangBot提供了一些常用的内置工具:
class GetCurrentTimeTool(LLMTool):
"""获取当前时间工具"""
def __init__(self):
super().__init__(
name="get_current_time",
description="获取当前时间和日期",
parameters={
"type": "object",
"properties": {},
"required": []
}
)
async def _execute_impl(self, parameters: dict) -> dict:
"""执行获取当前时间"""
import datetime
current_time = datetime.datetime.now()
return {
"current_time": current_time.isoformat(),
"formatted_time": current_time.strftime("%Y年%m月%d日 %H:%M:%S")
}
class GetWeatherTool(LLMTool):
"""获取天气工具"""
def __init__(self):
super().__init__(
name="get_weather",
description="获取指定城市的天气信息",
parameters={
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称"
}
},
"required": ["city"]
}
)
async def _execute_impl(self, parameters: dict) -> dict:
"""执行获取天气"""
city = parameters.get("city")
# 调用天气API(示例)
weather_info = await self._fetch_weather_data(city)
return {
"city": city,
"weather": weather_info
}
async def _fetch_weather_data(self, city: str) -> dict:
"""获取天气数据"""
# 实际实现中会调用真实的天气API
# 这里返回模拟数据
return {
"temperature": 25,
"condition": "晴天",
"humidity": 60,
"wind_speed": 3.5
}
4.2 插件工具
插件可以提供自定义工具:
# 插件工具示例
class PluginCalculatorTool(LLMTool):
"""插件计算器工具"""
def __init__(self):
super().__init__(
name="calculate",
description="执行数学计算",
parameters={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 '2 + 3 * 4'"
}
},
"required": ["expression"]
},
metadata={
"plugin": "calculator-plugin",
"version": "1.0.0"
}
)
async def _execute_impl(self, parameters: dict) -> dict:
"""执行计算"""
expression = parameters.get("expression")
try:
# 安全地计算表达式
result = self._safe_eval(expression)
return {
"expression": expression,
"result": result
}
except Exception as e:
return {
"error": f"计算出错: {str(e)}"
}
def _safe_eval(self, expression: str) -> float:
"""
安全计算表达式
Args:
expression: 数学表达式
Returns:
计算结果
"""
# 只允许数字、基本运算符和空格
import re
if not re.match(r'^[0-9+\-*/(). ]+$', expression):
raise ValueError("表达式包含非法字符")
# 使用eval计算(在实际应用中应使用更安全的方法)
return eval(expression)
5. 工具调用流程
LangBot中的工具调用流程如下:
6. 在大语言模型中使用工具
6.1 工具调用参数构造
class ToolEnabledLLM:
"""支持工具调用的大语言模型"""
async def invoke_with_tools(
self,
messages: list[dict],
tools: list[LLMTool] = None,
tool_choice: str = "auto"
) -> dict:
"""
调用支持工具的大语言模型
Args:
messages: 消息历史
tools: 可用工具列表
tool_choice: 工具选择策略
Returns:
模型响应
"""
# 转换工具格式
openai_tools = []
if tools:
for tool in tools:
openai_tools.append({
"type": "function",
"function": {
"name": tool.name,
"description": tool.description,
"parameters": tool.parameters
}
})
# 调用模型API
response = await self._call_model_api(
messages=messages,
tools=openai_tools if openai_tools else None,
tool_choice=tool_choice
)
return response
async def _call_model_api(self, messages: list[dict], tools: list[dict] = None, tool_choice: str = "auto"):
"""
调用模型API
Args:
messages: 消息历史
tools: 工具列表
tool_choice: 工具选择策略
Returns:
API响应
"""
import openai
client = openai.AsyncOpenAI(
api_key=self.config["api_key"],
base_url=self.config.get("base_url")
)
kwargs = {
"model": self.config["model_name"],
"messages": messages
}
if tools:
kwargs["tools"] = tools
kwargs["tool_choice"] = tool_choice
response = await client.chat.completions.create(**kwargs)
return response
6.2 工具调用结果处理
class ToolCallHandler:
"""工具调用处理器"""
def __init__(self, ap: app.Application):
self.ap = ap
async def handle_tool_calls(self, response: dict) -> list[dict]:
"""
处理工具调用
Args:
response: 模型响应
Returns:
工具调用结果列表
"""
tool_calls = response.choices[0].message.tool_calls
if not tool_calls:
return []
results = []
for tool_call in tool_calls:
# 获取工具
tool = await self.ap.tool_mgr.get_tool(tool_call.function.name)
if not tool:
results.append({
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"error": "工具未找到"
})
continue
# 解析参数
try:
arguments = json.loads(tool_call.function.arguments)
except json.JSONDecodeError:
results.append({
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"error": "参数解析失败"
})
continue
# 执行工具
try:
result = await tool.execute(arguments)
results.append({
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"content": json.dumps(result, ensure_ascii=False)
})
except Exception as e:
results.append({
"tool_call_id": tool_call.id,
"name": tool_call.function.name,
"error": f"工具执行失败: {str(e)}"
})
return results
7. 在流水线中集成工具调用
7.1 工具调用阶段
@stage.stage_class("tool-call")
class ToolCallStage(stage.PipelineStage):
"""工具调用阶段"""
async def process(
self,
query: pipeline_query.Query,
stage_inst_name: str,
) -> entities.StageProcessResult:
"""处理消息"""
# 获取用户消息
user_message = query.message_chain.get_text()
# 构造消息历史
messages = [
{"role": "system", "content": "你是一个有用的助手,可以根据需要调用工具来帮助用户。"},
{"role": "user", "content": user_message}
]
# 获取可用工具
available_tools = await self.ap.tool_mgr.list_tools()
if not available_tools:
# 没有可用工具,直接继续
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query
)
# 调用支持工具的模型
default_model = await self.ap.model_mgr.get_default_model()
response = await default_model.requester.invoke_llm(
query=query,
model=default_model,
messages=messages,
funcs=available_tools
)
# 检查是否有工具调用
if hasattr(response, 'tool_calls') and response.tool_calls:
# 处理工具调用
tool_call_handler = ToolCallHandler(self.ap)
tool_results = await tool_call_handler.handle_tool_calls(response)
# 将工具调用结果添加到消息历史
messages.append({
"role": "assistant",
"tool_calls": response.tool_calls
})
for result in tool_results:
messages.append({
"role": "tool",
"tool_call_id": result["tool_call_id"],
"name": result["name"],
"content": result.get("content", result.get("error", ""))
})
# 基于工具调用结果生成最终回复
final_response = await default_model.requester.invoke_llm(
query=query,
model=default_model,
messages=messages
)
# 构造回复
reply = platform_message.MessageChain([
platform_message.Plain(text=final_response.content)
])
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query,
user_notice=reply,
console_notice=f"执行了{len(tool_results)}个工具调用"
)
else:
# 没有工具调用,直接回复
reply = platform_message.MessageChain([
platform_message.Plain(text=response.content)
])
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query,
user_notice=reply
)
7.2 工具注册阶段
@stage.stage_class("tool-register")
class ToolRegisterStage(stage.PipelineStage):
"""工具注册阶段"""
async def initialize(self, pipeline_config: dict):
"""初始化阶段"""
# 可以根据流水线配置注册特定工具
self.pipeline_tools = pipeline_config.get("tools", [])
async def process(
self,
query: pipeline_query.Query,
stage_inst_name: str,
) -> entities.StageProcessResult:
"""处理消息"""
# 根据查询上下文动态注册工具
context_tools = await self._get_context_tools(query)
# 注册工具到会话或查询中
query.variables["available_tools"] = context_tools
return entities.StageProcessResult(
result_type=entities.ResultType.CONTINUE,
new_query=query,
console_notice=f"注册了{len(context_tools)}个上下文工具"
)
async def _get_context_tools(self, query: pipeline_query.Query) -> list[LLMTool]:
"""
获取上下文相关工具
Args:
query: 查询对象
Returns:
工具列表
"""
tools = []
# 根据会话上下文添加工具
session = query.session
user_preferences = session.get_variable("preferences", {})
# 如果用户偏好包含"计算",添加计算器工具
if "计算" in str(user_preferences):
calculator_tool = await self.ap.tool_mgr.get_tool("calculate")
if calculator_tool:
tools.append(calculator_tool)
# 如果是群组聊天,添加群组管理工具
if query.launcher_type == LauncherTypes.GROUP:
group_tools = await self._get_group_tools(query)
tools.extend(group_tools)
return tools
async def _get_group_tools(self, query: pipeline_query.Query) -> list[LLMTool]:
"""获取群组工具"""
# 实现群组相关工具的获取逻辑
return []
8. 工具调用最佳实践
8.1 工具设计原则
class BestPracticeTool(LLMTool):
"""最佳实践工具示例"""
def __init__(self):
super().__init__(
name="best_practice_example",
description="展示工具设计最佳实践的示例",
parameters={
"type": "object",
"properties": {
"input_data": {
"type": "string",
"description": "输入数据"
},
"options": {
"type": "object",
"description": "可选参数",
"properties": {
"timeout": {
"type": "integer",
"description": "超时时间(秒)",
"minimum": 1,
"maximum": 300
}
}
}
},
"required": ["input_data"]
}
)
async def _execute_impl(self, parameters: dict) -> dict:
"""执行实现"""
input_data = parameters.get("input_data")
options = parameters.get("options", {})
timeout = options.get("timeout", 30)
try:
# 设置超时
result = await asyncio.wait_for(
self._do_work(input_data),
timeout=timeout
)
return {
"success": True,
"result": result,
"execution_time": datetime.now().isoformat()
}
except asyncio.TimeoutError:
return {
"success": False,
"error": "操作超时",
"timeout": timeout
}
except Exception as e:
return {
"success": False,
"error": f"执行失败: {str(e)}"
}
async def _do_work(self, input_data: str) -> str:
"""执行具体工作"""
# 模拟工作
await asyncio.sleep(1)
return f"处理完成: {input_data}"
8.2 错误处理和日志记录
class RobustTool(LLMTool):
"""健壮的工具示例"""
async def _execute_impl(self, parameters: dict) -> dict:
"""执行实现"""
try:
# 记录工具调用开始
self._log_tool_call("start", parameters)
# 执行主要逻辑
result = await self._main_logic(parameters)
# 记录工具调用成功
self._log_tool_call("success", parameters, result)
return result
except ValueError as e:
# 参数错误
self._log_tool_call("param_error", parameters, error=str(e))
raise
except asyncio.TimeoutError:
# 超时错误
self._log_tool_call("timeout", parameters)
raise
except Exception as e:
# 其他错误
self._log_tool_call("error", parameters, error=str(e))
raise
def _log_tool_call(self, status: str, parameters: dict, result: dict = None, error: str = None):
"""
记录工具调用日志
Args:
status: 调用状态
parameters: 参数
result: 结果(可选)
error: 错误信息(可选)
"""
log_data = {
"tool_name": self.name,
"status": status,
"parameters": parameters
}
if result:
log_data["result"] = result
if error:
log_data["error"] = error
# 记录日志
logger.info(f"工具调用: {json.dumps(log_data, ensure_ascii=False)}")
总结
LangBot的工具调用机制为聊天机器人提供了强大的扩展能力,使其能够调用外部服务和执行复杂任务。通过统一的工具接口、与插件系统的深度集成以及完善的工具调用流程,开发者可以轻松地为机器人添加各种功能。
关键要点包括:
- 统一接口:提供标准化的工具定义和调用接口
- 插件集成:与插件系统深度集成,支持插件提供的工具
- 动态注册:支持运行时动态注册和管理工具
- 参数验证:对工具调用参数进行严格的验证
- 错误处理:完善的错误处理和日志记录机制
- 流水线集成:可以方便地在消息处理流水线中集成工具调用
在实际应用中,建议遵循以下最佳实践:
- 合理设计工具接口:工具应具有明确的职责和清晰的参数定义
- 完善错误处理:对各种可能的错误情况进行处理
- 设置超时机制:避免工具调用阻塞整个对话流程
- 记录详细日志:便于调试和监控工具调用情况
- 考虑安全性:对工具调用进行适当的权限控制
通过合理使用LangBot的工具调用机制,开发者可以构建出功能强大、智能的聊天机器人应用。
44

被折叠的 条评论
为什么被折叠?



