Skip to content

6.2 标准 Harness 工程的设计清单

一句话定义

Harness = Agent + Memory + Skill + Rule + Tool + EvalCheck + Monitor

Harness 不是一个单独的组件,是把前面几章的零件按 LLM 的五个缺陷组织起来的治理体系。

框架基础

本 Harness 工程基于微软 MAAI 框架构建,具体由两层组成:

框架层命名空间提供的核心抽象
Microsoft.Extensions.AI (MEAI)Microsoft.Extensions.AIIChatClientChatMessageAIToolFunctionInvokingChatClient 等底层 AI 交互抽象
Microsoft.Agents.AI (MAF)Microsoft.Agents.AIChatClientAgentAIContextProvider(Invoking/Invoked 生命周期)、AgentSession 等 Agent 编排抽象

在此基础上,项目自行构建了业务组件层:

自建组件基于的框架抽象职责
AgentFactoryChatClientAgent + ChatClientAgentOptions统一创建 LeadAgent/SubAgent,自动组装 Provider 链和装饰器管道
MemoryFactsContextProviderAIContextProvider注入记忆事实为 SystemMessage
MonitoringContextProviderAIContextProvider在调用前后发布事件、记录指标
TitleContextProviderAIContextProvider首轮对话自动生成会话标题
CompactionProviderAIContextProvider上下文窗口超阈值时按策略压缩
EvalCheck 委托模型自建(不依赖框架)函数式输出校验,可组合、可批量评估
InMemoryEventBus + MetricsAccumulator自建(不依赖框架)事件驱动的监控体系

Harness 全景图:Agent + Memory + Skill + Rule + Tool + EvalCheck + Monitor

Harness 全景图

┌─────────────────────────────────────────────────────────┐
│                     Harness 全景                         │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ┌─────────┐   ┌──────────┐   ┌──────────┐            │
│  │  Agent   │   │  Memory  │   │  Skill   │            │
│  │ (大脑)   │   │ (记忆)   │   │ (肌肉)   │            │
│  └────┬─────┘   └────┬─────┘   └────┬─────┘            │
│       │              │              │                    │
│       ▼              ▼              ▼                    │
│  ┌─────────────────────────────────────────┐            │
│  │          上下文提供者链                    │            │
│  │  MemoryFacts + Monitoring + Skill + ...  │            │
│  └─────────────────┬───────────────────────┘            │
│                    │                                     │
│                    ▼                                     │
│  ┌─────────────────────────────────────────┐            │
│  │          Agent 执行引擎                   │            │
│  │  LeadAgent → SubAgent → Tool 调用循环     │            │
│  └─────────────────┬───────────────────────┘            │
│                    │                                     │
│       ┌────────────┼────────────┐                       │
│       ▼            ▼            ▼                        │
│  ┌─────────┐  ┌─────────┐  ┌──────────┐               │
│  │  Rule   │  │  Tool   │  │EvalCheck │               │
│  │ (围栏)  │  │ (手)    │  │ (质检)   │               │
│  └─────────┘  └─────────┘  └──────────┘               │
│                                                         │
│  ┌─────────────────────────────────────────┐            │
│  │          Monitor (监控)                   │            │
│  │  事件总线 + 指标累积 + 诊断追踪            │            │
│  └─────────────────────────────────────────┘            │
└─────────────────────────────────────────────────────────┘

七个组件各做什么

组件职责对应 LLM 缺陷
Agent大脑,负责决策和编排
Memory记忆,存储用户偏好和历史事实有自己的偏好(用记忆覆盖)
Skill肌肉记忆,标准化操作流程会偷懒(用流程防止跳步)
Rule围栏,定义行为边界没有边界(用规则约束)
Tool手,执行具体操作
EvalCheck质检,输出后校验会迎合用户(用检查拦截)
Monitor监控,追踪执行过程会一根筋(用超时中断)

Agent 知识体系的四层结构

Agent 的知识来源 = 检索 + 记忆 + 规范 + 能力

检索层:
  ├── RepoWiki(结构化摘要)→ 项目全貌
  ├── AGENTS.md(手工策展)→ 最重要的上下文
  └── Memory Facts(记忆事实)→ 用户偏好和决策

规范层:
  ├── Rule(编码约束)→ 不能怎么做
  └── Skill(操作流程)→ 怎么做

能力层:
  ├── Tool(工具)→ 能做什么
  └── SubAgent(分身)→ 专业领域执行

治理层:
  ├── EvalCheck(输出校验)→ 做得对不对
  └── Monitor(监控追踪)→ 做没做到

一次完整的 Agent 调用链路

下面以 LeadAgent 处理一条用户消息为例,展示完整的调用链路。标注了【框架】的是 MAAI 框架自动完成的部分,标注了【自建】的是项目自行实现的组件。

1. 用户发消息

2. LeadAgentManager 确保会话存在                          【自建】

3. AgentFactory.CreateLeadAgentAsync()                    【自建】

   ├── 创建 IChatClient 原始连接(通过 IModelProvider)     【自建】

   ├── 构建 IChatClient 装饰器管道(MEAI Decorator 模式):  【框架+自建】
   │   ├── LoggingChatClient(日志记录)                    【自建】
   │   ├── OpenTelemetryChatClient(分布式追踪)            【框架】
   │   ├── CustomRetryingChatClient(429/5xx 指数退避重试)  【自建】
   │   └── FunctionInvokingChatClient(工具调用循环)        【框架】

   ├── 构建 AIContextProvider 链(MAF 生命周期管理):       【框架+自建】
   │   ├── ChatHistoryProvider(对话历史加载/存储)          【框架】
   │   ├── CompactionProvider(上下文压缩)                  【自建】
   │   ├── MemoryFactsContextProvider(注入记忆事实)        【自建】
   │   ├── MonitoringContextProvider(事件发布+指标记录)    【自建】
   │   ├── TitleContextProvider(标题生成)                  【自建】
   │   └── AgentSkillsProvider(注入 Skill 文档)           【自建】

   └── 返回 ChatClientAgent 实例                            【框架】

4. agent.RunAsync()                                        【框架】

   ├── 框架自动执行所有 Provider 的 Invoking 阶段            【框架】
   │   ├── ChatHistoryProvider → 加载历史消息
   │   ├── CompactionProvider → 检查是否需要压缩
   │   ├── MemoryFactsContextProvider → 注入记忆事实
   │   ├── MonitoringContextProvider → 发布 AgentInvokedEvent
   │   └── TitleContextProvider → (首轮时准备生成标题)

   ├── 构建完整上下文 → 调用 LLM                           【框架】

   ├── LLM 返回工具调用 → 执行工具 → 再次调用 LLM(循环)   【框架】
   │   └── 每个工具调用被 ObservingAIFunction 包装           【框架】

   ├── 每次 LLM 输出经过 EvalCheck 校验                    【自建】

   └── 框架自动执行所有 Provider 的 Invoked 阶段            【框架】
       ├── ChatHistoryProvider → 持久化消息到 MemoryStore
       ├── MonitoringContextProvider → 发布完成/失败事件
       └── TitleContextProvider → 首轮对话生成会话标题

5. 结果返回给用户

EvalCheck:输出后校验

LLM 输出不可控,但可以在输出之后做检查。

EvalCheck 是项目自建组件,不依赖 MAAI 框架,可以独立使用。核心思想:把校验逻辑解耦为独立的检查函数,每个 Check 只关注一个维度,通过组合多个 Check 形成完整的校验策略。

csharp
// EvalCheck 委托:输入评估项,输出校验结果
public delegate EvalCheckResult EvalCheck(EvalItem item);

// 校验结果
public class EvalCheckResult
{
    public bool Passed { get; set; }
    public string Reason { get; set; }
    public string CheckName { get; set; }
}

为什么用 delegate 而不是接口?因为 Check 应该是轻量的、函数式的。一个 Check 就是一个函数——输入评估项,输出通过/失败。

多个 Check 可以组合:

csharp
// 组合多个检查
var checks = new List<EvalCheck> { jsonFormatCheck, requiredFieldCheck, contentCheck };

// 依次执行,收集结果
var results = checks.Select(c => c(item)).ToList();
var allPassed = results.All(r => r.Passed);

校验失败时的处理:

  • 自动重试:把失败原因注入到下一轮对话,让 LLM 修正
  • 降级:如果重试次数超限,返回部分结果
  • 人工介入:标记为需要人工审核

Monitor:监控体系

监控体系是项目自建组件,基于 MAF 的 AIContextProvider 生命周期钩子(Invoking/Invoked)实现非侵入式接入。由三层组成:

组件职责
事件层InMemoryEventBus发布/订阅模式,解耦监控和业务
指标层MetricsAccumulator记录请求数、耗时、错误率
追踪层WorkflowDiagnosticContextOpenTelemetry 桥接,支持分布式追踪
csharp
// 监控上下文提供者:继承 MAF 框架的 AIContextProvider,在 Agent 调用前后发布事件
internal class MonitoringContextProvider : AIContextProvider  // ← MAF 框架抽象基类
{
    // Invoking 阶段:框架在调用 LLM 前自动执行
    protected override ValueTask<AIContext> ProvideAIContextAsync(...)
    {
        _eventBuffer.Add(new AgentInvokedEvent { AgentName = agentName, ... });
        context.Session.StateBag.SetValue(MonitoringStartTicksKey, ...);
        return new ValueTask<AIContext>(new AIContext());
    }
    
    // Invoked 阶段:框架在 LLM 调用完成后自动执行(成功/失败都会执行)
    protected override async ValueTask InvokedCoreAsync(...)
    {
        var durationMs = GetDurationMs(context.Session);
        
        if (context.InvokeException is not null)
        {
            _metricsAccumulator.RecordRequest(durationMs, 0, isError: true);
            await _eventBus.PublishAsync(new AgentFailedEvent { Error = ... });
        }
        else
        {
            _metricsAccumulator.RecordRequest(durationMs, 0, isError: false);
            await _eventBus.PublishAsync(new AgentCompletedEvent { Duration = ... });
        }
    }
}

关键设计:MonitoringContextProvider 重写了 InvokedCoreAsync 而非 StoreAIContextAsync,因为 MAF 框架默认在失败时会跳过 StoreAIContextAsync,而监控需要在成功和失败两种场景都执行。

设计清单总结

设计一个 Harness 工程,需要回答以下问题:

维度要回答的问题对应组件
知识Agent 怎么获取项目信息?AGENTS.md + RepoWiki + Memory
规范Agent 做事必须遵守什么?Rule + Skill
能力Agent 能做什么操作?Tool + SubAgent
校验怎么判断 Agent 做得对不对?EvalCheck
监控怎么追踪 Agent 的执行过程?Monitor
边界Agent 不能做什么?Rule + AGENTS.md + SubAgent 隔离

这六个维度,就是 Harness 工程的设计清单。缺任何一个,Agent 都可能失控。