最近在折腾自己的 Agent CLI 项目,本来用着一套挺“标准”的四层架构:Textual 前端负责界面,中间挂个 ChatRuntimeState 管状态,再来个 ConversationService 做门面,最后接上 deepagents 或者 LangGraph 这种大杀器跑逻辑。

想法是好的,分层清晰,解耦到位。但跑久了就发现一个字:重。

🤔 为什么现在的架构让人头大?

这种多层夹心饼结构的最大问题在于“过犹不及”。

  1. 数据流转太漫长:用户在 TUI 输入一个指令,得穿过 Textual、State、Service 才能到 Runtime。每一层都要做序列化/反序列化,或者是对象转换,这在小规模时没感觉,一旦交互频繁,延迟感会非常明显。
  2. 调试噩梦:遇到 Bug 想看日志,日志散落在四个不同的层级里。光是把一个请求的完整链路在日志里拼出来,就得花半天。
  3. 过度封装:ConversationService 这种 Facade 层,在 CLI 这种轻量级应用里显得有点多余。CLI 不需要复杂的 API 版本管理或多端复用,很多接口包装纯属为了“架构优美”而存在,实际上徒增复杂度。

💡 重构思路:做减法是关键

既然痛点找到了,那重写的时候就要痛下决心做减法。我有几个思路,大家可以参考一下。

1. 抛弃中间商,前端直连核心

如果你的 CLI 不需要极其复杂的状态机(比如像 VS Code 那种多插件联动),ChatRuntimeState 这一层可以考虑合并或者去掉。

Textual 本身就是一个基于状态的 UI 框架,完全可以在 Widget 层维护一个简化的状态模型,直接与后端逻辑交互。少一层转发,代码量能少三分之一。

2. Service 层真的需要吗?

Conversation Service Facade 最大的价值通常是隔离实现。但在 CLI 项目里,你大概率只会用一种 Runtime(要么 LangGraph,要么 DeepAgents)。既然绑死了,为什么不直接让 UI 层调用 Runtime 的抽象接口?

建议:如果你没打算做 Web 版或者 GUI 版,Service 层基本可以砍掉,把它变成一个简单的 Controller 或者直接作为异步任务管理器即可。

3. LangGraph/DeepAgents 的“瘦身”用法

RunTime 层最容易变重。很多人喜欢把整个 LangGraph 的复杂配置全搬进来。

  • 按需加载:不要在 CLI 启动时就初始化所有工作流。采用懒加载模式,只有当用户触发特定指令时,才把对应的 Graph 跑起来。
  • 流式优化:TUI 最讲究实时反馈。确保你的 Runtime 是以流的形式吐数据的,而不是等这整条链路跑完才给前端一个结果。Textual 对流式输出的支持其实不错,用好 on_message 钩子,体验能上一个台阶。

🛠 推荐的新架构方案

基于上面的分析,我们可以把四层压缩为两层半:

  • UI/Interaction Layer (Textual):负责渲染 +基础交互逻辑 + 简单的 UI State 维护。
  • Core Logic Layer (Runner):负责 Agent 的编排(LangGraph 等)。
  • Event Bus (那半层):如果 UI 和 Runner 不想直接耦合,可以用一个轻量级的事件总线(比如 asyncio.Queue)解耦。UI 发事件,Runner 收到干活,干完发回结果。

⚡ 其他避坑建议

  • 异步到底:TUI 的主线程绝对不能阻塞。LangGraph 的调用一定要扔进 asyncio.create_task 或者线程池里。任何阻塞 IO 都会让界面卡顿,那是 CLI 项目的体验大忌。
  • 配置分离:不要在代码里硬编码 Prompt 或者 Graph 结构。把 Agent 的定义做成 YAML 或 JSON 配置文件,启动时加载。这样以后你要换架构或者调 Prompt,都不用改代码重编译。

总结

CLI 工具的核心在于“快”和“准”。为了教科书般的分层而牺牲性能和开发效率,在 CLI 开发里是一笔不划算的买卖。

如果你也在为臃肿的架构头疼,不妨试试把中间那几层“保温杯”摔了,让数据裸奔一会儿,你会发现世界清爽很多。

标签: none

AI Skills Smart Station on Nick Launches

评论已关闭