大模型的基础知识--使用langgrap工作流自定义function calling
1.使用agently 实现不依赖模型接口进行Function Calling
背景:因function calling是依赖大模型接口提供支持tool的能力,并不是所有的模型都支持,如千帆模型:https://cloud.baidu.com/doc/qianfan-api/s/3m7of64lb#%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0,可查看具体支持的模型

使用agently实现不依赖模型接口的Function Calling能力
### 1. 脱离对模型接口的依赖进行Function Calling
from agently import Agently
Agently.set_settings(
"OpenAICompatible" ,
{
"model" : "qwen3:4b" ,
"base_url" : "http://127.0.0.1:11434/v1/" ,
"api_key" : "nothing" ,
},
)
# 创建一个模型请求对象
llm = Agently.create_agent()
# 使用装饰器快速注册工具
@llm .tool_func
def get_weather( * , latitude: float , longitude: float ):
"""Get current temperature for provided coordinates in celsius."""
# 上面的Docstring是Python的一种标准注释,可以被自动解析为工具的注释
return {
"temperature" : 23 ,
"weather" : "Sunny" ,
"wind_direction" : "South" ,
"windy" : 2 ,
}
@llm .tool_func
def add( * , a: int , b: int ):
"""返回两个整数加总之和"""
return a + b
# 使用.use_tool("<工具名>")明确启用工具
result = llm.use_tool([ "get_weather" ,"add" ]). input ( "北京的天气怎么样?" ).start()
print (result)
在Agently框架内部,自行实现了一套工具调用逻辑,具体流程如下图所示:

因避开了对云端模型服务的依赖,可以在任意模型上使用,但是也有局限:
- 我只想让模型做工具判断和请求指令的生成,但不想让模型真的去执行指令,怎么办?
- 我先让模型在信息不足的情况下,反过来先向用户提问,怎么办
这个时候,需要我们把function calling的逻辑展开,做一个自己的function calling
2.关键工具:工作流
2.1 为什么工作流是最适合大模型应用的编排方案?
- 工作流与测试工作常接触的pipeline相似度很高,都有执行编排,触发,条件,变量等设计
- 任务复杂,依赖关系:大模型应用往往不只是给模型一个prompt得到答案,而是一个流程:数据检索 → 预处理 →模型调用 →后处理 →可能再调用另一个模型 →最终输出。工作流通过管理任务、服务和数据流以实现解决方案目标。当一个请求可能触发文档检索、多个 LLM 推理、外部 API 调用、结果合成和评估,就需要一个能够管理任务执行、状态管理、依赖、资源优化的系统。这恰恰是一个典型工作流编排的场景。
- 重用性,标准化,可演化:工作流模型可以让你把常见流程结构抽象出来(如“用户提问→检索上下文→模型生成→后处理”),并在不同应用中复用/调整。对 LLM 项目而言,这意味着在不同业务场景之间可以复用流程模板。相比“每次写 API 调用脚本”,编排工具能提升复用性、可维护性。
2.2 工作流和单次请求模型的对比
单次请求的局限性
- 上下文窗口长度限制、输出长度限制(早期的LangChain长文本Summarize)
- 直接进行CoT控制(尤其是用自然语言表达CoT)会输出思考过程,但我们不希望用户看到这个过程
- 随着工作进展出现的新信息,对任务时序、编排有依赖的信息,不一定能在单次请求中一次性完成输入
工作流的优势
- 将工作任务拆分成多个工作节点
- 能够将模型单次请求调用视作一个工作节点
- 能够灵活将其他代码逻辑也写入工作节点
- 能够对工作节点进行任务编排
- 能够在工作节点之间进行数据传递
2.3 工作流要素
- 🟩 工作块/工作节点 (node)
- 🔀 连接关系 (edge)
- 普通连接 (edge)
- 条件连接 (conditional_edges)
- 📡 数据通讯 (state)
- 工作流内数据传递

- 进阶要求
-
能够成环,能进行重试操作
以支持在特定工作环(多步工作)中反复尝试,尝试结果不符合预期可以回到第一步重试
-
能够按条件分发
以支持意图识别、路径规划、工具选择、多agent路由等场景中,根据推理结果进入不同的下游工作流,同时也能支持符合特定条件后跳出环
-
能够多分支并行执行并在终点被等待
以支持面对复杂任务时,能够发起不同分支从不同处理角度/用不同处理方式对任务进行处理
-
2.4 langgraph工作流编排基本操作
前提准备:安装相关包
pip3 install langgraph
- 实现场景:让用户输入一条信息,获取用户输入的信息打印到控制台,然后增加计数器; 如果计数器值小于指定值:2,就继续,否则退出
- 使用langgrap创建工作流的步骤:
1.定义工作流内部的数据结构,用于工作流间节点之间的数据通讯
2.根据场景,定义节点运行函数
3.创建工作流
4.在工作流中添加节点、节点间的连接关系
5.编译工作流
6.运行工作流
# 引入LangGraph相应组件
from langgraph.graph import StateGraph, START, END
# StateGraph 有状态的图式工作流
# START LangGraph内置的默认的启动块
# END LangGraph内置的结束块
from typing import TypedDict
# 1.定义工作流内部流转的数据结构/State状态
class State(TypedDict):
user_input: str
count: int
# 2.定义节点运行函数
def get_user_input(state: State):
user_input = input ( "请随便输点什么:" )
return {
"user_input" : user_input
}
def echo(state: State):
print (f "YOU SAID: { state.get('user_input') }" )
return
def add_one(state: State):
count = state.get( "count" )
return {
"count" : count + 1 ,
}
# 3.创建工作流
graph = StateGraph(State)
# 4.注册运行节点
graph.add_node( "get_user_input" , get_user_input)
graph.add_node( "echo" , echo)
graph.add_node( "add_one" , add_one)
# 5.定义连接关系
graph.add_edge(START, "get_user_input" )
graph.add_edge( "get_user_input" , "echo" )
graph.add_edge( "echo" , "add_one" )
graph.add_conditional_edges(
"add_one" ,
lambda state: "get_user_input" if state.get( "count" )< 2 else END
)
# 6.编译工作流,生成执行实例
execution = graph. compile ()
# 7.调用工作流实例
execution.invoke({
"user_input" : "",
"count" : 0 ,
})
2.5 使用langgraph实现自定义function calling
官方文档:https://docs.langchain.com/oss/python/langgraph/overview
第一步:定义工具函数和工具信息
# 实现自定义function calling
# 定义工具
def add(a: float ,b: float ):
return round (a + b, 2 )
def times(a: float ,b: float ):
return round (a * b, 2 )
#定义工具信息
tools_info = [{
"name" : "add" ,
"desc" : "计算a+b,返回保留2位小数的结果" ,
"kwargs" :{
"a" :( float ,),
"b" :( float ,),
},
},
{
"name" : "times" ,
"desc" : "计算a*b,返回保留2位小数的结果" ,
"kwargs" :{
"a" :( float ,),
"b" :( float ,),
},
},
]
tools_mapping = { "add" :add, "times" :times}
第二步:定义需要使用的llm请求实例
from agently import Agently
Agently.set_settings(
"OpenAICompatible" ,
{
"model" : "qwen3:4b" ,
"base_url" : "http://127.0.0.1:11434/v1/" ,
"api_key" : "nothing" ,
},
)
# 创建一个模型请求对象
llm = Agently.create_agent()
第三步:定义、编译、执行工作流
根据以上function calling的工作流程,拆解了如下几个关键的工作流节点函数:
-
generate_tool_plan:根据用户输入来生成工具调用的计划
-
call_tool:工具调用
-
generate_response_with_tool_result:根据工具调用的结果生成返回结果
-
print_response:输出返回结果
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Any
# 定义在工作流中流转更新的数据结构
class State(TypedDict):
# 过程中需要使用到的运行数据
user_input: str
need_tool: bool | None
call_tool_data: dict | None
tool_result: Any | None
response: str | None
# 工具信息数据
tools_info: list [ dict ]
tools_mapping: dict
# 定义关键处理节点
def generate_tool_plan(state: State):
user_input = state.get( "user_input" )
tools_info = state.get( "tools_info" )
result = llm. input ({
"user_input" : user_input,
"tools_info" : tools_info,
}).output({
"need_tool" : ( bool , "判断如果回应用户的问题{user_input},是否需要使用{tools_info}中提供的工具?" ),
"directly_reply" : ( str , "如果{use_tool}==False,直接在此项进行回复,否则输出''" ),
"call_tool_data" : {
"name" : ( str , "从{tools_info}中选择需要使用的工具名,务必保持一致" ),
"kwargs" : ({
" " : " " ,
}, "务必遵照与{tools_info.name}对应的{tools_info.args}描述生成调用参数字典" ),
"purpose" : ( str , "用简单的文字描述为什么要调用这个工具" ),
}
}).start()
return {
"need_tool" : result[ "need_tool" ],
"response" : result[ "directly_reply" ],
"call_tool_data" : result[ "call_tool_data" ],
}
def call_tool(state: State):
tools_mapping = state.get( "tools_mapping" )
call_tool_data = state.get( "call_tool_data" )
tool_name = call_tool_data[ "name" ]
kwargs = call_tool_data[ "kwargs" ]
purpose = call_tool_data[ "purpose" ]
tool = tools_mapping[tool_name]
result = tool( * * kwargs)
return {
"tool_result" : f "{ purpose }: { result }" ,
}
def generate_response_with_tool_result(state: State):
user_input = state.get( "user_input" )
tool_result = state.get( "tool_result" )
response = (
llm. input ({
"user_input" : user_input,
"tool_result" : tool_result,
})
.instruct( "请根据{tool_result}提供的信息回复{user_input}" )
.start()
)
return {
"response" : response,
}
def print_response(state: State):
response = state.get( "response" )
print (f"[结果]:\n{ response }" )
# 创建工作流程
graph = StateGraph(State)
# 添加注册节点
graph.add_node( "generate_tool_plan" , generate_tool_plan)
graph.add_node( "call_tool" , call_tool)
graph.add_node( "generate_response_with_tool_result" , generate_response_with_tool_result)
graph.add_node( "print_response" , print_response)
# 添加连接节点
graph.add_edge(START, "generate_tool_plan" )
def route_tool_plan(state: State):
# 根据need_tool分流
need_tool = state.get( "need_tool" )
if need_tool:
return "call_tool"
else :
return "print_response"
graph.add_conditional_edges( "generate_tool_plan" , route_tool_plan)
graph.add_edge( "call_tool" , "generate_response_with_tool_result" )
graph.add_edge( "generate_response_with_tool_result" , "print_response" )
graph.add_edge( "print_response" , END)
# 编译工作流,生成一个可执行的实例
execution = graph.compile ()
#调用工作流实例
result = execution.invoke({
"user_input" : "请帮我计算23*2等于多少" ,
"need_tool" : None ,
"call_tool_data" : None ,
"tool_result" : None ,
"response" : None ,
"tools_info" : tools_info,
"tools_mapping" : tools_mapping,
})
print ( ">>>>>" , result[ "response" ])
运行结果:

3.function calling 与mcp的联系与区别
3.1 一句话理解
-
Function Calling:AI 调用单个工具的方式(像遥控器按一个键)
-
MCP:AI 连接所有工具的协议标准(像 USB 接口,什么设备都能插)
3.2 核心区别(最快理解)
| 维度 | Function Calling | MCP (Model Context Protocol) |
|---|---|---|
| 本质 | 调用方式/接口 | 连接协议/标准 |
| 范围 | 单个 AI 调用单个工具 | 任意 AI 连接任意工具 |
| 标准化 | 各厂商各自实现(OpenAI、Anthropic 各不同) | 统一标准(Anthropic 提出) |
| 类比 | 一个插头 | 通用插座标准 |
3.3 什么时候用哪个?
| 场景 | 推荐 |
|---|---|
| 简单项目,只调用 1-2 个 API | Function Calling |
| 需要使用特定 AI 厂商的高级功能 | Function Calling |
| 多个工具、多个 AI 之间共享 | MCP |
| 开发 IDE 插件、桌面应用 | MCP |
| 想让工具被不同 AI 客户端使用 | MCP |
3.4 总结
Function Calling 是"怎么调用"(方法),MCP 是"怎么连接"(协议)。
-
Function Calling:AI 说"我要用这个工具"
-
MCP:AI 说"我按这个标准来连接工具"
可以这样理解:Function Calling 像各种品牌的充电头(互不通用),MCP 像 USB-C 接口(统一标准,都能用)。

浙公网安备 33010602011771号