惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
月光博客
月光博客
博客园 - Franky
小众软件
小众软件
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
Visual Studio Blog
有赞技术团队
有赞技术团队
V
V2EX
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
腾讯CDC
D
DataBreaches.Net
Hugging Face - Blog
Hugging Face - Blog
Martin Fowler
Martin Fowler
罗磊的独立博客
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
WordPress大学
WordPress大学
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog

V2EX

我用 AI 写代码,但终端管理反而成了累赘——于是我做了 codux [调研] 各位在公司都用什么 ide 和 agent 写代码? 老运维 share 一个运维平台 看到有公司考核 token 指标,很好奇大家上个月的 AI 账单是多少 GLM-Coding 调用持续报错: z.ai 的 Lite 套餐几乎无法使用,官方 Pro/Max 是否稳定? 现在还有什么渠道可以稳定安全地使用 Claude 吗? 上海漕河泾内推,本组有 2 个 hc,一个后端,一个前端,预算都是 20k 左右,不打卡,氛围好 如果 V2EX 上有一组不永久保存聊天记录(比如只保存 7 天或者 24 小时)的聊天室,那么会开启哪些有用或者有趣的可能? gemini cli 貌似挂了,一直返回 403 第一次在自媒体上赚到钱 收集了最近在使用的低价 GPT, Gemini,邮箱等 AI 会员的小店合集 讨论个大实话:现在企业还在说 AI 编程提效 20%, 30%的,真的太落后,没用懂 AI。因为包括很多前沿公司,已经狂奔到提效 200%-500%的情况 [招聘][远程][币安] 前端/后端/QA/iOS/Android 至少 3 年以上经验 目前有大量 HC 欢迎投递 Chatgpt Pro 用量用不完的可以开这些设置 面试的时候好像遇到钓鱼了,给各位避个坑 cursor 年续费 22 号到期, 自动续费是否还是老的计次套餐呢 被两件破事毁掉的一下午,琐碎的内耗消磨人的精力 使用 Planet 存储 Codex 的会话或者重要信息 如果业务部门领导不要你开发功能,而是要求你教会它用 claude code 开发功能,你会怎么做? 分享一个 MacOS 接绿联 CM818 USB 转 DP 转接器使用感受 我的 HR 朋友 10 年老 Java ,非全大专,大家帮忙看看简历 开源了一个 AI 口语练习工具,音素级发音评分,完全免费可自部署 V2EX 上有哪些你觉得很有趣、印象深刻的妹纸? 字节为啥不出个国内版 Vercel? 有在大马的朋友吗? 问个运营商问题 你们在有领导的公司大群发过的最大胆的消息是什么 公司裁员,目前没有工作。想试试摆摊,做一个移动鲜啤打酒车 我的硬盘 Memblaze Pblaze 5 Linux 下不识别,给 Linux 内核提交了补丁, AI 说有望被合并 只有我一个人觉得 codex 不好用?
哈? LLM 的工具调用还能这么玩?!
pmpmp · 2025-11-28 · via V2EX

前面我们讨论过工具调用的一些文章,比如《 MCP 到底是个什么鬼?》,看过的朋友们似乎已经大概搞清楚了 LLM 的 function calling 的是怎么回事、该怎么用了。你说,function calling 的作用不就这样嘛?还有啥好聊的?

但是,你有没有想过,它还能用来干啥?你可能觉得我要开始输出什么奇技淫巧了,不不不,我们这个号不会干这种事情,大可以放心食用。

再开始之前,我们得再回忆一下 function calling 的本质到底是什么?

你说这还不简单嘛,不就是,让 LLM 输出工具调用,然后 APP 真的去调用工具,然后把结果再给到 LLM 进行更精准的推理么?

是的,没错,我们先回忆一下这个过程(如下) —— 一会你会拍大腿的🤭🤭🤭

1. APP 调用 LLM ,并传入工具的定义
	↓
2. LLM 返回工具调用的 JSON 描述(工具调用指令)
	↓
3. APP 去调用工具,并得到结果,将结果再次传给 LLM
	↓
4. LLM 根据原始 prompt + 工具结果,推理出结论
	↓
5. APP 收到结果

然后再问自己一个问题:2 中,LLM 返回的工具调用指令我们能不能拿来干点其他的事情呢?

我们从 instructor 这个库开始聊起吧,Instructor 是一个非常轻量级的库,他的作用是 Structured Outputs for LLMs ,让 LLM 输出结构化的数据,什么意思呢,正常情况下,我们调用 LLM 期望它输出结构化的数据是非常恼火的,有些模型是支持的,但是输出的结果也不一定是准确的,要么是格式问题,要么缺胳膊少腿,虽然你用提示词去约束它,但是它依然是有可能会出错的,Instructor 干了一件非常简单的事情,就是保证 LLM 输出的就是你想要的结构化数据,它的代码大概是这样的(来自它的 Github ):

import instructor
from pydantic import BaseModel

# Define what you want
class User(BaseModel):
    name: str
    age: int

# Extract it from natural language
client = instructor.from_provider("openai/gpt-4o-mini")
user = client.chat.completions.create(
    response_model=User,
    messages=[{"role": "user", "content": "John is 25 years old"}],
)

print(user)  # User(name='John', age=25)

他是怎么做到的呢?难道里面偷偷摸摸的套了一个流程,while loop 直到 LLM 输出正确的结构化信息?当然不是了,其实它用了一个 function calling 的 trick ,什么意思呢?

它是这样做的:首先 User 必须是一个 pydantic 的 BaseModel ,这样,Instructor 就能拿到这个数据结构的 json 描述了,对吧?比如是这样的:

# Instructor 内部会把它转成这样:

function_schema = {
    "name": "User",
    "parameters": {
        "type": "object",
        "properties": {
            "name": {"type": "string"},
            "age": {"type": "integer"}
        },
        "required": ["name", "age"]
    }
}

然后呢?然后它就把这个数据结构“伪装”成一个函数调用,具体怎么做呢?根据不同的 LLM 的数据格式要求,将这个 json 转为一个“函数”传给 LLM ,让 LLM 必须调用这个"函数",比如,当 LLM 是 openai 的时候,他就偷偷的在 tool_choice 里面放上:

# 👉👉👉 先把 Pydantic 模型转换为 tool 定义
new_kwargs["tools"] = [
    {
        "type": "function",
        "function": {"name": "User", "parameters": {"name": "string", "age": "int"}}, # 👈 关键在这里
    }
]

# 然后,强制让 LLM 调用这个"函数",🤣🤣🤣
new_kwargs["tool_choice"] = {
    "type": "function",
    "function": {"name": "User", "parameters": {"name": "string", "age": "int"}},
}

“傻乎乎”的 LLM 看到一定要调用这个函数,它就会在推理的过程中输出一个 function calling 的返回,这时候 Instructor 顺其自然的就捕获到 LLM 返回的这个 tool_call 了,比如是这样的:

"tool_call":{"name": "User", "arguments": '{"name": "John", "age": 25}'}

然后呢?然后 Instructor 就自己构建一个 User 的对象再返回给你呗。

于是,这就是你在开头看到的“魔法”的那一幕 —— 传一个数据的定义,哇塞,它真的就给你返回来了,严丝合缝,不出错,还节省了 token (不然你自己还得反复的 call LLM 对吧?)。

有意思吧?你看,这个过程是不是就是利用了 function calling 的能力?虽然最后并没有真的去 call 什么函数,但是这个机制是可以被我们用作结构化数据的。Instructor 用了一个“欺骗”LLM 的办法拿到了自己想要的东西,

你说,这不就是个雕虫小技么?登不上什么大雅之堂吧?

呵呵,其实有不少著名的框架里面的一些巧思其实都是这么做的,我再给你举几个例子吧。

Langchain 不久前发布了 V1 版本,其中有一个重要的更新就是:Sructured output

from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
from pydantic import BaseModel


class Weather(BaseModel):
    temperature: float
    condition: str

def weather_tool(city: str) -> str:
    """Get the weather for a city."""
    return f"it's sunny and 70 degrees in {city}"

agent = create_agent(
    "gpt-4o-mini",
    tools=[weather_tool],
    response_format=ToolStrategy(Weather)
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "What's the weather in SF?"}]
})

print(repr(result["structured_response"]))
# results in `Weather(temperature=70.0, condition='sunny')`

他是怎么做的?有兴趣的去看下它的代码,其实和 Instructor 的做法几乎如出一辙。这里稍微吐槽一下(其实社区里面都有这样的吐槽),Langchain 的这个接口设计的多少是有点“业余”的 —— 你发现没,tool 和 response_format 并没有什么对应关系,如果我传了多个 tool 和多个 response_format 呢?我怎么知道里面是怎么处理的?最后会返回什么给我?站在开发者的角度看,很晦涩很不透明。我估计后面还得改进。

我们再举一个例子,比如大名鼎鼎的 autoGen ,也用了这个“小技巧”。

autoGen 里面的多智能体合作是怎么实现的呢?难道真的想像营销号说的那样,框架实现了让多个智能体在里面“群聊”么?

当然不是的,这都是营销话术,真正是怎么实现的呢?还是用 function calling 的 trick ,这个过程大概是这样的:

首先,autoGen 的 AssistantAgent 有一个参数叫做:handoffs ,虽然你可能不怎么会用到它,这是什么东西呢,其实本质上它就是描述了“在何种情况下将发言权转移给哪个 Agent”,所谓的发言权也是个营销话术,其实就是 autoGen 的调度引擎决定运行哪个 agent ,这是第一步

然后,autoGen 的 Swarm (就是负责调度的)就开始“演戏了”,当某一个 agentA 被调度起来的时候(内部就是一个 ReAct ),它一定会去跟 LLM 交互吧,关键来了,跟 LLM 交互的时候,它偷偷的将 handoffs 这种描述包装成了一个“假函数”传给 LLM ,例如函数名叫 xxx ,描述是“假设遇到这样这样的情况,请调用该函数,参数是 AgentB”,LLM 一看,哦,现在是这种情况,所以我要调用 xxx 函数,于是,这个 xxx 的调用指令就被 Swarm 捕捉到了,然后它一看,LLM 上套了,那么我们现在就要转而去调度 B 智能体,你看,本质上就是用“欺骗”LLM 的办法,让 LLM 通过 function calling 做了一次路由的调度,是不是很巧妙?

整个过程的基本思想就是这样的:

# 给 LLM 看到的"工具":
tools = [
    {
        "name": "calculator",  # 真工具
        "description": "计算数学表达式"
    },
    {
        "name": "transfer_to_agent_BBB",  # 🫣 假工具
        "description": "转交给 agent_BBB"
    },
    {
        "name": "transfer_to_agent_CCC",  # 🫣 假工具
        "description": "转交给 agent_CCC"
    }
]

# LLM 以为自己在"调用工具"
# 实际上是在"做路由决策"

所以,哪里有什么“群聊”?表面上看起来是多个 Agent 在"讨论",实际上呢,都是Swarm耍的花招,让 LLM 被忽悠的在后面吭哧吭哧的干活,Swarm 在前面出尽了风头,哈哈。

类似的例子还有很多,比如 agno 这个框架,也在用这样的方式刷花招,agno 里面有一个东西叫做 ReasoningTools,看起来也是一个很神奇的东西,仿佛加上这个参数,LLM 就能进行“深度思考”了,他是怎么做到的呢?也是用了 function calling 的原理来忽悠“老实巴交”的 LLM 进行中间过程的输出,有兴趣的小伙伴自己去探索一下吧,评论区见,哈哈哈。

好啦,今天就聊到这里吧,Agent 的领域其实很多东西并没有大家想的那么神奇,都是在工程上利用了 LLM 的特性和机制做了事情,有些事情很有趣,比如我们今天讨论的,绝大部分事情都是苦哈哈的事情。

以上,全文。感谢大家

最后再为自己写的一个框架做个广告,求一波⭐⭐⭐啊大神们

chak ( https://github.com/zhixiangxue/chak-ai ),一个极简的 LLM 调用工具,轻量级,内置上下文管理和工具调用,使用起来非常简单、顺手、优雅。

☝️☝️☝️☝️☝️☝️☝️☝️点它点它点它点它☝️☝️☝️☝️☝️☝️☝️☝️