Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Function Calling 链路追踪

Function Calling 调用链追踪:从 Prompt 参数到执行的全流程可视化

作者:码龙大大

Function Calling的链路追踪,核心就是把一次工具调用从 Prompt 输入到最终结果返回的全过程,拆解成几个关键节点并记录下来,让每一步都可见、可查、可回溯,本文介绍Function Calling调用链追踪:从Prompt参数到执行的全流程可视化,感兴趣的朋友一起看看吧

Function Calling 的链路追踪,核心就是把一次工具调用从 Prompt 输入到最终结果返回的全过程,拆解成几个关键节点并记录下来,让每一步都可见、可查、可回溯。下面把这条链路和具体实现方式拆开讲清楚。

如何实现链路追踪

实现方式主要分两类,可以根据实际场景选:

‌关键实现细节‌:

一、当 Agent 告诉你"我已经调用了搜索",但实际没有

一个常见的场景:用户问 Agent 某个问题。
Agent 回复"我已查询了最新数据,结果是……"
但实际上,工具调用根本没有发生。
大模型凭空生成了结果,即幻觉。

排查这类问题极其困难。
你无法知道模型是否真的产生了 tool_call。
即便产生了,参数是否正确传递给了工具。
即便正确传递了,工具返回的结果是否被模型正确消费。
每一步都是黑盒,只能靠猜。

核心诉求很明确。
需要看到 Function Calling 的完整调用链路。
从用户输入 Prompt,到模型输出 tool_call 参数。
再到参数注入工具执行,最后结果回传给模型。

二、Function Calling 的请求-执行链路

Function Calling 的调用包含四个关键节点。
第一,Prompt 编译:将用户输入和工具 Schema 组装为模型输入。
第二,模型推理:模型决定调用哪个工具及参数。
第三,工具执行:解析参数,调用实际函数。
第四,结果注入:工具返回结果写入对话上下文。

sequenceDiagram
    participant U as 用户
    participant P as Prompt 组装层
    participant M as LLM
    participant T as 工具调度器
    participant F as 实际函数
    participant L as 追踪系统
    U->>P: 发送问题
    P->>P: 注入工具 Schema
    P->>L: 📝 Node1: Prompt 快照记录
    P->>M: 发起推理请求
    M->>L: 📝 Node2: 模型输出(含 tool_call)
    M-->>P: 返回 tool_call 决策
    alt 需要调用工具
        P->>T: 派发 tool_call
        T->>T: 参数校验
        T->>L: 📝 Node3: 工具名 + 参数
        T->>F: 执行函数
        F-->>T: 返回结果
        T->>L: 📝 Node4: 工具返回值
        T-->>P: 注入结果到上下文
    else 直接回复
        M-->>U: 返回文本回复
    end
    P->>L: 📝 Node5: 最终响应
    P-->>U: 返回给用户

每个节点记录三类信息。
时间戳、输入输出快照、延迟耗时。
这些数据构成完整的调用链。

三、链路追踪的 Go 实现

package tracing
import (
    "context"
    "encoding/json"
    "fmt"
    "time"
)
// TraceNode 追踪节点
type TraceNode struct {
    ID        string          `json:"id"`
    Type      string          `json:"type"`      // prompt_compile, llm_call, tool_exec, result_inject
    Timestamp time.Time       `json:"timestamp"`
    Input     json.RawMessage `json:"input"`
    Output    json.RawMessage `json:"output"`
    Duration  time.Duration   `json:"duration_ms"`
    Error     string          `json:"error,omitempty"`
}
// TraceChain 完整调用链
type TraceChain struct {
    TraceID   string      `json:"trace_id"`
    Nodes     []TraceNode `json:"nodes"`
    StartTime time.Time   `json:"start_time"`
    EndTime   time.Time   `json:"end_time"`
}
// Tracer 链路追踪器
type Tracer struct {
    chain *TraceChain
    mu    sync.Mutex
}
// NewTracer 创建追踪器
func NewTracer(traceID string) *Tracer {
    return &Tracer{
        chain: &TraceChain{
            TraceID:   traceID,
            Nodes:     make([]TraceNode, 0),
            StartTime: time.Now(),
        },
    }
}
// RecordNode 记录一个调用节点,返回结束函数
func (t *Tracer) RecordNode(nodeType string, input interface{}) func(output interface{}, err error) {
    inputBytes, _ := json.Marshal(input)
    start := time.Now()
    node := TraceNode{
        ID:        fmt.Sprintf("%s-%d", nodeType, len(t.chain.Nodes)),
        Type:      nodeType,
        Timestamp: start,
        Input:     inputBytes,
    }
    return func(output interface{}, err error) {
        node.Duration = time.Since(start)
        if err != nil {
            node.Error = err.Error()
        } else if output != nil {
            node.Output, _ = json.Marshal(output)
        }
        t.mu.Lock()
        t.chain.Nodes = append(t.chain.Nodes, node)
        t.mu.Unlock()
    }
}
// Export 导出完整链路
func (t *Tracer) Export() *TraceChain {
    t.mu.Lock()
    defer t.mu.Unlock()
    t.chain.EndTime = time.Now()
    return t.chain
}

在 Function Calling 流程中的使用。

func handleFunctionCall(ctx context.Context, prompt string, tools []Tool) (string, *TraceChain) {
    tracer := NewTracer(generateTraceID())
    // Node 1: Prompt 编译
    endPrompt := tracer.RecordNode("prompt_compile", map[string]interface{}{
        "user_input": prompt,
        "tool_count": len(tools),
    })
    compiledPrompt := buildPrompt(prompt, tools)
    endPrompt(compiledPrompt, nil)
    // Node 2: LLM 推理
    endLLM := tracer.RecordNode("llm_call", map[string]interface{}{
        "model":     "gpt-4",
        "prompt_len": len(compiledPrompt),
    })
    response, err := callLLM(ctx, compiledPrompt)
    if err != nil {
        endLLM(nil, fmt.Errorf("LLM 调用失败: %w", err))
        return "", tracer.Export()
    }
    endLLM(response, nil)
    // Node 3: 工具执行
    if response.ToolCall != nil {
        endTool := tracer.RecordNode("tool_exec", map[string]interface{}{
            "tool_name":  response.ToolCall.Name,
            "parameters": response.ToolCall.Args,
        })
        result, execErr := executeTool(ctx, response.ToolCall)
        endTool(result, execErr)
        if execErr != nil {
            return fmt.Sprintf("工具执行失败: %v", execErr), tracer.Export()
        }
    }
    return response.Content, tracer.Export()
}

四、追踪系统的边界与成本

全链路追踪带来存储和性能开销。
每个调用链约 2-5KB JSON 数据。
日均 10 万次调用产生约 500MB 追踪数据。
建议设置采样率,生产环境采样 10% 即可。

追踪系统不适合的场景:
延迟极度敏感的实时推理(增加 1-2ms 序列化开销);
本地开发的快速调试(直接用 print 更高效)。

隐私问题需注意。
用户 Prompt 可能包含敏感信息。
追踪数据中应脱敏处理或设置存储过期策略。

五、总结

Function Calling 的链路追踪让每个调用可观测。
通过在关键节点插入 Recorder,捕获输入、输出、耗时。
Tracer 模式比侵入式日志更结构化。
生产环境建议 10% 采样率,配合数据脱敏。
有了完整链路,排查幻觉和参数错误不再靠猜。

到此这篇关于Function Calling 调用链追踪:从 Prompt 参数到执行的全流程可视化的文章就介绍到这了,更多相关Function Calling 链路追踪内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文