Agent之Function Call

LLM只知道根据一段token预测下一个token,给它一段话,它能一个token一个token地猜出一个回答来。但是现在Agent做到的事可不仅仅是回答问题,它还能自己读代码改代码,自己设计方案写成文档,自己检查写的代码有没有问题等等。从知道到做到的关键,就是 function call(又被称为tool call,工具调用),LLM会告诉客户端,应该调用什么工具,客户端调用对应的工具,来真正做到一些事情。

一、回答日期相关问题

LLM是无状态的,更没有独立的时钟,问它时间的问题,它就完全不知道了:

(用的上一篇文的练习代码,只是调用了DeepSeek API,并没有调用工具:github.com/rengmo/ai-p...

但是打开DeepSeek APP一问,回答得好好的:

可能是前端获取了会话时的时间信息,或者是后端调用了时间工具获取时间信息传给了模型。

这里并没有去看DeepSeek APP具体是怎么实现的。对于这次的练习来说,是在后端代码中加上了调用时间工具的逻辑,加上后就可以正常获得时间相关的答复了,这个工具是后端代码里实现的,不是LLM的能力。

  1. 首先定义工具的类型、名称和描述、工具的调用参数等信息:
jsx 复制代码
var toolDefs = []Tool{
	{
		Type: "function",
		Function: toolFunction{
			Name:        "get_current_time",
			Description: "获取当前日期、时间和星期。用户问「现在几点」「今天几号」时必须调用,不要编造。",
			Parameters: map[string]any{
				"type":       "object",
				"properties": map[string]any{},
			},
		},
	},
	...
  1. 然后在DeepSeek的API请求里面,作为Tools参数的值传递:
jsx 复制代码
body, _ := json.Marshal(apiChatRequest{Model: model, Messages: messages, Tools: toolDefs})
  1. 如果需要用到工具,模型会返回以下格式的数据(这个是DeepSeek API返回的,LLM本身可能只是返回某种格式的文本 ):
jsx 复制代码
tool_calls: [{ name: "get_current_time" }]
  1. 应用程序拿到数据之后,根据name信息,调用在应用程序代码里定义好的函数:
jsx 复制代码
result, _ := runTool(call.Function.Name, call.Function.Arguments)
jsx 复制代码
func runTool(name, argsJSON string) (ToolResult, error) {
	switch name {
	case "get_current_time":
		return ToolResult{Text: currentTimeText()}, nil
jsx 复制代码
func currentTimeText() string {
	loc, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		loc = time.FixedZone("CST", 8*3600)
	}
	now := time.Now().In(loc)
	weekdays := []string{"日", "一", "二", "三", "四", "五", "六"}
	return fmt.Sprintf("%s 星期%s", now.Format("2006年1月2日 15:04:05"), weekdays[now.Weekday()])
}
  1. 拿到函数调用的结果后,把结果添加到message列表中,下一轮循环中,用包含tool调用结果的message列表,传给LLM,获取最终结果:
jsx 复制代码
			working = append(working, Message{
				Role:       "tool",
				ToolCallID: call.ID,
				Content:    result.Text,
			})
jsx 复制代码
	for step := 1; step <= 8; step++ {
		reply, u, err := chatOnce(working)
		
		...
		
		if len(reply.ToolCalls) == 0 {
			if reply.Content != "" {
				onEvent(map[string]string{"type": "delta", "content": reply.Content})
			}
			return working, total, nil
		}

这里比较关键的一点是ReAct,不是直接回答,而是推理(Reason)和 行动(Act)交替进行的多轮循环。

  • 观察(Observe)输入模型的内容。(观察 系统提示词 + 用户问题:"今天是几号,星期几?" )
  • 推理(Reason),LLM决定:先调用时间工具 get_current_time。
  • 行动(Act),应用执行对应的get_current_time函数。
  • 再观察(Observe),观察 系统提示词 + 用户问题 + get_current_time返回的时间数据。
  • 推理(Reason),LLM给到问题的答案。
  • 行动(Act),返回给用户答案。

上面这个是一个比较简单的例子,只循环了2次ReAct。

jsx 复制代码
func runAgent(messages []Message, onEvent SSEEventHandler) ([]Message, usageTotals, error) {
    working := append([]Message(nil), messages...)
    var total usageTotals

    for step := 1; step <= 8; step++ {                    // 上限 8 轮
        reply, u, err := chatOnce(working)                // 非流式,才能解析 tool_calls
        // ... 累加 token ...

        working = append(working, reply)

        if len(reply.ToolCalls) == 0 {                    // 模型不再调工具 → 正常结束
            if reply.Content != "" {
                onEvent(map[string]string{"type": "delta", "content": reply.Content})
            }
            return working, total, nil
        }

        for _, call := range reply.ToolCalls {
            onEvent(map[string]any{"type": "tool_start", "name": fn, "args": args})
            result, _ := runTool(call.Function.Name, call.Function.Arguments)
            onEvent(map[string]any{"type": "tool_done", "name": fn, "result": result.Text})
            if result.FileURL != "" {
                onEvent(map[string]any{"type": "file", "url": result.FileURL, "filename": result.FileName})
            }
            working = append(working, Message{Role: "tool", ToolCallID: call.ID, Content: result.Text})
        }
    }
    return working, total, fmt.Errorf("超过最大工具调用步数(8)")
}

这里设置的最大步骤为8次,目前这个循环是线性执行的非常简单的任务,如果是更复杂的任务,可以使用LangChain、LangGraph之类的实现加强版循环。

二、根据当前季节生成菜谱

根据当前季节,给到一个当季的菜谱PPT,用户可以直接下载。这个例子包含了文件处理能力,工具的实现要复杂一些,比上一个例子的ReAct循环次数会多一点,但主要思路还是一样的。

代码地址:github.com/rengmo/ai-p...

相关推荐
米小虾2 小时前
2026年AI Agent全面爆发:从开源生态到企业级应用的进化之路
人工智能·agent
用户6919026813392 小时前
Vibe Coding 开发项目的基本范式
人工智能·设计模式·代码规范
To_OC2 小时前
别再跟 AI 死磕 prompt 了,我写了个 Loop 让它自己改到满意为止
人工智能·aigc·agent
血小溅2 小时前
三大 AI 编码框架深度对比:GSD vs OpenSpec vs Superpowers
人工智能·后端
默_笙3 小时前
🛬 我让 AI 帮我写了一个打飞机游戏,结果 Canvas 把我整不会了
前端·javascript
梯度不陡3 小时前
AI 到底能不能从零写软件?ProgramBench 和 RepoZero 给出了两种答案
前端·javascript·面试
胡萝卜术4 小时前
滑动窗口最大值:从暴力到单调队列,层层优化全解析
前端·javascript·面试
kyriewen6 小时前
2026 年了,这 6 个 npm 包可以卸载了——浏览器原生 API 已经能替代
前端·javascript·npm
武子康6 小时前
调查研究-186 LangChain 和 LangGraph 的区别:从快速构建 Agent 到生产级工作流编排
人工智能·langchain·llm