猪头少年 - 云南AI专家 - 云南独立开发者

Engineering note

项目启动与第一条消息:先把最小闭环跑起来

这是「Agent 工程实战」的第 3 篇。专题从一个能聊天、能调工具的 .NET Agent 出发,逐步补齐可靠性、安全、测试与发布能力。

本篇要解决的问题: 从配置、启动到第一轮对话,建立一个可以继续演进的 .NET Agent 起点。

返回专题路线


本章定位:本章从 Program.cs 开始,讨论一个控制台入口为什么适合原型、又为什么不能一直承载全部职责。你会设计退出命令、取消机制和输入命令层。

建议阅读方式:先通读原理,再对照当前项目源码,最后完成本章实践。本章的实战重点是交互契约:先决定用户怎样控制 Agent,再决定代码怎样实现。

本章导读

本章从 Program.cs 开始,讨论一个控制台入口为什么适合原型、又为什么不能一直承载全部职责。你会设计退出命令、取消机制和输入命令层。

本章采用“源码观察 → 概念拆解 → 工程改造 → 实践验证”的顺序。示例中的接口和代码骨架用于说明设计方向,真正提交代码时应结合项目当前状态逐步落地。

3.1 入口代码

当前入口非常直接:

using Boo;

var agent = new BooAgent();
await agent.Start();

Start() 先调用 HandleTools(),然后无限读取控制台输入。只要输入非空,就交给 HandleMessage()

3.2 为什么要保留简单入口

早期项目不需要一开始就引入复杂依赖注入、Web 服务和数据库。控制台入口有三个优点:

  • 能快速验证模型协议;
  • 能直接观察工具调用过程;
  • 出错时调用链短,容易调试。

但“简单入口”不应变成“所有逻辑都放在入口附近”。随着项目增长,建议把 BooAgent 拆成会话服务、工具注册中心、模型客户端和展示层。

3.3 第一项小改进:退出命令与取消

不要让用户只能强制关闭进程。第一步可以加入:

if (message is "/exit" or "/quit")
    break;

下一步用 CancellationTokenSource 响应 Ctrl+C:

using var cancellation = new CancellationTokenSource();
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    cancellation.Cancel();
};

之后把 token 从 Start() 传到 HTTP 请求和工具执行方法。

3.4 本章小结

控制台程序适合验证 Agent 的核心机制,但应该从第一天就设计退出、取消和错误提示,否则后续迁移到 Web 时会被迫重写。

3.5 控制台是一个 Adapter

当前 BooAgent 同时承担了三件事:用户界面、工具发现和 Agent 编排。这样做在几十行代码时很舒服,但随着功能增加,控制台会逐渐变成“上帝类”。

可以先不做大规模重构,只要建立一个目标概念:控制台是 Adapter。

public interface IUserInput
{
    Task<string?> ReadAsync(CancellationToken cancellationToken);
}

public interface IUserOutput
{
    void Write(string text);
    void WriteLine(string text);
}

BooAgent 以后只依赖这两个接口,Web、桌面应用或测试就可以提供不同实现。控制台不再决定 Agent 怎么思考,它只负责把输入转换成请求,把事件显示出来。

3.6 输入处理要有命令层

当用户开始使用 Agent,很快会需要 /help/tools/clear/history 等控制命令。如果所有输入都直接发给模型,命令会浪费 token,也会让模型误以为用户真的要执行一个任务。

可以先用一个小的解析器:

public sealed record ConsoleCommand(string Name, string[] Arguments);

public static ConsoleCommand? TryParseCommand(string input)
{
    if (!input.StartsWith('/')) return null;
    var parts = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
    return new ConsoleCommand(parts[0][1..], parts.Skip(1).ToArray());
}

命令层只处理本地控制,不参与模型消息历史。这样 /clear 的含义是清空会话,而不是请模型执行一个名为 clear 的动作。

3.7 本章练习:设计 CLI 交互

为程序设计以下行为,并写出输入输出样例:

  • /help 显示帮助;
  • /tools 列出已注册工具;
  • /clear 清理当前会话;
  • /exit 安全退出;
  • 空行不发送请求。

完成后再考虑把这些命令接入代码。先写交互契约,能避免后续不断修改用户体验。

3.8 本章产出

本章结束时,控制台应有明确的退出方式、取消提示、控制命令和错误显示。即使 Agent 以后迁移到 Web,这些交互约定仍然可以转化为 API 行为。

单篇实战作业

实践:设计五条 CLI 输入样例,并为每条写出预期状态和输出;先写行为,再写实现。

建议把作业拆成一个独立提交,并在提交说明中写清楚:改动前的行为、改动后的行为、验证命令、尚未解决的风险。本章的实战重点是交互契约:先决定用户怎样控制 Agent,再决定代码怎样实现。

章节复盘

复盘问题:控制台退出、取消、错误和空输入是否有稳定行为?稳定行为比漂亮提示更重要。

本章的完成标准不是把所有设计一次性做完,而是能把它变成项目中的一个明确边界,并为下一章留下可验证的接口。


下一篇:第 4 篇《模型调用与流式输出:正确处理 SSE 和增量事件》

如果你正在把 Agent 放进真实工作流,建议完成本篇的实战作业后再继续:每一步都应留下可验证的代码、测试或运行记录。

Next step

不要停在单篇文章。

沿主专题继续阅读,查看同一问题从概念到实战的完整路径。

返回「Agent 工程实战」路线