业务代码凭什么不能直接调 Agent?——我在律所 AI 项目里做的 Harness 运行时治理

📅 2026/8/24 22:33:46
业务代码凭什么不能直接调 Agent?——我在律所 AI 项目里做的 Harness 运行时治理
我们最近开发了一个律所 AI 平台Go 管业务、Python 管 Agent。项目里有一条硬性约束业务代码禁止直接调 Agent、Skill 或大模型唯一出口是 Harness 的RunTurn。这篇文章拆解这套运行时治理到底解决了什么。一、先把问题说清楚Demo 和企业级的区别在哪Demo 阶段你在 service 里写一行agent.Chat(msg)能跑就完事。真上生产立刻会冒出一堆问题权限凭什么这个接口能调这个工具谁授权稳定Agent 挂了业务要不要一起挂可观测一次回答错了是检索、工具、模型还是后处理的问题审计谁在什么时候调了什么能不能追溯安全日志里不能把手机号、案情明文写进去。这些问题不靠多写几个 if而要靠一层统一的运行时治理。我们管它叫 Harness。二、唯一出口所有 AI 调用都从 RunTurn 走这是最关键的一条规则Go 业务层不直接调 Agent唯一入口是harness.Runtime.RunTurn。RunTurn(ctx,input)├─1.白名单校验deny bydefault ├─2.审计 think 步骤 ├─3.熔断器Allow()放行判断 ├─4.AgentClient.RunTurn 调用 Python Agent ├─5.成功RecordSuccess审计 llm_call带耗时 └─6.失败RecordFailure写入失败交接队列返回错误码核心代码长这样已精简func(rt*Runtime)RunTurn(ctx context.Context,in RunTurnInput)(*RunTurnOutput,error){start:time.Now()ifrt.whitelistnil{err:apierr.New(apierr.CodeToolDenied,白名单未初始化deny by default)rt.writeAudit(ctx,in,think,nil,nil,false,err.Error(),time.Since(start))returnnil,err}rt.writeAudit(ctx,in,think,map[string]string{message:in.Message},nil,true,,0)ifrt.breaker!nil{iferr:rt.breaker.Allow();err!nil{rt.writeAudit(ctx,in,circuit_break,in,nil,false,err.Error(),time.Since(start))returnnil,apierr.New(apierr.CodeAgentServiceError,err.Error())}}resp,err:rt.agentClient.RunTurn(ctx,AgentTurnRequest{TraceID:in.TraceID,RunID:in.RunID,SessionID:in.SessionID,CustomerID:in.CustomerID,Message:in.Message,Channel:in.Channel})latency:time.Since(start)iferr!nil{ifrt.breaker!nil{_rt.breaker.RecordFailure()}ifrt.handoff!nil{_,_rt.handoff.Enqueue(FailureContext{TraceID:in.TraceID,RunID:in.RunID,SessionID:in.SessionID,CustomerID:in.CustomerID,Message:in.Message,Cause:err.Error()})}apiErr:apierr.New(apierr.CodeAgentServiceError,err.Error())rt.writeAudit(ctx,in,llm_call,in,nil,false,apiErr.Error(),latency)returnnil,apiErr}ifrt.breaker!nil{rt.breaker.RecordSuccess()}out:RunTurnOutput{SessionID:resp.SessionID,Reply:resp.Reply,Stage:resp.Stage,Turn:resp.Turn,Intent:resp.Intent,NeedMoreInfo:resp.NeedMoreInfo,Tag:resp.Tag,FollowupID:resp.FollowupID,RAG:resp.RAG}rt.writeAudit(ctx,in,llm_call,in,resp,true,,latency)returnout,nil}注意几个细节白名单为 nil 时直接拒绝默认拒绝熔断在前、调用在后审计在调用前后各打一次失败不是只返回错误还把它丢进 handoff 队列后面可以补做或人工接管。三、默认拒绝白名单是最小权限的落地我们用一份 YAML 白名单控制 Agent 能调哪些工具allowed_tools:-get_customer_info-create_followup_record-send_message-legal_qa_skill-customer_tagging_skill加载时如果文件缺失或解析失败直接返回 error而不是放行。校验逻辑一句话概括白名单为空或未加载任何工具都拒绝。func(w*Whitelist)Check(toolNamestring)error{ifwnil{returnErrToolDenied{ToolName:toolName}}if_,ok:w.allowed[toolName];!ok{returnErrToolDenied{ToolName:toolName}}returnnil}这就是最小权限不是默认允许、黑名单排除而是默认拒绝、白名单显式授权。对 AI 应用尤其重要——模型的输出是不确定的能调的工具越少出事的面积越小。四、熔断器Agent 挂了不能让业务一起挂Agent 服务超时、报错、不可用是常态。如果业务每次都死等一个 Agent 故障就能拖垮整个后端。这里是一个极简的三态熔断器closed / open / half-open。closed正常放行连续失败达到阈值默认 3 次→ openopen在 openTimeout默认 30 秒内直接拒绝快速失败open 超时后进入 half-open放一个探测请求成功回到 closed失败继续 openfunc(c*CircuitBreaker)Allow()error{c.mu.Lock()deferc.mu.Unlock()ifc.stateCircuitOpen{iftime.Since(c.openedAt)c.options.OpenTimeout{returnErrCircuitOpen}c.stateCircuitHalfOpen c.probetrue}ifc.stateCircuitHalfOpen!c.probe{returnErrCircuitOpen}ifc.stateCircuitHalfOpen{c.probefalse}returnnil}快速失败 半开放探测是熔断器的两个关键动作。业务侧拿到的是明确错误码而不是无限等待。五、审计要脱敏还要不阻塞主链路审计很容易做成又慢又脏。这里做了两件事写之前先脱敏手机号13812345678→138****5678超长内容截断到 2048 字符避免把完整案情明文塞进日志。异步写审计落库放在 goroutine 里带 recover 兜底审计挂了不能拖垮主链路。funcmaskPII(sstring)string{returnphonePattern.ReplaceAllStringFunc(s,func(mstring)string{iflen(m)!11{returnm}returnm[:3]****m[7:]})}调用方只关心 TraceID、SessionID、步骤、成功与否、耗时。一次回答为什么错可以顺着 TraceID 从头看到尾。六、失败要能交接而不只是报错失败时除了 RecordFailure还把上下文塞进 HandoffQueueiferr!nil{_rt.breaker.RecordFailure()_,_rt.handoff.Enqueue(FailureContext{TraceID:in.TraceID,RunID:in.RunID,SessionID:in.SessionID,CustomerID:in.CustomerID,Message:in.Message,Cause:err.Error(),})returnnil,apierr.New(apierr.CodeAgentServiceError,err.Error())}这一步的价值在于模型失败不是终点它是一段可以被重新处理、被人工接管、被复盘的任务。Demo 阶段没人做这件事企业级必须做。七、这套东西面试怎么讲如果面试官问你做过什么工程化的事情别只说我用了 RAG可以说我做项目时业务层不直接调 Agent而是加了一层 Harness 运行时治理用白名单做默认拒绝的权限控制、用三态熔断器做降级、用异步审计加脱敏做可观测、用失败交接队列做兜底。这样 Agent 不稳定或模型出错时业务不会被拖垮问题也能定位。这段话背后是真实代码、真实踩坑比背八股有说服力得多。八、欢迎交流这个项目还落地了 MCP 工具总线、Skill 三段式和 207 条回归评测完整实现和在线演示我放在评论区和我的个人主页欢迎交流https://wangzhongyang.com/