当前位置: 首页 > news >正文

@mcp.tool如何从函数定义映射到llm系统输入

llm是如何获熟mcp tool定义的function函数,比如以下mcp示例的get_whether,又如何决定调用那个mcp function。

这里通过跟踪langchain_ollama、qwen_agent源代码进行分析。

from mcp.server.fastmcp import FastMCPmcp = FastMCP(name="weather",)@mcp.tool()
def get_weather(city: str) -> str:"""获取指定城市的天气信息"""# 简单模拟数据,实际应用中应该调用对应的APIweather_data = {"北京": "晴天,温度 22°C","上海": "多云,温度 25°C", "广州": "小雨,温度 28°C","深圳": "阴天,温度 26°C"}return weather_data.get(city, f"{city} 的天气数据暂不可用")@mcp.resource("resource://cities")
def get_cities():"""返回支持查询天气的城市列表"""cities = ["北京", "上海", "广州", "深圳"]return f"Cities: {', '.join(cities)}"@mcp.resource("resource://{city}/weather")
def get_city_weather(city: str) -> str:return f"Weather for {city}"

1 langchain agent

如下langchain agent代码所示,mcp定义的工具tools,是和模型model一起送入langchain agent,辅助agent响应用户问题。

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from langchain_mcp_adapters.tools import load_mcp_tools
from langgraph.prebuilt import create_react_agent
import asyncioserver_params = StdioServerParameters(command="python", args=["server.py"])
async def run_agent():async with stdio_client(server_params) as (read, write):async with ClientSession(read, write) as session:await session.initialize()tools = await load_mcp_tools(session)agent = create_react_agent(model, tools)agent_response = await agent.ainvoke({"messages": [{"role": "user", "content": "上海天气如何?"},]})

2 langchain_ollama

这里model为ollama,最终调用langchain_ollama,实际为langchain_ollama/chat_models.py定义的ChatOllama(BaseChatModel)。

经过跟踪,tools保存在kwars参数中,最后在_acreate_chat_stream中,通过self._chat_params(messages,  stop, **kwargs)将tools参数融合在chat_params,再通过self.async_client直接调用ollama llm。

self._chat_params融合tools过程代码如下。

    def _chat_params(self,messages: list[BaseMessage],stop: Optional[list[str]] = None,**kwargs: Any,) -> dict[str, Any]:ollama_messages = self._convert_messages_to_ollama_messages(messages)if self.stop is not None and stop is not None:msg = "`stop` found in both the input and default params."raise ValueError(msg).....params = {"messages": ollama_messages,"stream": kwargs.pop("stream", True),"model": kwargs.pop("model", self.model),"think": kwargs.pop("reasoning", self.reasoning),"format": kwargs.pop("format", self.format),"options": Options(**options_dict),"keep_alive": kwargs.pop("keep_alive", self.keep_alive),**kwargs,}if tools := kwargs.get("tools"):params["tools"] = toolsreturn params

https://github.com/langchain-ai/langchain/blob/master/libs/partners/ollama/langchain_ollama/chat_models.py

langchain_ollama将messges和tools提交给ollama llm,并没有详细说明llm处理tools的过程。

3 llm如何处理tools

ollama使用go开发,代码组织和langchain_llama有较大不同,llm处理tools代码隐藏在细节中,不太好直接分析。

这里借用Qwen-Agent示例llm处理tools的过程,两者原理一致。

1)tools整合进system message

以下为Qwen-Agent处理tool工具函数的具体过程,代码链接如下。

https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/llm/fncall_prompts/qwen_fncall_prompt.py#L74

可见tool最终是被整合到system message中,这解释了为什么llm能看到这些tool,并能依据上下文决策合适tool的原因。

2)整合结果示例

参考网络资料,整合后输入到llm的messages示例如下

[Message({'role': 'system', 'content': '你是一个有用的帮手,可以使用合适的工具解决我的我问题
# 工具
## 你拥有如下工具:
### get_current_weather
get_current_weather: 基于给定的城市获取天气 输入参数:{"type": "object", "properties": {"location": {"type": "string", "description": "城市名称"}}, "required": ["location"]}
### get_current_time
get_current_time: 获取当前时间 输入参数:{}
## 你可以在回复中插入零次、一次或多次以下命令以调用工具:
✿FUNCTION✿: 工具名称,必须是[get_current_weather,get_current_time]之一。
✿ARGS✿: 工具输入
✿RESULT✿: 工具结果,需将图片用![](url)渲染出来。
✿RETURN✿: 根据工具结果进行回复'}),

Message({'role': 'user', 'content': '上海天气如何'})]
 

3)整合过程代码示例

整合过程代码如下

#*******************************tool function整合模版*******************************
FN_NAME = '✿FUNCTION✿'
FN_ARGS = '✿ARGS✿'
FN_RESULT = '✿RESULT✿'
FN_EXIT = '✿RETURN✿'
FN_STOP_WORDS = [FN_RESULT, FN_EXIT]FN_CALL_TEMPLATE_INFO_ZH = """# 工具## 你拥有如下工具:{tool_descs}"""FN_CALL_TEMPLATE_INFO_EN = """# Tools## You have access to the following tools:{tool_descs}"""FN_CALL_TEMPLATE_FMT_ZH = """## 你可以在回复中插入零次、一次或多次以下命令以调用工具:%s: 工具名称,必须是[{tool_names}]之一。
%s: 工具输入
%s: 工具结果
%s: 根据工具结果进行回复,需将图片用![](url)渲染出来""" % (FN_NAME,FN_ARGS,FN_RESULT,FN_EXIT,
)FN_CALL_TEMPLATE_FMT_EN = """## When you need to call a tool, please insert the following command in your reply, which can be called zero or multiple times according to your needs:%s: The tool to use, should be one of [{tool_names}]
%s: The input of the tool
%s: Tool results
%s: Reply based on tool results. Images need to be rendered as ![](url)""" % (FN_NAME,FN_ARGS,FN_RESULT,FN_EXIT,
)FN_CALL_TEMPLATE_FMT_PARA_ZH = """## 你可以在回复中插入以下命令以并行调用N个工具:%s: 工具1的名称,必须是[{tool_names}]之一
%s: 工具1的输入
%s: 工具2的名称
%s: 工具2的输入
...
%s: 工具N的名称
%s: 工具N的输入
%s: 工具1的结果
%s: 工具2的结果
...
%s: 工具N的结果
%s: 根据工具结果进行回复,需将图片用![](url)渲染出来""" % (FN_NAME,FN_ARGS,FN_NAME,FN_ARGS,FN_NAME,FN_ARGS,FN_RESULT,FN_RESULT,FN_RESULT,FN_EXIT,
)FN_CALL_TEMPLATE_FMT_PARA_EN = """## Insert the following command in your reply when you need to call N tools in parallel:%s: The name of tool 1, should be one of [{tool_names}]
%s: The input of tool 1
%s: The name of tool 2
%s: The input of tool 2
...
%s: The name of tool N
%s: The input of tool N
%s: The result of tool 1
%s: The result of tool 2
...
%s: The result of tool N
%s: Reply based on tool results. Images need to be rendered as ![](url)""" % (FN_NAME,FN_ARGS,FN_NAME,FN_ARGS,FN_NAME,FN_ARGS,FN_RESULT,FN_RESULT,FN_RESULT,FN_EXIT,
)FN_CALL_TEMPLATE = {'zh': FN_CALL_TEMPLATE_INFO_ZH + '\n\n' + FN_CALL_TEMPLATE_FMT_ZH,'en': FN_CALL_TEMPLATE_INFO_EN + '\n\n' + FN_CALL_TEMPLATE_FMT_EN,'zh_parallel': FN_CALL_TEMPLATE_INFO_ZH + '\n\n' + FN_CALL_TEMPLATE_FMT_PARA_ZH,'en_parallel': FN_CALL_TEMPLATE_INFO_EN + '\n\n' + FN_CALL_TEMPLATE_FMT_PARA_EN,
}def get_function_description(function: Dict, lang: Literal['en', 'zh']) -> str:"""Text description of function"""tool_desc_template = {'zh': '### {name_for_human}\n\n{name_for_model}: {description_for_model} 输入参数:{parameters} {args_format}','en': '### {name_for_human}\n\n{name_for_model}: {description_for_model} Parameters: {parameters} {args_format}'}tool_desc = tool_desc_template[lang]name = function.get('name', None)name_for_human = function.get('name_for_human', name)name_for_model = function.get('name_for_model', name)assert name_for_human and name_for_modelif name_for_model == 'code_interpreter':args_format = {'zh': '此工具的输入应为Markdown代码块。','en': 'Enclose the code within triple backticks (`) at the beginning and end of the code.',}else:args_format = {'zh': '此工具的输入应为JSON对象。','en': 'Format the arguments as a JSON object.',}args_format = function.get('args_format', args_format[lang])return tool_desc.format(name_for_human=name_for_human,name_for_model=name_for_model,description_for_model=function['description'],parameters=json.dumps(function['parameters'], ensure_ascii=False),args_format=args_format).rstrip()#*************************将tool信息整合进system message**************************
## 基于以上模版和组合函数,将tool信息整合进system message# Add a system prompt for function calling:tool_desc_template = FN_CALL_TEMPLATE[lang + ('_parallel' if parallel_function_calls else '')]tool_descs = '\n\n'.join(get_function_description(function, lang=lang) for function in functions)tool_names = ','.join(function.get('name_for_model', function.get('name', '')) for function in functions)tool_system = tool_desc_template.format(tool_descs=tool_descs, tool_names=tool_names)if messages and messages[0].role == SYSTEM:messages[0].content.append(ContentItem(text='\n\n' + tool_system))else:messages = [Message(role=SYSTEM, content=[ContentItem(text=tool_system)])] + messages

4)llm回复解析

将本节第二部分示例的内容输入llm,llm会回复如下所示的内容

✿FUNCTION✿: get_current_weather
✿ARGS✿: {"location": "上海"}
✿RESULT✿: 上海当前天气:晴,温度28摄氏度,湿度65%,东南风3级
✿RETURN✿: 上海现在是晴天,气温28度,湿度65%,东南风3级,请注意防晒。

显然,返回内容格式清晰,可以很快就转化为如下所示的tool_calls。

tool_calls=[{'name': 'get_current_weather', 'args': {'name': '上海'}, 'id': '11621a7f-6a7a-442e-81ed-e9a1454ebc1b', 'type': 'tool_call'}

reference

---

langchain_ollama/chat_models.py

https://github.com/langchain-ai/langchain/blob/master/libs/partners/ollama/langchain_ollama/chat_models.py

qwen_fucall_prompt.py

https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/llm/fncall_prompts/qwen_fncall_prompt.py

详解大模型是如何理解并使用 tools ?

https://blog.csdn.net/2301_81940605/article/details/140063364

Qwen3

https://github.com/QwenLM/Qwen3

Qwen-Agent

https://github.com/QwenLM/Qwen-Agent

qwen2.5-max

https://qwenlm.github.io/blog/qwen2.5-max/

http://www.dtcms.com/a/333860.html

相关文章:

  • Kotlin作用域函数全解:run/with/apply/let/also与this/it的魔法对决
  • LORA模块的通讯速率(915Mhz)以及通道数量规划
  • 图片滤镜处理(filters)
  • 【机器学习深度学习】生成式评测
  • 数据处理分析环境搭建+Numpy使用教程
  • Design Compiler:使用IC Compiler II Link
  • PCA降维 提升模型训练效率
  • CUDA TensorRT Python智能提示补全解决方案
  • MySQL约束知识点
  • iceberg 底层存储HDFS与juiceFS的区别
  • epoll发数据学习
  • 自己开发的VIP monitor通过TLM port口连接到RefenceModel 但是get不出transaction的问题
  • 《中国棒球知识科普》国家级运动健将标准·棒球1号位
  • 力扣(接雨水)——标准双指针
  • 最长链(二叉树直径DFS)
  • 【学习笔记】NTP服务客户端配置
  • 医疗领域名词标准化工具
  • 二分算法(模板)
  • Vue插槽---slot详解
  • RAGFlow入门
  • 大麦APP抢票揭秘
  • 亚马逊新品推广的智能突围:从传统SP广告到DeepBI策略革新
  • 结合项目对AOP的实践和理论讲解-AOP八股
  • 第十四节:物理引擎集成:Cannon.js入门
  • Windows Server Core智能调优
  • 智能体开发实战:用Deepseek做一个生成思维导图的智能体
  • SSH协议的GIT转换
  • 访问者模式C++
  • Day55 Java面向对象09 super关键字详解
  • MySQL 高效查询五虎将——分页、排序、分组、去重、聚合函数实战手册