1 这一阶段的背景

队友在前几周完成了前端核心视图的实现——Changes View、History View、仓库管理、布局系统,Go Sidecar 后端的 branch / commit / diff / merge / remote / staging 等操作也已全部到位。

需求文档里定义的智能化特性——AI 辅助提交、冲突智能管控、自然语言 Git 操作——在这个阶段还未完成。

这一阶段我的工作是完成项目规划文档里定义的 P0: Agent 框架与配置集成,为后续三个 P1 智能工作流(智能提交、冲突管控、自然语言助手)搭建可复用的底层基础。


2 本阶段完成工作

  • src/renderer/src/agent/ 下建立完整 Agent 基础层,覆盖 LLM 客户端、工具注册、Prompt 模板、安全策略、输出解析、降级处理、运行时核心七个模块
  • 扩展全局配置类型,支持多 LLM Provider 配置与持久化
  • 实现 GlobalSettingsPanel 完整配置界面,替换原有占位 UI
  • 修复配置持久化逻辑中的覆盖问题
  • 将 StatusBar 的 AI 状态指示灯改为动态状态显示

3 架构设计:为什么要单独建一个 Agent 层

在动手之前,有一个问题需要先想清楚:AI 能力以什么形式接入现有代码?

最直接的做法是在需要 AI 的地方直接调用 LLM API,比如在提交面板的"AI 生成"按钮里写一段 fetch 调用,拿到结果填入输入框。这样实现最快,但有几个明显的问题:三个 P1 工作流都需要调用 LLM,每处各自实现意味着错误处理、降级逻辑、安全校验都要重复写;LLM 的输出是非结构化文本,每个调用方都要自己做解析,容错处理很容易遗漏;Provider 切换(从 OpenAI 换到 DeepSeek)需要改动多处代码。

所以我们决定先建一个独立的 Agent 层,把 LLM 调用、工具注册、Prompt 管理、安全策略、输出解析、降级处理统一封装,P1 工作流只需要描述任务,不需要关心底层细节。

最终 agent/ 目录的结构如下:

agent/
  types.ts          # 核心类型定义
  llmClient.ts      # LLM HTTP 客户端
  toolRegistry.ts   # Tool 注册与调用
  prompts/          # Prompt 模板管理
  safety.ts         # 风险分级策略
  outputParser.ts   # 结构化输出解析
  fallback.ts       # 降级处理
  agentRuntime.ts   # Runtime 核心
  index.ts          # 统一导出

每个文件职责单一,P1 工作流接入时只需要调用 agentRuntime.ts 暴露的接口,内部细节对上层透明。


4 LLM 客户端:不引入 SDK,直接用 fetch 实现

LLM 客户端(llmClient.ts)的设计原则是尽量轻,目前没有引入第三方 SDK,直接用 fetch 实现,支持两套协议:

OpenAI 兼容协议,覆盖 OpenAI 官方、DeepSeek、通义千问、以及本地部署的模型(只要兼容 /v1/chat/completions 接口格式):

// POST /v1/chat/completions
// Authorization: Bearer {apiKey}

Anthropic 协议,对应 Claude 系列模型:

// POST /v1/messages
// x-api-key: {apiKey}
// anthropic-version: 2023-06-01

两套协议实现为两个独立的 Client 类,对外暴露统一接口:

export interface LlmClient {
  chat(messages: AgentMessage[], tools?: ToolDefinition[]): Promise<LlmResponse>
  ping(): Promise<void>
}

export function createLlmClient(config: LlmConfig): LlmClient {
  if (config.provider === 'anthropic') return new AnthropicClient(config)
  return new OpenAICompatibleClient(config)
}

用工厂函数统一创建,上层代码不感知 Provider 差异。ping() 方法用于连接检测,发送最小请求验证 API Key 有效性,结果写入 llmConfigStore,在 StatusBar 里实时反映。

配置类型定义在 src/shared/types/sidecar.ts 中,作为前后端共享的类型契约:

export type LlmProvider = 'openai' | 'anthropic'

export interface LlmConfig {
  provider: LlmProvider
  apiKey: string
  baseUrl?: string       // OpenAI 兼容模式下可覆盖,用于接入国产模型
  modelName: string
  temperature?: number
  maxTokens?: number
}

baseUrl 字段的设计值得说一下。OpenAI 兼容协议现在已经是国内大模型的事实标准,DeepSeek、通义千问都支持。通过允许覆盖 baseUrl,用户可以在不改任何代码的情况下切换到这些模型,只需要在设置界面填入对应的 API 地址即可。


5 工具注册系统

Tool Call 是让 LLM 与应用功能交互的标准机制。Agent 层预声明了 13 个 Git 工具定义,P1 工作流在接入时注入具体的执行逻辑:

export const GIT_TOOL_NAMES = {
  GET_STATUS: 'git.getStatus',
  GET_DIFF: 'git.getDiff',
  GET_STAGED_DIFF: 'git.getStagedDiff',
  STAGE_FILE: 'git.stageFile',
  STAGE_ALL: 'git.stageAll',
  CREATE_COMMIT: 'git.createCommit',
  GET_CONFLICT_FILES: 'git.getConflictFiles',
  GET_TRIPLET_CONTENT: 'git.getTripletContent',
  APPLY_PATCH: 'git.applyPatch',
  CONTINUE_MERGE: 'git.continueMerge',
  ABORT_MERGE: 'git.abortMerge',
  // ...
}

P0 阶段只声明定义,不注册实现——因为实现依赖 Sidecar IPC 调用,属于各 P1 工作流自己的职责。P1 接入时只需要两步:

// 第一步:注册工具实现
toolRegistry.register({
  definition: GIT_TOOL_DEFINITIONS['git.stageFile'],
  execute: async ({ filePath }) => { /* 调用 sidecar */ }
})

// 第二步:调用 Agent
const result = await runAgentWithFallback(
  getCurrentLlmConfig(),
  {
    taskType: 'commit.generateMessage',
    systemPrompt: COMMIT_SYSTEM_PROMPT,
    userMessage: renderCommitMessagePrompt(diff),
    tools: ['git.getStagedDiff']
  },
  (raw) => parseStructured(raw, COMMIT_MESSAGE_SCHEMA)
)

这种"声明在 P0,实现在 P1"的分层设计,让 Agent 框架本身不依赖具体的 Git 操作实现,可以独立测试和维护。


6 处理 LLM 输出的不确定性

LLM 的输出是自然语言文本,即便在 Prompt 里明确要求返回 JSON,模型有时仍会在 JSON 前后附加说明文字,或者把 JSON 包在 markdown 代码块里。outputParser.ts 实现了三层提取逻辑:

export function extractJson(text: string): unknown {
  // 第一层:提取 ```json ... ``` 代码块内容
  // 第二层:提取第一个完整的 { ... } 块
  // 第三层:整体尝试 JSON.parse
}

三层兜底能覆盖绝大多数模型的输出格式差异。在此基础上,针对三个 P1 工作流预置了五个 Schema(CommitMessage / CommitGroups / ConflictRisk / ConflictResolve / NlIntent),P1 接入时直接用 parseStructured(raw, SCHEMA) 验证并转换输出,不需要各自处理格式问题。


7 心得体会

这次工作和之前几周有明显不同——之前主要是理解已有代码、跑通通信链路,这次是在相对空白的地方从零建立一套新的基础设施。

设计 Agent 层的过程中,对"接口先于实现"这件事有更具体的体会。Tool 注册系统的工具定义和执行实现分开,Output Parser 的 Schema 和解析逻辑分开,Fallback 的 taskType 和降级策略分开——这些分离看起来在 P0 阶段没有立竿见影的收益,但它们决定了 P1 工作流能否真正独立推进,而不是每个人都在改同一层代码。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐