Agent日志审计全链路记录:prompt、工具调用

当 Agent 出现异常回答或反复调用某个工具时,只看最终结果很难定位问题。
本文介绍如何通过全链路日志记录 Agent 每次请求的 prompt、工具调用、返回结果与时间戳,并提供一套可落地的 Python 装饰器方案,帮助零基础用户快速建立起可检索的审计日志,方便后续排查与监控告警。

先确定日志要覆盖哪些字段

要真正实现全链路审计,日志字段必须足够完整。
除了最基本的 prompt 和工具返回值,建议至少包含以下内容:

  • 请求ID:每次用户请求生成一个唯一标识,方便串联整个对话流程。
  • prompt原文:记录用户输入的完整内容,注意去掉可能违规或敏感的信息。
  • 工具名称:本次调用了哪个工具,比如 searchcalculatorget_weather
  • 入参快照:调用工具时传入的关键参数。
  • 返回结果:工具的原始输出,建议截断超长结果,避免日志文件膨胀。
  • 时间戳与耗时:记录开始时间、结束时间和总体耗时,用于性能分析。

这些字段可以放在 JSON Lines 文件中,每行一条记录,既方便阅读,也方便后续用 jq 或日志平台解析。

用装饰器统一埋点

手动在每个工具函数里写日志容易漏报,而且代码重复。
推荐用 Python 装饰器统一处理,下面是一个最小可用的实现:

import time
import logging
import json
from functools import wraps

logger = logging.getLogger("agent_audit")
logging.basicConfig(filename="agent_audit.log", level=logging.INFO)

def audit_tool_call(request_id=None):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            # 记录入参
            payload = {
                "request_id": request_id,
                "tool": func.__name__,
                "args": kwargs,
                "start_time": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start)),
                "timestamp": int(start * 1000)
            }
            try:
                result = func(*args, **kwargs)
                payload.update({
                    "status": "success",
                    "result": result,
                    "duration_ms": round((time.time() - start) * 1000, 2),
                    "end_time": time.strftime("%Y-%m-%d %H:%M:%S")
                })
                logger.info(json.dumps(payload, ensure_ascii=False))
                return result
            except Exception as e:
                payload.update({
                    "status": "error",
                    "error": str(e),
                    "duration_ms": round((time.time() - start) * 1000, 2)
                })
                logger.error(json.dumps(payload, ensure_ascii=False))
                raise
        return wrapper
    return decorator

在工具函数上加上 @audit_tool_call(request_id=current_request_id),每次调用都会自动生成一条 JSON 日志。
如果需要在 prompt 层面记录,可以在 Agent 的主流程入口单独写一行日志,把当前 prompt 也带上。

把 prompt 和工具调用链条串起来

光记录工具调用还不够,还要知道哪个 prompt 触发了后续动作。
建议在主处理函数中先写入一条“请求开始”日志,包含请求ID、prompt、开始时间,然后再记录工具调用,这样日志文件里就能看到完整链路:

def run_agent(prompt):
    request_id = uuid.uuid4().hex
    logger.info(json.dumps({
        "event": "prompt_received",
        "request_id": request_id,
        "prompt": prompt,
        "timestamp": int(time.time() * 1000)
    }, ensure_ascii=False))

    # 后续所有工具调用都传入 request_id
    @audit_tool_call(request_id=request_id)
    def search(query):
        return "搜索结果"

    result = search(prompt)
    return result

这样日志里就能按 request_id 过滤出某个请求的完整时间线,包括 prompt_receivedtool: search、返回结果和耗时。

常见坑位与避坑建议

  • 只记录成功不记录失败:异常路径同样要写日志,否则线上出问题时日志里一片空白。
  • 日志写入阻塞业务:在低峰期直接写文件问题不大,高并发建议改用异步队列或 logging.Handler 异步写入。
  • 忘记截断上下文:工具返回大量文本时,完整写入会拖慢 I/O。可以只保留前几十个字符,并附加 result_truncated 标记。
  • 敏感信息脱敏:prompt 或结果里可能包含令牌、手机号等,记录前先做脱敏处理,避免审计日志成为数据泄露点。

验证审计日志是否完整

写完代码后,建议用一条真实 Agent 请求做验证。
日志文件生成后,用下面的命令检查 JSON 是否合法:

tail -n 5 agent_audit.log | jq .

如果 jq 能正常解析,说明日志结构没问题。
再按请求ID统计某次请求的调用次数和耗时:

grep "你的请求ID" agent_audit.log | jq -r '[.tool, .duration_ms] | @tsv'

正常情况下,你能看到 prompt_received 记录,以及后续每个工具调用的名称、状态、耗时和时间戳。

通过完整记录 prompt、工具调用过程、返回结果和时间,Agent 就不再是一个黑盒。
建议先按本文方法跑通最小闭环,再根据实际场景增加筛选和告警。
遇到异常时优先查看日志里的时间戳和调用顺序,通常能快速定位问题所在。

分享到:
上一篇
Agent调用Shell命令沙箱隔离
下一篇
Agent访问内网服务SSRF风险
1
系统公告

机房迁移升级通知

尊敬的用户: IP 段 103.23.148.x、156.224.29.x 原香港一区线路波动、攻击频繁,平台定于 7 月 5 日凌晨分批迁移至香港 GIA 机房,硬件升级 AMD 铂金机型。 迁移均在凌晨操作,最大程度降低业务影响,迁移期间服务器临时关机; 升级后配置不降低、费用不涨价,数据默认同步迁移; 迁移后 IP 全部更换,请及时修改域名解析、防火墙白名单; 建议提前备份重要数据,有问题可联系在线客服。 感谢理解与支持! 泽御云科技 2026.06.30
服务中心
客服
在线客服
24小时为您服务
咨询
联系我们
联系我们,为您的业务提供专属服务。
24/7 技术支持
如果您遇到寻求进一步的帮助,请过工单与我们进行联系。
24/7 即时支持
泽御云
售前客服
泽御云
泽御云
售后客服
泽御云
技术支持
评价
您对当前页面的整体感受是否满意?
😞
非常不满意
😕
不满意
😐
一般
🙂
满意
😊
非常满意