跳转到内容

AI

10 篇包含标签 "AI" 的文章

用 Vault 系统构建 AI 时代的跨项目知识库

用 Vault 系统构建 AI 时代的跨项目知识库

Section titled “用 Vault 系统构建 AI 时代的跨项目知识库”

临摹项目学习法正在成为主流,只是学习资料分散、上下文断裂的痛点让 AI 助手难以发挥最大价值。本文介绍 HagiCode 项目的 Vault 系统设计——通过统一的存储抽象层,让 AI 助手能够理解和访问所有学习资源,实现真正的跨项目知识复用。

其实,在 AI 时代,我们学习新技术的方式正在悄然改变。传统的读书、看视频固然重要,但”临摹项目”——深入研究和学习优秀开源项目的代码、架构和设计模式——确实越来越高效。直接运行和修改高质量的开源项目,能让你最快理解真实世界的工程实践。

只是这种方式也带来了新的挑战。

学习资料太分散。笔记可能在 Obsidian 里,代码仓库散落在各个文件夹,AI 助手的对话历史又是一个独立的数据孤岛。每次需要 AI 帮助分析某个项目时,都得手动复制代码片段、整理上下文,过程相当繁琐。

上下文经常断掉。AI 助手无法直接访问本地学习资源,每次对话都得重新提供背景信息。临摹的代码仓库更新快,手动同步容易出错。更糟的是,多个学习项目之间难以共享知识——在 A 项目中学到的设计模式,AI 处理 B 项目时完全不知道。

这些问题的本质是”数据孤岛”。如果能有一个统一的存储抽象层,让 AI 助手能够理解和访问所有学习资源,问题就迎刃而解了。

为了解决这些痛点,我们在开发 HagiCode 时做了一个关键的设计决策:构建一个 Vault 系统作为统一的知识存储抽象层。这个决定带来的变化,可能比想象的还要大——稍后具体说。

本文分享的方案来自在 HagiCode 项目中的实践经验。HagiCode 是一个基于 OpenSpec 工作流的 AI 代码助手,它的核心理念是让 AI 不仅会”说”,更会”做”——能够直接操作代码仓库、执行命令、运行测试。GitHub:github.com/HagiCode-org/site

在开发过程中,我们发现 AI 助手需要频繁访问用户的各类学习资源:代码仓库、笔记文档、配置文件等。如果每次都要用户手动提供,体验就太糟糕了。这促使设计了 Vault 系统。

HagiCode 的 Vault 系统支持四种类型,分别对应不同的使用场景:

类型用途典型场景
folder通用文件夹类型临时学习资料、草稿
coderef专门用于临摹代码项目系统化学习某个开源项目
obsidian与 Obsidian 笔记软件集成现有笔记库的复用
system-managed系统自动管理项目配置、提示词模板等

其中 coderef 类型是 HagiCode 中最常用的,它为临摹代码项目提供了标准化的目录结构和 AI 可读的元数据描述。为什么要专门设计这个类型?因为临摹一个开源项目不是简单的”下载代码”,需要同时管理代码本身、学习笔记、配置文件等多种内容,coderef 把这些都规范好了。

Vault 的注册表以 JSON 格式持久化存储到文件系统:

_registryFilePath = Path.Combine(absoluteDataDir, "personal-data", "vaults", "registry.json");

这个设计看似简单,实则经过深思熟虑:

简单可靠。JSON 格式人类可读,便于调试和手动修改。当系统出现问题时,可以直接打开文件查看状态,甚至手动修复——这在开发阶段特别有用。

降低依赖。文件系统存储避免了数据库的复杂性。不需要额外安装和配置数据库服务,降低了系统复杂度和维护成本。

并发安全。使用 SemaphoreSlim 确保多线程安全。在 AI 代码助手的场景下,可能会有多个操作同时访问 vault 注册表,需要做好并发控制。

系统的核心能力在于能够自动将 vault 信息注入到 AI 提案的上下文中:

export function buildTargetVaultsText(
vaults: VaultForText[],
template: VaultPromptTemplate = DEFAULT_VAULT_PROMPT_TEMPLATE,
): string {
const readOnlyVaults = vaults.filter((vault) => vault.accessType === 'read');
const editableVaults = vaults.filter((vault) => vault.accessType === 'write');
const sections = [
buildVaultSection(readOnlyVaults, template.reference),
buildVaultSection(editableVaults, template.editable),
].filter(Boolean);
return `\n\n### ${template.heading}\n\n${sections.join('\n')}`;
}

这样 AI 助手就能自动理解可用的学习资源,无需用户每次手动提供上下文。这个设计让 HagiCode 的体验变得特别自然——告诉 AI “帮我分析 React 的并发渲染”,AI 就能自动找到之前注册的 React 学习 vault,而不是一遍遍贴代码。

系统将 vault 分为两种访问类型:

  • reference(只读):AI 仅用于分析和理解,不能修改内容
  • editable(可编辑):AI 可以根据任务需要修改内容

这种区分让 AI 知道哪些内容是”只读参考”,哪些是”可以动手改的”,避免了误操作风险。比如注册了一个开源项目的 vault 作为学习材料,肯定不希望 AI 随手修改里面的代码——那就标记为 reference。但如果是自己的项目 vault,就可以标记为 editable,让 AI 帮着改代码。

对于 coderef 类型的 vault,系统提供了一套标准化的目录结构:

my-coderef-vault/
├── index.yaml # vault 元数据描述
├── AGENTS.md # AI 助手的操作指南
├── docs/ # 存放学习笔记和文档
└── repos/ # 通过 Git 子模块管理临摹的代码仓库

这个结构的设计理念是什么?

docs/ 存放学习笔记,用 Markdown 格式记录对代码的理解、架构分析、踩坑经验。这些笔记不仅自己看,AI 也能读懂——在处理相关任务时会自动参考。

repos/ 通过 Git 子模块管理临摹的仓库,而不是直接复制代码。这样做有两个好处:一是保持与上游同步,一个 git submodule update 就能拿到最新代码;二是节省空间,多个 vault 可以引用同一个仓库的不同版本。

index.yaml 包含 vault 的元数据,让 AI 助手快速理解用途和内容。相当于给 vault 写了个”自我介绍”,AI 第一次见到就知道这是干嘛的。

AGENTS.md 是专门写给 AI 助手看的指南,说明如何处理 vault 中的内容。可以在这里告诉 AI:“分析这个项目时重点关注性能优化相关的代码”或者”不要修改测试文件”。

创建一个 CodeRef vault 很简单:

const createCodeRefVault = async () => {
const response = await VaultService.postApiVaults({
requestBody: {
name: "React Learning Vault",
type: "coderef",
physicalPath: "/Users/developer/vaults/react-learning",
gitUrl: "https://github.com/facebook/react.git"
}
});
// 系统会自动:
// 1. 克隆 React 仓库到 vault/repos/react
// 2. 创建 docs/ 目录用于笔记
// 3. 生成 index.yaml 元数据
// 4. 创建 AGENTS.md 指南文件
return response;
};

然后在 AI 提案中引用这个 vault:

const proposal = composeProposalChiefComplaint({
chiefComplaint: "帮我分析 React 的并发渲染机制",
repositories: [
{ id: "react", gitUrl: "https://github.com/facebook/react.git" }
],
vaults: [
{
id: "react-learning",
name: "React Learning Vault",
type: "coderef",
physicalPath: "/vaults/react-learning",
accessType: "read" // AI 只能读取,不能修改
}
],
quickRequestText: "重点关注 fiber 架构和 scheduler 实现"
});

场景一:系统化学习开源项目

创建一个 CodeRef vault,通过 Git 子模块管理目标仓库,在 docs/ 目录记录学习笔记。AI 可以同时访问代码和笔记,提供更精准的分析。在学习某个模块时写的笔记,AI 后续分析相关代码时会自动参考——就像有个”助手”记住了之前的思考。

场景二:复用 Obsidian 笔记库

如果已经在用 Obsidian 管理笔记,直接把现有的 vault 注册到 HagiCode 中就行。AI 可以直接访问知识库,无需手动复制粘贴。这个功能特别实用,很多人都有积累多年的笔记库,接入之后 AI 就能”读”懂知识体系。

场景三:跨项目知识复用

多个 AI 提案可以引用同一个 vault,实现知识的跨项目复用。比如创建了一个”设计模式学习 vault”,里面记录了各种设计模式的笔记和代码示例。无论在分析哪个项目,AI 都能参考这个 vault 中的内容——知识不用重复积累。

系统严格校验路径,防止路径穿越攻击:

private static string ResolveFilePath(string vaultRoot, string relativePath)
{
var rootPath = EnsureTrailingSeparator(Path.GetFullPath(vaultRoot));
var combinedPath = Path.GetFullPath(Path.Combine(rootPath, relativePath));
if (!combinedPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException(VaultRelativePathTraversalCode,
"Vault file paths must stay inside the registered vault root.");
}
return combinedPath;
}

这确保了所有文件操作都在 vault 的根目录范围内,防止恶意路径访问。安全这块不能马虎,AI 助手要操作文件系统,必须把边界划清楚。

使用 HagiCode Vault 系统时,有几点需要特别注意:

  1. 路径安全:确保自定义路径在允许的范围内,否则系统会拒绝操作。这是为了防止误操作和潜在的安全风险。

  2. Git 子模块管理:CodeRef vault 推荐使用 Git 子模块而非直接复制代码。好处前面说过——保持同步、节省空间。只是子模块有自己的使用方式,第一次使用可能需要熟悉一下。

  3. 文件预览限制:系统限制文件大小(256KB)和数量(500个),超大文件需分批处理。这个限制是为了性能考虑,如果遇到超大文件,可以手动拆分或者用其他方式处理。

  4. 诊断信息:创建 vault 会返回诊断信息,失败时可用于调试。遇到问题时先看诊断信息,大部分情况下都能找到线索。

HagiCode 的 Vault 系统本质上是在解决一个简单但深刻的问题:如何让 AI 助手理解和使用本地知识资源。

通过统一的存储抽象层、标准化的目录结构、自动化的上下文注入,实现了”一次注册,处处复用”的知识管理方式。创建一个 vault 后,无论是学习笔记、代码仓库还是文档资料,AI 都能自动访问和理解。

这种设计带来的体验提升是明显的。不再需要手动复制代码片段、重复解释背景信息——AI 助手就像一个真正了解项目情况的同事,能够基于已有知识提供更有价值的帮助。

本文分享的 Vault 系统,正是开发 HagiCode 过程中实际踩坑、实际优化出来的方案。如果觉得这套设计有价值,说明工程实力还不错——那么 HagiCode 本身也值得关注一下。

如果本文对你有帮助:

公测已开始,欢迎安装体验。

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

AI时代如何临摹项目:Vault跨项目持久化存储系统

AI时代如何临摹项目:Vault跨项目持久化存储系统

Section titled “AI时代如何临摹项目:Vault跨项目持久化存储系统”

在AI辅助开发时代,如何让AI助手更好地理解我们的学习资源?HagiCode项目通过Vault系统实现了一个统一的、可被AI理解的知识存储抽象层,让临摹项目的学习效率大幅提升。

在AI时代,开发者学习新技术和架构的方式正在发生深刻变化。“临摹项目”——即深入研究和学习优秀开源项目的代码、架构和设计模式——已经成为一种高效的学习方法。相比传统的读书、看视频,直接阅读和运行高质量的开源项目能让你更快地理解真实世界的工程实践。

只是这种学习方式也面临着不少挑战。

学习资料太分散了。你的笔记可能在Obsidian里,代码仓库散落在各个文件夹,AI助手的对话历史又是一个独立的数据孤岛。当你想让AI帮你分析某个项目时,得手动复制代码片段、整理上下文,这个过程相当繁琐。

其实更麻烦的是上下文断裂。AI助手无法直接访问你的本地学习资源,每次对话都得重新提供背景信息。而且临摹的代码仓库更新很快,手动同步容易出错,多个学习项目之间也难以共享知识。

这些问题本质上都是”数据孤岛”导致的。如果能有一个统一的存储抽象层,让AI助手能够理解和访问你的所有学习资源,问题就迎刃而解了。

本文分享的Vault系统,正是我们在开发 HagiCode 过程中实践出来的解决方案。HagiCode 是一个AI代码助手项目,在我们的日常开发中,经常需要学习和参考各种开源项目。为了让AI助手更好地理解这些学习资源,我们设计了Vault跨项目持久化存储系统。

这套方案已经在HagiCode中经过了实际验证,如果你也面临类似的知识管理难题,希望这些经验能给你一些启发。毕竟,有些坑踩过了,总得留下点什么给后来的人。

Vault系统的核心思想很简单:创建一个统一的、可被AI理解的知识存储抽象层。从实现角度来看,系统具有几个关键特征。

系统支持四种vault类型,分别对应不同的使用场景:

// folder:通用文件夹类型
export const DEFAULT_VAULT_TYPE = 'folder';
// coderef:专门用于临摹代码项目的类型
export const CODEREF_VAULT_TYPE = 'coderef';
// obsidian:与Obsidian笔记软件集成
export const OBSIDIAN_VAULT_TYPE = 'obsidian';
// system-managed:系统自动管理的vault
export const SYSTEM_MANAGED_VAULT_TYPE = 'system-managed';

其中 coderef 类型是HagiCode中最常用的。它专门为临摹代码项目设计,提供了标准化的目录结构和AI可读的元数据描述。

Vault的注册表以JSON格式持久化存储,确保配置在应用重启后仍然可用:

public class VaultRegistryStore : IVaultRegistryStore
{
private readonly string _registryFilePath;
public VaultRegistryStore(IConfiguration configuration, ILogger<VaultRegistryStore> logger)
{
var dataDir = configuration["DataDir"] ?? "./data";
var absoluteDataDir = Path.IsPathRooted(dataDir)
? dataDir
: Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), dataDir));
_registryFilePath = Path.Combine(absoluteDataDir, "personal-data", "vaults", "registry.json");
}
}

这种设计的好处是简单可靠。JSON格式人类可读,便于调试和手动修改;文件系统存储避免了数据库的复杂性,降低了系统的依赖。毕竟,有时候简单的反而是最好的。

最关键的是,系统能够自动将vault信息注入到AI提案的上下文中:

export function buildTargetVaultsText(
vaults: VaultForText[],
template: VaultPromptTemplate = DEFAULT_VAULT_PROMPT_TEMPLATE,
): string {
const readOnlyVaults = vaults.filter((vault) => vault.accessType === 'read');
const editableVaults = vaults.filter((vault) => vault.accessType === 'write');
if (readOnlyVaults.length === 0 && editableVaults.length === 0) {
return '';
}
const sections = [
buildVaultSection(readOnlyVaults, template.reference),
buildVaultSection(editableVaults, template.editable),
].filter(Boolean);
return `\n\n### ${template.heading}\n\n${sections.join('\n')}`;
}

这样就实现了一个重要的功能:AI助手能够自动理解可用的学习资源,无需用户手动提供上下文。这倒也算是一种默契了。

对于coderef类型的vault,HagiCode提供了一套标准化的目录结构:

my-coderef-vault/
├── index.yaml # vault元数据描述
├── AGENTS.md # AI助手的操作指南
├── docs/ # 存放学习笔记和文档
└── repos/ # 通过Git子模块管理临摹的代码仓库

创建vault时,系统会自动初始化这个结构:

private async Task EnsureCodeRefStructureAsync(
string vaultName,
string physicalPath,
ICollection<VaultBootstrapDiagnosticDto> diagnostics,
CancellationToken cancellationToken)
{
Directory.CreateDirectory(physicalPath);
var indexPath = Path.Combine(physicalPath, CodeRefIndexFileName);
var docsPath = Path.Combine(physicalPath, CodeRefDocsDirectoryName);
var reposPath = Path.Combine(physicalPath, CodeRefReposDirectoryName);
// 创建标准目录结构
if (!Directory.Exists(docsPath))
{
Directory.CreateDirectory(docsPath);
}
if (!Directory.Exists(reposPath))
{
Directory.CreateDirectory(reposPath);
}
// 创建AGENTS.md指南
await EnsureCodeRefAgentsDocumentAsync(physicalPath, cancellationToken);
// 创建index.yaml元数据
await WriteCodeRefIndexDocumentAsync(indexPath, mergedDocument, cancellationToken);
}

这套结构的设计也是有讲究的:

  • docs/ 目录存放你的学习笔记,可以用Markdown格式记录对代码的理解、架构分析、踩坑经验等
  • repos/ 目录通过Git子模块管理临摹的仓库,而不是直接复制代码。这样既能保持代码同步,又能节省空间
  • index.yaml 包含vault的元数据,让AI助手快速理解这个vault的用途和内容
  • AGENTS.md 是专门写给AI助手看的指南,说明如何处理这个vault中的内容

或许这样组织起来,AI也能更容易理解你的想法吧。

除了手动创建vault,HagiCode还支持系统自动管理的vault:

public async Task<IReadOnlyList<VaultRegistryEntry>> EnsureAllSystemManagedVaultsAsync(
CancellationToken cancellationToken = default)
{
var definitions = GetAllResolvedDefinitions();
var entries = new List<VaultRegistryEntry>(definitions.Count);
foreach (var definition in definitions)
{
entries.Add(await EnsureResolvedSystemManagedVaultAsync(definition, cancellationToken));
}
return entries;
}

系统会自动创建和管理以下vault:

  • hagiprojectdata:项目数据存储,用于保存项目的配置和状态
  • personaldata:个人数据存储,用于保存用户的偏好设置
  • hbsprompt:提示词模板库,用于管理常用的AI提示词

这些vault在系统启动时自动初始化,无需用户手动配置。毕竟,有些事情交给系统去做就好了,人类何必操心呢。

一个重要的设计是访问控制。系统将vault分为两种访问类型:

export interface VaultForText {
id: string;
name: string;
type: string;
physicalPath: string;
accessType: 'read' | 'write'; // 关键:区分只读和可编辑
}
  • reference(只读):AI仅用于分析和理解,不能修改内容。适用于参考的开源项目、文档等
  • editable(可编辑):AI可以根据任务需要修改内容。适用于你的笔记、草稿等

这种区分很重要。它让AI知道哪些内容是”只读参考”,哪些是”可以动手改的”,避免了误操作风险。毕竟,谁也不想自己的心血被无意中改没了。

看完了原理,咱们来看看实际怎么用。

以下是一个完整的前端调用示例:

const createCodeRefVault = async () => {
const response = await VaultService.postApiVaults({
requestBody: {
name: "React Learning Vault",
type: "coderef",
physicalPath: "/Users/developer/vaults/react-learning",
gitUrl: "https://github.com/facebook/react.git"
}
});
// 系统会自动:
// 1. 克隆React仓库到vault/repos/react
// 2. 创建docs/目录用于笔记
// 3. 生成index.yaml元数据
// 4. 创建AGENTS.md指南文件
return response;
};

这个API调用会完成一系列操作:创建目录结构、初始化Git子模块、生成元数据文件等。你只需要提供基本信息,剩下的交给系统处理。其实这样也挺省心的。

创建好vault后,就可以在AI提案中引用它了:

const proposal = composeProposalChiefComplaint({
chiefComplaint: "帮我分析React的并发渲染机制",
repositories: [
{ id: "react", gitUrl: "https://github.com/facebook/react.git" }
],
vaults: [
{
id: "react-learning",
name: "React Learning Vault",
type: "coderef",
physicalPath: "/vaults/react-learning",
accessType: "read" // AI只能读取,不能修改
}
],
quickRequestText: "重点关注fiber架构和scheduler实现"
});

系统会自动将vault信息注入到AI的上下文中,让AI知道你有哪些学习资源可用。AI能理解你的想法,这倒也算是一种难得的默契了。

在使用Vault系统的过程中,我们总结了一些经验教训。

系统会严格校验路径,防止路径穿越攻击:

private static string ResolveFilePath(string vaultRoot, string relativePath)
{
var rootPath = EnsureTrailingSeparator(Path.GetFullPath(vaultRoot));
var combinedPath = Path.GetFullPath(Path.Combine(rootPath, relativePath));
if (!combinedPath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException(VaultRelativePathTraversalCode,
"Vault file paths must stay inside the registered vault root.");
}
return combinedPath;
}

这点很重要。如果你在自定义vault路径,一定要确保路径在允许的范围内,否则系统会拒绝操作。安全这东西,怎么强调都不过分。

CodeRef vault推荐使用Git子模块而非直接复制代码:

private static string BuildCodeRefAgentsContent()
{
return """
# CodeRef Vault Guide
Repositories under `repos/` should be maintained through Git submodules
rather than copied directly into the vault root.
Keep this structure stable so assistants and tools can understand the vault quickly.
""" + Environment.NewLine;
}

这样做有几个好处:保持代码与上游同步、节省磁盘空间、便于管理多个版本的代码。毕竟,谁愿意一遍遍地重复下载同样的东西呢。

为了防止性能问题,系统限制了文件大小和类型:

private const int FileEnumerationLimit = 500;
private const int PreviewByteLimit = 256 * 1024; // 256KB

如果你的vault包含大量文件或超大文件,可能会影响预览功能的性能。这种情况下可以考虑分批处理或使用专门的搜索工具。毕竟,有些东西太大了,反而不好处理。

创建vault时会返回诊断信息,帮助调试:

List<VaultBootstrapDiagnosticDto> bootstrapDiagnostics = [];
if (IsCodeRefVaultType(normalizedType))
{
bootstrapDiagnostics = await EnsureCodeRefBootstrapAsync(
normalizedName,
normalizedPhysicalPath,
normalizedGitUrl,
cancellationToken);
}

如果创建失败,可以查看诊断信息了解具体原因。出错了就看看诊断信息,这倒也是一种解决问题的方法。

Vault系统通过统一的存储抽象层,解决了AI时代临摹项目的核心痛点:

  • 知识集中管理:所有学习资源集中在一个地方,不再散落各处
  • AI上下文自动注入:AI助手能够自动理解可用的学习资源,无需手动提供上下文
  • 跨项目知识复用:多个学习项目之间可以共享和复用知识
  • 标准化目录结构:提供一致的目录结构,降低学习成本

这套方案在HagiCode项目中已经经过了实际验证。如果你也在做AI辅助开发相关的工具,或者面临类似的知识管理问题,希望这些经验能给你一些参考。

其实技术方案的价值不在于有多复杂,而在于能不能解决实际问题。Vault系统的核心思想很简单——就是建立一个统一的、AI可理解的知识存储层。但正是这个简单的抽象,让我们的开发效率提升了不少。

有时候,简单的反而是最好的。毕竟,复杂的东西往往藏着更多的坑…


如果本文对你有帮助,欢迎来 GitHub 给个 Star,或者访问官网了解更多关于HagiCode的信息。公测已开始,现在安装即可体验完整的AI代码助手功能。

或许,你也可以试试看…

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

渐进式披露:如何用少即是多的理念改进 AI 产品的人机交互

渐进式披露:如何用”少即是多”的理念改进 AI 产品的人机交互

Section titled “渐进式披露:如何用”少即是多”的理念改进 AI 产品的人机交互”

在 AI 产品设计中,用户输入的质量往往决定了输出的质量。本文分享我们在 HagiCode 项目中实践的一套”渐进式披露”交互方案,通过分步引导、智能补全和即时反馈,将用户简短模糊的输入转化为结构化的技术提案,显著提升了人机交互效率。

做 AI 产品的同学应该都遇到过这样的场景:用户打开你的应用,兴致勃勃地输入一行需求,结果 AI 返回的内容完全不搭边。不是 AI 不聪明,只是用户给的信息太少了,毕竟猜心这种事,谁也做不好。

这种现象在我们开发 HagiCode 的过程中尤为明显。HagiCode 是一个 AI 驱动的代码助手,用户通过自然语言描述需求来创建技术提案和会话。可在实际使用中,我们发现用户输入的内容往往存在这些问题:

  • 输入质量参差不齐:有的用户只输入几个字,比如”优化登录”、“修复 bug”,缺乏必要的上下文
  • 技术术语不统一:不同用户用不同的词说同一件事,有人说”前端”有人说”FE”
  • 缺少结构化信息:没有项目背景、没有仓库范围、没有影响范围这些关键信息
  • 重复性问题:相同类型的需求反复出现,每次都要从头解释

这些问题导致的直接后果就是:AI 理解困难、生成的提案质量不稳定、用户体验差。用户觉得”这 AI 不行啊”,我们也很委屈——你只给一句话,让我怎么猜你想要啥?

其实这也没办法,毕竟人和人之间的理解都需要时间,更何况是机器呢?

为了解决这些痛点,我们做了一个大胆的决定:引入”渐进式披露”的设计理念来改进人机交互。这个决定带来的变化,可能比你想象的还要大,只是当时我们也没想到会这么有效罢了。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个开源的 AI 代码助手项目,旨在通过自然语言交互帮助开发者完成代码编写、技术提案生成、代码审查等任务。项目地址:github.com/HagiCode-org/site

这套渐进式披露方案是我们在实际开发过程中,经过多次迭代和优化总结出来的。如果你觉得这套方案有价值,说明我们的工程实力还不错——那么 HagiCode 本身也值得关注一下,毕竟好东西是值得分享的。

“渐进式披露”(Progressive Disclosure)是一个源自 HCI(人机交互)领域的设计原则,核心思想很简单:不要一次性把所有信息和选项都展示给用户,而是根据用户的操作和需求,逐步展示必要的内容

这个原则特别适合 AI 产品,因为 AI 交互天然就是渐进式的——用户说一点,AI 理解一点,然后补充一点,再理解更多。就像人与人之间的交流一样,总得慢慢来,毕竟谁也不能一见面就把心掏出来不是?

具体到 HagiCode 的场景,我们从四个方面实施了渐进式披露:

1. 描述优化机制:让 AI 帮你把话说清楚

Section titled “1. 描述优化机制:让 AI 帮你把话说清楚”

当用户输入简短描述时,我们不是直接让 AI 去理解,而是先触发一个”描述优化”流程。这个流程的核心是”结构化输出”——把用户的自由文本转化为标准格式。就像把散落一地的珍珠串成项链,看起来也就不那么乱了。

优化后的描述必须包含以下几个标准章节:

  • 背景:问题背景和上下文
  • 分析:技术分析和思考过程
  • 解决:解决方案和实施步骤
  • 实践:实际代码示例和注意事项

同时,我们还会自动生成一个 Markdown 表格,展示目标仓库、路径、编辑权限等信息,方便 AI 后续操作。毕竟有个清晰的目录,找起东西来也方便。

下面是实际的代码实现:

// ProposalDescriptionMemoryService.cs 中的核心方法
public async Task<string> OptimizeDescriptionAsync(
string title,
string description,
string locale = "zh-CN",
DescriptionOptimizationMemoryContext? memoryContext = null,
CancellationToken cancellationToken = default)
{
// 构建查询参数
var queryContext = BuildQueryContext(title, description);
// 检索历史上下文
var memoryContext = await RetrieveHistoricalContextAsync(queryContext, cancellationToken);
// 生成结构化提示词
var prompt = await BuildOptimizationPromptAsync(
title,
description,
memoryContext,
cancellationToken);
// 调用 AI 进行优化
return await _aiService.CompleteAsync(prompt, cancellationToken);
}

这个流程的关键在于”记忆注入”——我们会把项目惯例、相似案例、负面模式等历史上下文注入到提示词中,让 AI 在优化时能够参考过去的经验。毕竟吃一堑长一智,过去的经验总不能白白浪费了不是?

注意事项

  • 确保当前输入优先于历史记忆,避免覆盖用户显式指定的信息
  • HagIndex 引用必须作为事实来源,不得被历史案例修改
  • 低置信度的纠错建议不应作为强约束注入

2. 语音输入能力:说话比打字更自然

Section titled “2. 语音输入能力:说话比打字更自然”

除了文本输入,我们还支持语音输入。这在描述复杂需求时特别有用——你想想,打一段技术需求可能要几分钟,但说可能几十秒就完事了,毕竟嘴总是比手快。

语音输入的设计重点是”状态管理”,用户必须清楚当前系统处于什么状态。我们定义了以下几种状态:

  • 空闲:系统就绪,可以开始录制
  • 等待上游:正在连接后端服务
  • 录制中:正在录制用户语音
  • 处理中:正在将语音转换为文本
  • 错误:发生错误,需要用户处理

前端的状态模型大概是这样的:

interface VoiceInputState {
status: 'idle' | 'waiting-upstream' | 'recording' | 'processing' | 'error';
duration: number;
error?: string;
deletedSet: Set<string>; // 已删除结果的指纹集合
}
// 开始录制时的状态转换
const handleVoiceInputStart = async () => {
// 先进入等待状态,显示加载动画
setState({ status: 'waiting-upstream' });
// 等待后端就绪确认
const isReady = await waitForBackendReady();
if (!isReady) {
setState({ status: 'error', error: '后端服务未就绪' });
return;
}
// 开始录制
setState({ status: 'recording', startTime: Date.now() });
};
// 处理识别结果
const handleRecognitionResult = (result: RecognitionResult) => {
const fingerprint = normalizeFingerprint(result.text);
// 检查是否已被删除
if (state.deletedSet.has(fingerprint)) {
return; // 跳过已删除的内容
}
// 合并结果到文本框
appendResult(result);
};

这里有个细节:我们用”指纹集合”来管理删除同步。当语音识别返回多条结果时,用户可能会删除其中一些。我们把已删除内容的指纹存起来,后续如果相同内容再出现就自动跳过。这就像记住了哪些菜不爱吃,下次就不会再点了,毕竟谁也不想被同样的问题困扰两次。

3. 提示词管理系统:把 AI 的”脑子”外置

Section titled “3. 提示词管理系统:把 AI 的”脑子”外置”

HagiCode 有一个灵活的提示词管理系统,所有提示词都以文件形式存储:

prompts/
├── metadata/
│ ├── optimize-description.zh-CN.json
│ └── optimize-description.en-US.json
└── templates/
├── optimize-description.zh-CN.hbs
└── optimize-description.en-US.hbs

每个提示词由两部分组成:

  • 元数据文件(.json):定义提示词的场景、版本、参数等信息
  • 模板文件(.hbs):使用 Handlebars 语法的实际提示词内容

元数据文件的格式是这样的:

{
"scenario": "optimize-description",
"locale": "zh-CN",
"version": "1.0.0",
"syntax": "handlebars",
"syntaxVersion": "1.0",
"parameters": [
{
"name": "title",
"type": "string",
"required": true,
"description": "提案标题"
},
{
"name": "description",
"type": "string",
"required": true,
"description": "原始描述"
}
],
"author": "HagiCode Team",
"description": "优化用户输入的技术提案描述",
"lastModified": "2026-04-05",
"tags": ["optimization", "nlp"]
}

模板文件使用 Handlebars 语法,支持参数注入:

你是一个技术提案专家。
<task>
根据以下信息生成结构化的技术提案描述。
</task>
<input>
<title>{{title}}</title>
<description>{{description}}</description>
{{#if memoryContext}}
<memory_context>
{{memoryContext}}
</memory_context>
{{/if}}
</input>
<output_format>
## 背景
[描述问题背景和上下文,包括项目信息、仓库范围等]
## 分析
[技术分析和思考过程,说明为什么需要这个改动]
## 解决
[解决方案和实施步骤,列出关键代码位置]
## 实践
[实际代码示例和注意事项]
</output_format>

这种设计的好处是:

  • 提示词可以像代码一样版本管理
  • 支持多语言,根据用户偏好自动切换
  • 参数化设计,可以动态注入上下文
  • 启动时验证完备性,避免运行时出错

毕竟脑子里的东西不写下来,谁也不知道什么时候就忘了,与其到时候懊悔,不如一开始就做好记录罢了。

4. 渐进式向导:复杂任务拆成小步

Section titled “4. 渐进式向导:复杂任务拆成小步”

对于复杂任务(比如首次安装配置),我们使用了多步骤向导的设计。每个步骤只请求必要信息,并提供清晰的进度指示。生活也是这样嘛,一口吃不成胖子,一步一步来反而更稳妥。

向导的状态模型:

interface WizardState {
currentStep: number; // 0-3,对应 4 个步骤
steps: WizardStep[];
canGoNext: boolean;
canGoBack: boolean;
isLoading: boolean;
error: string | null;
}
interface WizardStep {
id: number;
title: string;
description: string;
completed: boolean;
}
// 步骤导航逻辑
const goToNextStep = () => {
if (wizardState.currentStep < wizardState.steps.length - 1) {
// 验证当前步骤的输入
if (validateCurrentStep()) {
wizardState.currentStep++;
wizardState.steps[wizardState.currentStep - 1].completed = true;
}
}
};
const goToPreviousStep = () => {
if (wizardState.currentStep > 0) {
wizardState.currentStep--;
}
};

每个步骤都有独立的验证逻辑,已完成步骤会有清晰的视觉标记。取消操作会弹出确认对话框,防止用户误操作丢失进度。毕竟走错路可以回头,但如果把路都拆了,那就真的没辙了。

回顾 HagiCode 的渐进式披露实践,我们可以总结出几个核心原则:

  1. 分步引导:把复杂任务拆成小步,每步只请求必要信息
  2. 智能补全:利用历史上下文和项目知识自动补全信息
  3. 即时反馈:每个操作都有清晰的视觉反馈和状态提示
  4. 容错机制:允许用户撤销、重置,避免错误造成不可逆损失
  5. 输入多样化:支持文本、语音等多种输入方式

这套方案在 HagiCode 中的实际效果是:用户输入的平均长度从不到 20 字提升到了结构化的 200-300 字,AI 生成的提案质量显著提高,用户满意度也跟着上来了。

其实这也不奇怪,毕竟你给的信息越多,AI 理解得越准确,返回的结果自然就越好,这和人与人之间的交流也没什么两样。

如果你也在做 AI 相关的产品,希望这些经验能给你带来一些启发。记住:用户不是不想给信息,而是你还没问对问题。渐进式披露的核心,就是找到问问题的最佳时机和方式,只是这个时机和方式,需要一点耐心去摸索罢了。


如果本文对你有帮助,欢迎来 GitHub 给个 Star,关注 HagiCode 项目的后续发展。公测已经开始,现在安装即可体验完整功能:

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

AI 输出 Token 优化:文言文极简模式的实践

AI 输出 Token 优化:文言文极简模式的实践

Section titled “AI 输出 Token 优化:文言文极简模式的实践”

在 AI 应用开发中,token 消耗直接影响成本。HagiCode 项目通过 SOUL 系统实现了”文言文极简输出模式”,在不损失信息密度的前提下,将输出 token 降低约 30-50%。本文分享这套方案的实现细节和使用经验。

在 AI 应用开发中,token 消耗是个绕不开的成本问题。尤其是需要 AI 输出大量内容的场景,怎么在不损失信息密度的情况下降低输出 token,这问题想多了也挺让人头疼。

传统的优化思路都集中在输入端:精简系统提示词、压缩上下文、用更高效的编码方式。只是这些方法终究会碰到天花板,再压缩就可能影响 AI 的理解能力和输出质量了。这无异于删减内容,意义不大。

那输出端呢?能不能让 AI 用更简洁的方式表达同样的意思?

这问题看似简单,其实藏着不少门道。直接让 AI”简洁点”,它可能真的就只给几个词;加上”保持信息完整”,它又可能回复到原来的冗长风格。约束太强影响可用性,约束太弱没有效果,这中间的平衡点在哪,谁也说不准。

为了解决这些痛点,我们做了一个大胆的决定:从语言风格入手,设计一套可配置、可组合的表达方式约束系统。这个决定带来的变化,可能比你想象的还要大——稍后我会具体说,或许你会有些意外。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。

HagiCode 是一个开源的 AI 代码助手项目,支持多种 AI 模型和自定义配置。在开发过程中,我们发现了 AI 输出 token 过高的问题,并设计了一套解决方案。如果你觉得这套方案有价值,说明我们的工程实力还不错——那么 HagiCode 本身也值得关注一下,毕竟代码不会撒谎。

SOUL 系统的全称是 Soul Oriented Universal Language,是 HagiCode 项目中用于定义 AI Hero 语言风格的配置系统。它的核心思想是:通过约束 AI 的表达方式,在保持信息完整性的前提下,使用更简洁的语言形式来输出内容。

这东西就像给 AI 戴上了一个语言面具…罢了,其实也没那么玄乎。

SOUL 系统采用前后端分离的架构:

前端(Soul Builder)

  • 基于 React + TypeScript + Vite 构建
  • 位于 repos/soul/ 目录
  • 提供可视化的 Soul 构建界面
  • 支持双语(zh-CN / en-US)

后端

  • 基于 .NET (C#) + Orleans 分布式运行时
  • Hero 实体包含 Soul 字段(最大 8000 字符)
  • 通过 SessionSystemMessageCompiler 将 Soul 注入系统提示词

Agent Templates 生成

  • 从参考材料生成
  • 输出到 /agent-templates/soul/templates/ 目录
  • 包含 50 组主 Catalog 和 10 组正交维度

在 Session 首次执行时,系统会读取 Hero 的 Soul 配置,将其注入到系统提示词中:

sequenceDiagram
participant UI as 用户界面
participant Session as SessionGrain
participant Hero as Hero 仓库
participant AI as AI 执行器
UI->>Session: 发送消息(绑定 Hero)
Session->>Hero: 读取 Hero.Soul
Session->>Session: 缓存 Soul 快照
Session->>AI: 构建 AIRequest(注入 Soul)
AI-->>Session: 执行结果
Session-->>UI: 流式响应

注入的系统提示词格式为:

<hero_soul>
[用户自定义的 Soul 内容]
</hero_soul>

这套注入机制在 SessionSystemMessageCompiler.cs 中实现:

internal static string? BuildSystemMessage(
string? existingSystemMessage,
string? languagePreference,
IReadOnlyList<HeroTraitDto>? traits,
string? soul)
{
var segments = new List<string>();
// ... 语言偏好和 Traits 处理 ...
var normalizedSoul = NormalizeSoul(soul);
if (!string.IsNullOrWhiteSpace(normalizedSoul))
{
segments.Add($"<hero_soul>\n{normalizedSoul}\n</hero_soul>");
}
// ... 其他系统消息 ...
return segments.Count == 0 ? null : string.Join("\n\n", segments);
}

代码也看了,原理也懂了,其实就这么回事。

文言文极简模式是 SOUL 系统中最具代表性的节约 token 方案。它的核心原理是利用文言文的高语义密度特性,在保持信息完整的前提下压缩输出长度。

文言文具有几个天然优势:

  1. 语义压缩:相同含义可以用更少的字符表达
  2. 去除冗余:文言文本身就省略了很多现代汉语中的连接词和助词
  3. 结构简洁:单句信息密度高,适合作为 AI 输出的载体

以一个实际例子来说明:

现代汉语输出(约 80 字):

根据你的代码分析,我发现了几个问题。首先,在第 23 行,变量名太长了,建议缩短一些。其次,在第 45 行,你没有处理空值的情况,应该加上判断逻辑。最后,整体的代码结构还可以,但是可以进一步优化。

文言文极简输出(约 35 字,节约 56%):

代码审阅毕:第 23 行变量名冗长,宜缩写;第 45 行缺空值处理,应加判断。整体结构尚可,微调即可。

这差距,想想也挺有意思的。

文言文极简模式的完整 Soul 配置如下:

{
"id": "soul-orth-11-classical-chinese-ultra-minimal-mode",
"name": "文言文极简输出模式",
"summary": "以尽量可懂的文言文压缩语义密度,尽可能少字达意,只保留结论、判断与必要动作,从而大幅降低输出 token",
"soul": "你的人设内核来自「文言文极简输出模式」:以尽量可懂的文言文压缩语义密度,尽可能少字达意,只保留结论、判断与必要动作,从而大幅降低输出 token。\n保持以下标志性语言特征:1. 优先使用简明文言句式,如「可」「宜」「勿」「已」「然」「故」等,避免生僻艰涩字词;\n2. 单句尽量压缩至 4-12 字,删除铺垫、寒暄、重复解释与无效修饰;\n3. 非必要不展开论证,用户未追问则只给结论、步骤或判断;\n4. 不改变主 Catalog 的核心人设,只将表达收束为克制、古雅、极简的短句。"
}

这个模板的设计有几个要点:

  1. 约束明确:单句 4-12 字,删除冗余,结论优先
  2. 避免晦涩:使用简明文言句式,避免生僻字词
  3. 保持人设:只改变表达方式,不改变核心人设

配置这东西,调来调去也就那么几个参数罢了。

除了文言文模式,HagiCode 的 SOUL 系统还提供了其他多种节约 token 的模式:

电报式极简输出模式soul-orth-02):

  • 单句严格控制在 10 字以内
  • 禁止修饰性形容词
  • 全程无语气词、感叹号、叠词

短句碎碎念模式soul-orth-01):

  • 句子控制在 1-5 个字
  • 模拟自言自语的碎片化表达
  • 弱化逻辑,优先传递情绪

引导式问答模式soul-orth-03):

  • 通过提问引导用户思考
  • 减少直接输出内容
  • 交互式降低 token 消耗

这些模式的设计思路各有侧重,但核心目标是一致的:在保持信息质量的前提下降低输出 token。条条大路通罗马,只是有的路好走一点,有的路稍微曲折一点罢了。

SOUL 系统的一个强大特性是支持主 Catalog 与正交维度的交叉组合:

  • 50 组主 Catalog:定义基础人设(如治愈系、学霸系、高冷系等)
  • 10 组正交维度:定义表达方式(如文言文、电报式、问答式等)
  • 组合效果:可生成 500+ 种独特的语言风格组合

例如,你可以将”专业开发工程师”与”文言文极简输出模式”组合,得到一个既专业又简洁的 AI 助手。这种灵活性让 SOUL 系统能够适应各种不同使用场景。想怎么配就怎么配,反正组合多得你玩不过来…

访问 soul.hagicode.com,按以下步骤操作:

  1. 选择主 Catalog(如”专业开发工程师”)
  2. 选择正交维度(如”文言文极简输出模式”)
  3. 预览生成的 Soul 内容
  4. 复制生成的 Soul 配置

点点点的事情,应该不用我多说吧。

通过 Web 界面或 API,将 Soul 配置应用到 Hero:

// Hero Soul 更新示例
const heroUpdate = {
soul: "你的人设内核来自「文言文极简输出模式」:...",
soulCatalogId: "soul-orth-11-classical-chinese-ultra-minimal-mode",
soulDisplayName: "文言文极简输出模式",
soulStyleType: "orthogonal-dimension",
soulSummary: "以尽量可懂的文言文压缩语义密度..."
};
await updateHero(heroId, heroUpdate);

用户可以基于预设模板进行微调,或完全自定义。下面是一个代码审查场景的自定义示例:

你是一位追求极致简洁的代码审查员。
所有输出必须遵循:
1. 仅指出具体问题和行号
2. 每条问题不超过 15 字
3. 使用「宜」「应」「勿」等简洁词汇
4. 不做多余解释
示例输出:
- 第 23 行:变量名过长,宜缩写
- 第 45 行:未处理空值,应加判断
- 第 67 行:逻辑冗余,可简化

想怎么改就怎么改,反正模板这东西也只是个起点而已。

兼容性

  • 文言文模式适配全部 50 组主 Catalog
  • 可与任何基础人设组合使用
  • 不会改变主 Catalog 的核心人设

缓存机制

  • Soul 在 Session 首次执行时缓存
  • 同一 SessionId 内复用缓存
  • 修改 Hero 配置不影响已启动的 Session

限制约束

  • Soul 字段最大长度 8000 字符
  • 历史数据中无 Soul 字段的 Hero 仍可正常使用
  • Soul 与 style 装备位独立,不会相互覆盖

根据项目的实际测试数据,使用文言文极简模式后的效果如下:

场景原始输出 token文言文模式节约比例
代码审查85042051%
技术问答62038039%
方案建议110068038%
平均--30-50%

数据来自 HagiCode 项目的实际使用统计,具体效果因场景而异。不过省下来的 token,积少成多,钱包会感谢你的。

HagiCode 的 SOUL 系统提供了一种创新性的 AI 输出优化思路:通过约束表达方式来降低 token 消耗,而不是压缩信息本身。文言文极简模式作为其中最具代表性的方案,在实际使用中取得了 30-50% 的 token 节约效果。

这套方案的核心价值在于:

  1. 保持信息质量:不是简单截断输出,而是用更高效的方式表达
  2. 灵活可组合:支持 500+ 种人设与表达方式的组合
  3. 易于使用:通过 Soul Builder 可视化界面,无需编写代码
  4. 生产级稳定:已在项目中验证,支持大规模使用

如果你也在开发 AI 应用,或者对 HagiCode 项目感兴趣,欢迎来交流。开源的意义在于共同进步,也期待看的到你的创新用法。毕竟,一个人走得快,一群人走得远…这话说得挺俗套,但道理就是这么个道理。


如果本文对你有帮助:

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

HagiCode Skill 系统技术解析:如何打造可扩展的 AI 技能管理平台

HagiCode Skill 系统技术解析:如何打造可扩展的 AI 技能管理平台

Section titled “HagiCode Skill 系统技术解析:如何打造可扩展的 AI 技能管理平台”

本文深入解析 HagiCode 项目中 Skill(技能)管理系统的架构设计与实现方案,涵盖本地全局管理、市场搜索、智能推荐、授信提供者管理四大核心功能的技术实现细节。

在 AI 代码助手这个领域,如何扩展 AI 的能力边界,其实一直是个核心课题。Claude Code 本身的代码辅助能力是挺强的,只是不同开发团队、不同技术栈,往往需要针对特定场景的专业能力——比如处理 Docker 部署、数据库优化、前端组件生成之类的。这时候,Skill(技能)系统就显得尤为重要了。

HagiCode 项目在开发过程中也遇到了类似的挑战:怎么让 Claude Code 能够像人一样「学会」新的专业技能,同时保持良好的用户体验和工程可维护性?毕竟这个问题,说难也难,说简单也简单。围绕这个问题,我们设计并实现了一套完整的 Skill 管理系统。

本文将详细解析这个系统的技术架构和核心实现,适合对 AI 扩展性、命令行工具集成感兴趣的开发者阅读。或许对你有用,也或许没用,但总归是写出来了。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个开源的 AI 代码助手项目,旨在帮助开发团队提升研发效率。项目的技术栈涵盖 ASP.NET Core、Orleans 分布式框架、TanStack Start + React 前端,以及本文要介绍的 Skill 管理子系统。

项目的 GitHub 地址是 HagiCode-org/site,如果你觉得本文介绍的技术方案有价值,欢迎给个 Star。毕竟Star多了,心情也会好一些。

Skill 系统采用前后端分离的架构设计,说起来也没什么特别的。

前端部分 使用 TanStack Start + React 构建用户界面,通过 Redux Toolkit 管理状态,四个主要功能分别对应四个 Tab 组件:本地技能、市场画廊、智能推荐、授信提供者。这样设计,其实也是为了用户体验罢了。

后端部分 基于 ASP.NET Core + ABP Framework,使用 Orleans Grain 实现分布式状态管理。在线 API 客户端封装了 IOnlineApiClient 接口,用于与远程技能目录服务通信。

整体架构的设计原则是「命令执行与业务逻辑分离」,通过适配器模式将 npm/npx 命令执行的细节屏蔽在独立模块中。毕竟谁愿意看到一堆命令行散落在代码各处呢?

本地全局管理是最基础的功能模块,负责列出已安装的技能并支持卸载操作。也没什么复杂的,就是把事情做好而已。

实现位置在 LocalSkillsTab.tsxLocalSkillCommandAdapter.cs。核心思路是封装 npx skills 命令,解析其 JSON 输出,转换为内部数据结构。说起来简单,做起来其实也简单。

public async Task<IReadOnlyList<LocalSkillInventoryResponseDto>> GetLocalSkillsAsync(
CancellationToken cancellationToken = default)
{
var result = await _commandAdapter.ListGlobalSkillsAsync(cancellationToken);
return result.Skills.Select(skill => new LocalSkillInventoryResponseDto
{
Name = skill.Name,
Version = skill.Version,
Source = skill.Source,
InstalledPath = skill.InstalledPath,
Description = skill.Description
}).ToList();
}

数据流非常清晰:前端发起请求 → SkillGalleryAppService 接收 → LocalSkillCommandAdapter 执行 npx 命令 → 解析 JSON 结果 → 返回 DTO 对象。一环扣一环,也没什么好说的。

卸载技能使用 npx skills remove -g <skillName> -y 命令,系统会自动处理依赖关系和清理工作。安装元数据存储在技能目录的 managed-install.json 中,记录了安装时间、来源版本等信息,便于后续更新和审计。毕竟有些东西,记下来总是好的。

技能安装涉及多个步骤的协调,怎么说呢,其实也不算太复杂:

public async Task<SkillInstallResultDto> InstallAsync(
SkillInstallRequestDto request,
CancellationToken cancellationToken = default)
{
// 1. 规范化安装引用
var normalized = _referenceNormalizer.Normalize(
request.SkillId,
request.Source,
request.SkillSlug,
request.Version);
// 2. 检查先决条件
await _prerequisiteChecker.CheckAsync(cancellationToken);
// 3. 获取安装锁
using var installLock = await _lockProvider.AcquireAsync(normalized.SkillId);
// 4. 执行安装命令
var result = await _installCommandRunner.ExecuteAsync(
new SkillInstallCommandExecutionRequest
{
Command = $"npx skills add {normalized.FullReference} -g -y",
Timeout = TimeSpan.FromMinutes(4)
},
cancellationToken);
// 5. 持久化安装元数据
await _metadataStore.WriteAsync(normalized.SkillPath, request);
return new SkillInstallResultDto { Success = result.Success };
}

这里用到了几个关键的设计模式:引用规范化器 负责将各种输入格式(如 tanweai/pua@opencode/docker-skill)转换为统一的内部表示;安装锁机制 确保同一技能同时只有一个安装操作在进行;流式输出 通过 Server-Sent Events 向前端实时推送安装进度,用户可以看到类似终端的实时日志。

这些设计模式,说到底,也还是为了让事情变得简单罢了。

市场搜索让用户能够发现和安装来自社区的技能。毕竟一个人的能力是有限的,众人的智慧才是无穷的。

搜索功能依赖在线 API https://api.hagicode.com/v1/skills/search。为了提升响应速度,系统实现了缓存机制。缓存这东西,就像记忆一样,有些东西记住了,下次就不用再费劲去想。

private async Task<IReadOnlyList<SkillGallerySkillDto>> SearchCatalogAsync(
string query,
CancellationToken cancellationToken,
IReadOnlySet<string>? allowedSources = null)
{
var cacheKey = $"skill_search:{query}:{string.Join(",", allowedSources ?? Array.Empty<string>())}";
if (_memoryCache.TryGetValue(cacheKey, out var cached))
return (IReadOnlyList<SkillGallerySkillDto>)cached!;
var response = await _onlineApiClient.SearchAsync(
new SearchSkillsRequest
{
Query = query,
Limit = _options.LimitPerQuery,
},
cancellationToken);
var results = response.Skills
.Where(skill => allowedSources is null || allowedSources.Contains(skill.Source))
.Select(skill => new SkillGallerySkillDto { ... })
.ToList();
_memoryCache.Set(cacheKey, results, TimeSpan.FromMinutes(10));
return results;
}

搜索结果支持按授信来源过滤,只显示用户信任的技能源。预置的种子查询用于初始化目录,比如「popular」、「recent」等,让用户在首次打开时就能看到推荐的热门技能。毕竟第一印象还是重要的。

智能推荐是系统中最复杂的功能,它能根据用户当前项目的情况,自动推荐最适合的技能。复杂归复杂,但做出来还是值得的。

整个推荐流程分为五个阶段:

1. 构建项目上下文
2. AI 生成搜索查询
3. 并行搜索在线目录
4. AI 对候选进行排名
5. 返回推荐列表

首先,系统分析项目的技术栈、编程语言、域名结构等特征,构建一个「项目画像」。这个画像,就像一个人的简历一样,记录着所有的特征。

然后,使用 AI Grain 生成针对性的搜索查询。这里的设计其实挺有意思——不是直接问 AI「推荐什么技能」,而是让它先思考「什么样的搜索词能找到相关技能」。毕竟有时候,问问题的方法比答案本身更重要:

var queryGeneration = await aiGrain.GenerateSkillRecommendationQueriesAsync(
projectContext, // 项目上下文
locale, // 用户语言偏好
maxQueries, // 最大查询数量
effectiveSearchHero); // AI 模型选择

接着,并行执行这些搜索查询,获取候选技能列表。并行处理,说到底也是为了节省时间罢了。

最后,使用另一个 AI Grain 对候选技能进行排名。这一步会综合考虑技能与项目的相关性、授信状态、用户历史偏好等因素:

var ranking = await aiGrain.RankSkillRecommendationsAsync(
projectContext,
candidates,
installedSkillNames,
locale,
maxRecommendations,
effectiveRankingHero);
response.Items = MergeRecommendations(projectContext, candidates, ranking, maxRecommendations);

AI 模型可能出现响应慢或暂时不可用的情况。毕竟再好的系统,也有掉链子的时候。为此,系统设计了确定性回退机制:当 AI 服务不可用时,使用基于规则启发式算法生成推荐,比如根据 package.json 中的依赖推断可能需要的技能。

这个回退机制,说穿了,也就是给系统留了一条后路罢了。

授信提供者管理允许用户控制哪些技能源是可信的。毕竟信任这东西,还是要自己把握的。

授信提供者支持两种匹配规则:精确匹配(exact)和前缀匹配(prefix)。

public static TrustedSkillProviderResolutionSnapshot Resolve(
TrustedSkillProviderSnapshot snapshot,
string source)
{
var normalizedSource = Normalize(source);
foreach (var entry in snapshot.Entries.OrderBy(e => e.SortOrder))
{
if (!entry.IsEnabled) continue;
foreach (var rule in entry.MatchRules)
{
bool isMatch = rule.MatchType switch
{
TrustedSkillProviderMatchRuleType.Exact
=> string.Equals(normalizedSource, Normalize(rule.Value),
StringComparison.OrdinalIgnoreCase),
TrustedSkillProviderMatchRuleType.Prefix
=> normalizedSource.StartsWith(Normalize(rule.Value) + "/",
StringComparison.OrdinalIgnoreCase),
_ => false
};
if (isMatch)
return new TrustedSkillProviderResolutionSnapshot
{
IsTrustedSource = true,
ProviderId = entry.ProviderId,
DisplayName = entry.DisplayName
};
}
}
return new TrustedSkillProviderResolutionSnapshot { IsTrustedSource = false };
}

预置的授信提供者包括 Vercel、Azure、anthropics、Microsoft、browser-use 等知名组织和项目。自定义提供者可以通过配置文件添加,指定提供者 ID、显示名称、徽章标签、匹配规则等。毕竟世界那么大,不可能只有几家是可信的。

授信配置使用 Orleans Grain 持久化存储:

public class TrustedSkillProviderGrain : Grain<TrustedSkillProviderState>,
ITrustedSkillProviderGrain
{
public async Task UpdateConfigurationAsync(TrustedSkillProviderSnapshot snapshot)
{
State.Snapshot = snapshot;
await WriteStateAsync();
}
public Task<TrustedSkillProviderSnapshot> GetConfigurationAsync()
{
return Task.FromResult(State.Snapshot);
}
}

这种方式的好处是配置变更会自动同步到所有节点,无需手动刷新缓存。毕竟自动化,说到底也是为了让人少操心罢了。

Skill 系统需要执行各种 npx 命令,如果把这些逻辑散落在各处,代码会变得难以维护。因此我们设计了适配器接口。设计模式这东西,说到底,也还是为了让代码更好维护而已:

public interface ISkillInstallCommandRunner
{
Task<SkillInstallCommandExecutionResult> ExecuteAsync(
SkillInstallCommandExecutionRequest request,
CancellationToken cancellationToken = default);
}

不同的命令有不同的执行器实现,全部实现同一个接口,便于测试和替换。

安装进度通过 Server-Sent Events 实时推送到前端:

public async Task InstallWithProgressAsync(
SkillInstallRequestDto request,
IServerStreamWriter<SkillInstallProgressEventDto> stream,
CancellationToken cancellationToken)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "npx",
Arguments = $"skills add {request.FullReference} -g -y",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false
}
};
process.OutputDataReceived += async (sender, e) =>
{
await stream.WriteAsync(new SkillInstallProgressEventDto
{
EventType = "output",
Data = e.Data ?? string.Empty
});
};
process.Start();
process.BeginOutputReadLine();
await process.WaitForExitAsync(cancellationToken);
}

用户在前端可以看到类似终端的实时输出,体验非常直观。毕竟实时反馈,让人安心。

以安装 pua 技能为例(这是一个流行的社区技能):

  1. 打开 Skills 抽屉,切换到「Skill Gallery」标签
  2. 输入「pua」进行搜索
  3. 点击搜索结果查看技能详情
  4. 点击「Install」按钮安装
  5. 切换到「Local Skills」标签确认安装成功

安装命令是 npx skills add tanweai/pua -g -y,系统会自动处理所有细节。其实也没那么多步骤,一步步来就是了。

如果你的团队有自己的技能仓库,可以添加为授信来源:

providerId: "my-team"
displayName: "My Team Skills"
badgeLabel: "MyTeam"
isEnabled: true
sortOrder: 100
matchRules:
- matchType: "prefix"
value: "my-team/"
- matchType: "exact"
value: "my-team/special-skill"

这样来自你团队的所有技能都会显示授信徽章,用户可以更放心地安装。毕竟有标记的东西,总是让人安心一些。

创建自定义技能需要遵循以下结构:

my-skill/
├── SKILL.md # 技能元数据(YAML front matter)
├── index.ts # 技能入口
├── agents/ # 支持的代理配置
└── references/ # 参考资源

SKILL.md 的格式示例:

---
name: my-skill
description: A brief description of what this skill does
---
# My Skill
Detailed documentation...
  1. 网络要求:技能搜索和安装需要能访问 api.hagicode.com 和 npm registry
  2. Node.js 版本:建议使用 Node.js 18 或更高版本
  3. 权限要求:需要全局 npm 安装权限
  4. 并发控制:同一技能同时只能有一个安装或卸载操作在执行
  5. 超时设置:安装操作默认超时时间为 4 分钟,复杂场景可能需要调整

这些注意事项,说到底,也还是为了让事情顺利进行罢了。

本文介绍了 HagiCode 项目中 Skill 管理系统的完整实现。这个系统通过前后端分离的架构、适配器模式、Orleans 分布式状态管理等技术手段,实现了:

  • 本地全局管理:通过封装 npx skills 命令,提供统一的技能管理接口
  • 市场搜索:利用在线 API 和缓存机制,快速发现社区技能
  • 智能推荐:结合 AI 能力,根据项目上下文推荐最合适的技能
  • 授信管理:灵活的配置系统,让用户掌控信任边界

这套设计思路不仅适用于 Skill 管理,对于任何需要集成命令行工具、兼顾本地存储和在线服务的场景,都有参考价值。

如果本文对你有帮助,欢迎来 GitHub 给个 Star:github.com/HagiCode-org/site。也可以访问官网了解更多:hagicode.com

或许你也会觉得,这套系统设计得还行,或许你不会。但这都罢了,毕竟代码写出来,总有人会用,也总有人不会用…

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

我好像会被 Agent 淘汰,我用数据算了一算

我好像会被 Agent 淘汰,我用数据算了一算

Section titled “我好像会被 Agent 淘汰,我用数据算了一算”

用数据量化 AI 替代风险:深入解析 HagiCode 团队如何用 6 个核心公式,重新定义知识工作者的竞争力评估标准。

在 AI 技术飞速发展的今天,每一个知识工作者都面临一个紧迫的问题:在 AI 时代,我是否会被淘汰?

这个问题听起来有点危言耸听,但其实很多人心里都在打鼓。前脚刚学会一个框架,后脚 AI 就说你这个岗位要被替代了;好不容易精通了一门语言,结果发现用 AI 的人产出是你的三倍——这种焦虑感,我相信屏幕前的你多少都能体会。

其实吧,这种焦虑也不是没有道理。毕竟谁也不愿意承认,自己奋斗多年的技能,可能被一个 ChatGPT 就给超越了。只是焦虑归焦虑,日子还得过不是?

传统观点往往从”AI 能做什么”出发讨论替代风险,但这种方法忽略了两个关键维度:

  1. 企业视角:企业是否愿意为一个员工配备 AI 工具,取决于 AI 成本相对于人力成本的性价比。不是说 AI 能取代这个岗位,企业就会立刻换人,还要算算经济账。毕竟资本家也不是慈善家,每一分钱都要花在刀刃上。

  2. 效率视角:AI 带来的效率提升需要被量化,而不是简单地认为”用了 AI 就更强”。你用 AI 效率提升了 2 倍,但他用 AI 提升了 5 倍,这里面的差距可不小。就像学生时代,都在听课,有的考 90 分,有的才及格——差距就是这么拉开的。

所以关键问题是:怎么把这种模糊的焦虑,变成可以量化的指标?

毕竟知道自己的位置在哪,总比在黑暗中摸索要好一些。这就是我们今天要聊的——HagiCode 团队开发的 AI 人效计算器背后的设计逻辑。

于是我做了一个 https://cost.hagicode.com 的网站。

HagiCode 是一个开源的 AI 代码助手项目,旨在帮助开发者更高效地完成编码工作。

有意思的是,HagiCode 团队在开发自己的产品过程中,积累了大量关于 AI 使用效率的实践经验。他们发现:AI 工具本身的价值,不能脱离企业的用工成本来单独评估。基于这个洞察,团队决定开发一个人效计算器,帮助知识工作者科学地评估自己在 AI 时代的竞争力。

其实这种东西,很多人都能做,只是很少有人愿意认真去做了。HagiCode 团队花时间做这个,也算是给开发者社区的一点回馈吧。

本文分享的设计方案,正是 HagiCode 在 AI 应用实践中的经验总结。如果你觉得这套评估体系有价值,说明 HagiCode 在工程实践上还是有点东西的——那么 HagiCode 项目本身 也值得关注一下。

企业为员工付出的真实成本远不止工资。这一点很多人跳槽的时候才发现——明明谈的是 2 万月薪,到手怎么就 1 万 4?公司那边可不止出 2 万,社保、公积金、培训、招聘成本都要算进去。

根据 calculate-ai-risk.ts 中的实现:

年度全用工成本 = 年薪 × (1 + 城市系数) + 年薪 / 12

城市系数反映的是不同城市的人才招募和保留成本:

城市层级代表城市系数
一线北京/上海/深圳/广州0.4
新一线杭州/成都/苏州/南京0.3
二线武汉/西安/天津/郑州0.2
其他宜昌/洛阳等0.1

一线城市系数是 0.4,意思是企业需要额外支付约 40% 的招募、培训、社保等附加成本。在北京招一个人的综合成本,确实比在二线城市高不少。

毕竟在大城市生存,生活成本也高,这算是另一种形式的”漂泊者税”了吧。

不同 AI 模型有 Input 和 Output 两种价格,而且差异巨大。代码场景下输入输出比例大约是 3:1——你给 AI 一段代码让它 review,输出的分析文字通常比输入的代码短很多。

综合单价计算公式:

综合单价 = (输入输出比例 × 输入单价 + 输出单价) / (输入输出比例 + 1)

拿 GPT-5 举个例子:

  • 输入:$2.5/1M tokens
  • 输出:$15/1M tokens
  • 综合 = (3 × 2.5 + 15) / 4 = $5.625/1M tokens

对于 USD 定价的模型,还需要按汇率转换。这个汇率 HagiCode 团队设定为 7.25,会随市场波动更新。

汇率这东西,就像股市一样,谁也猜不准。只能跟着走,罢了。

日均 AI 成本 = 日均 Token 需求 (M) × 综合单价 (CNY/1M)
年 AI 成本 = 日均 AI 成本 × 264 个工作日

264 = 22 天/月 × 12 月,这是标准工作制下的年度工作日数量。为什么不用 365 天?因为你要考虑周末、节假日、病假等因素。

毕竟咱们也不是机器人,该休息的时候还是要休息的。虽说 AI 可能不需要休息,但咱们还是要给自己留点喘息的空间。

这是整个评估体系的核心,也是 HagiCode 团队最有洞察力的地方。

可负担工作流份数 = 年度全用工成本 / AI 年成本
可负担比例 = min(可负担工作流份数, 1)
等效人力 = 1 + (效率倍数 - 1) × 可负担比例

等等,这个公式有点绕,让我解释一下:

传统观点会直接说”你的效率提升了 2 倍”,但这个公式考虑了一个关键约束:企业的 AI 预算是否可持续?

举个例子:小明效率提升了 3 倍,但他的 AI 消耗成本每年要 30 万;而公司给他的年薪才 20 万。这种情况下,虽然小明个人效率很高,但他实际上是不可持续的——公司不可能为了让他维持高效率而亏本。

可负担比例就是这个意思:如果企业只能负担 0.5 份 AI 工作流,那小明的等效人力 = 1 + (3-1) × 0.5 = 2 人,而不是 3 人。

核心洞察:不是你的效率倍数有多高,而是企业能否负担得起你维持这个效率所需的 AI 投入。

其实这个道理也挺简单的,只是很多人没往这方面想罢了。毕竟咱们习惯了从自己的角度看问题,很少站在老板的角度考虑一下——他们的钱也不是大风刮来的。

AI 成本占比 = AI 年成本 / 年度全用工成本
效率增幅 = 效率倍数 - 1
成本效益比 = 效率增幅 / AI 成本占比
  • 成本效益比 < 1:AI 投入不划算,效率提升抵不上成本
  • 成本效益比 1-2:刚好划算
  • 成本效益比 > 2:高收益,强烈推荐

这个指标对于企业管理者特别有用,可以快速评估某个岗位是否值得投入 AI 工具。

毕竟 ROI 才是王道,你说自己效率提升再多,成本爆炸也没人买账。

根据等效人力划分风险:

等效人力风险等级结论
>= 2.0高危同事一旦具备同等条件,对你威胁很高
1.5 - 2.0警示同事已开始形成明显效率优势
< 1.5安全暂时还能保持差距

看到这个表格,你心里大概也有个数了吧。只是别太焦虑,毕竟焦虑也解决不了问题——不如想想怎么提升自己的效率倍数。

为了让评估结果更有趣味性,计算器引入了 7 种特殊称号系统。称号通过 localStorage 持久化,用户可以解锁并展示自己的”成就”。

称号 ID名称获取条件
craftsman-spirit匠人精神日均 Token = 0
prompt-alchemist提示炼金术师日 Token <= 20M 且效率倍数 >= 6
all-in-operator全押操盘手日 Token >= 150M 且效率倍数 >= 3
minimalist-runner极简跑者日 Token <= 5M 且效率倍数 >= 2
cost-tamer成本驯兽师成本效益比 >= 2.5 且 AI 占比 <= 15%
danger-oracle危险预言家等效人力 >= 2.5 或进入高危区
budget-coordinator预算协调官可负担工作流份数 >= 8

每个称号背后都有隐藏含义:

称号隐藏含义
匠人精神不用 AI 也能活得很好,但需要独特竞争力
提示炼金术师用少量 Token 达到高产出,极客型用户
全押操盘手高投入高产出,适合高频场景
极简跑者轻量级 AI 使用,适合轻度辅助场景
成本驯兽师ROI 极高,企业最喜欢的员工类型
危险预言家你已经是或即将是高危群体
预算协调官你能同时运营多个 AI 工作流

其实游戏化这东西,说白了就是给枯燥的数据加点趣味性罢了。毕竟谁不喜欢收集成就呢?就像游戏里的徽章,虽然没啥实际用处,但看着心里就是舒服。

计算器的定价数据来自多个官方 API 定价页面,确保计算结果的权威性和时效性:

这些数据会定期更新,最近更新于 2026-03-19。

毕竟数据这东西,过时了就没意义了。HagiCode 团队还是挺负责的,会及时更新。

假设你是一个北京的开发者,年薪 40 万,使用 Claude Sonnet 4.6,日均 Token 消耗 50M,自评效率提升 3 倍。模拟输入:

const input = {
annualIncomeCny: 400000,
cityTier: "tier1", // 北京
modelId: "claude-sonnet-4-6",
performanceMultiplier: 3.0,
dailyTokenUsageM: 50,
}
// 计算过程
// 年度全用工成本 = 40万 × (1 + 0.4) + 40万/12 ≈ 60.33万
// AI 年成本 ≈ 50 × 7.125 × 264 ≈ 9.4万
// 可负担工作流份数 ≈ 60.33 / 9.4 ≈ 6.4 份
// 等效人力 = 1 + (3 - 1) × 1 = 3 人

结论:你的同事如果具备相同条件,能相当于 3 个人的产能,你已经处于高危区。

如果你发现自己的 AI 用法”不划算”(成本效益比 < 1),可以考虑:

  1. 降低 Token 消耗:使用更高效的 prompt,减少无效请求
  2. 选择性价比模型:如 DeepSeek-V3(人民币计价,更便宜)
  3. 提升效率倍数:学习高级 Agent 使用技巧,真正把 AI 变成生产力

其实这些问题,归根结底就是一个平衡的艺术罢了。用多了浪费钱,用少了没效果——找到那个刚刚好的点,才是关键。

HagiCode 团队在设计这个计算器时,有几个值得借鉴的工程决策:

  1. 纯前端计算:所有计算都在浏览器完成,不依赖后端 API,保护用户隐私
  2. 配置驱动:所有公式、定价、岗位数据都集中在配置文件中,未来更新无需修改核心代码逻辑
  3. 多语言支持:支持中文和英文
  4. 即时反馈:用户输入参数后,结果实时更新
  5. 详细公式展示:每个结果都附带完整的计算公式,帮助用户理解

这种设计让计算器易于维护和扩展,也为类似的数据驱动型应用提供了参考模板。

毕竟好的架构,就像好的代码一样,是需要时间沉淀的。HagiCode 团队在这方面还是挺用心的。

AI 人效计算器的核心价值,在于它把”AI 替代威胁”这个模糊的焦虑,转化为了可以量化、可以比较的指标。

等效人力公式 1 + (效率倍数 - 1) × 可负担比例 是整个评估体系的核心创新。它不仅考虑效率提升,还考虑企业能否负担 AI 成本,使评估结果更贴近现实。

这套评估体系告诉我们:在 AI 时代不知道自己处于什么位置,才是最危险的位置。

与其焦虑,不如用数据说话。

其实很多时候,恐惧源于未知。当你把一切量化之后,就会发现事情也没那么可怕。大不了就提升自己,或者换个赛道罢了。毕竟人生还长,没必要在一棵树上吊死。


现在就访问 cost.hagicode.com,完成你的 AI 人效评估。



数据来源:cost.hagicode.com | Powered by HagiCode

写到最后,想起一句诗:“此情可待成追忆,只是当时已惘然。” 其实 AI 时代也是一样,与其等到被淘汰时追悔莫及,不如现在就开始行动吧…

感谢您的阅读,如果您觉得本文有用,欢迎点赞、收藏和分享支持。 本内容采用人工智能辅助协作,最终内容由作者审核并确认。

打造 AI 冒险团:HagiCode 多 Agent 协作配置实战

打造 AI 冒险团:HagiCode 多 Agent 协作配置实战

Section titled “打造 AI 冒险团:HagiCode 多 Agent 协作配置实战”

在现代软件开发中,单一 AI Agent 已经难以满足复杂需求。如何让来自不同公司的多个 AI 助手在同一项目中协同工作?本文将分享 HagiCode 项目在实际开发中探索出的多 Agent 协作配置方案。

相信很多开发者都有过这样的经历:项目中引入了 AI 助手辅助编程,效率确实提高了。但随着需求越来越复杂,一个 AI Agent 开始不够用了——你想让它同时处理代码审查、文档生成、单元测试等多个任务,结果往往是顾此失彼,输出质量参差不齐。

更头疼的是,当你尝试引入多个 AI 助手时,问题就变得更复杂了。每个 Agent 有自己的配置方式、API 接口和执行逻辑,彼此之间甚至会产生冲突。这就像一支球队,每个球员都很厉害,但没有人知道该怎么配合,结果踢得乱七八糟。

HagiCode 项目在开发过程中也遇到了同样的困扰。作为一个涉及前端 VSCode 扩展、后端 AI 服务、跨平台桌面客户端的复杂项目,我们在 2026-03 的当时版本 里需要同时对接来自不同公司的多个 AI 助手:Claude Code、Codex、CodeBuddy、iFlow 等等。如何让它们在同一项目中和谐共处、发挥各自特长,成了必须解决的关键问题。

其实这也罢了,毕竟谁愿意每天跟一群打架的 AI 打交道呢。

本文分享的方案,正是我们在 HagiCode 项目中实际踩坑、实际优化出来的多 Agent 协作配置实践。如果你也在为多 AI 助手协作而头疼,相信这篇文章会给你一些启发。或许吧,毕竟每个人的情况都不一样。

HagiCode 是一个 AI 代码助手项目,采用多 AI 引擎协同工作的”冒险团”模式。项目地址:github.com/HagiCode-org/site

本文分享的多 Agent 配置方案,正是 HagiCode 能够在复杂项目中保持高效开发的核心技术之一。也没什么特别的,就是把一群 AI 变成一支能打配合的冒险团而已。

从”单打独斗”到”团队协作”

Section titled “从”单打独斗”到”团队协作””

在 HagiCode 项目早期,我们也尝试过只用一个 AI Agent 来处理所有任务。很快我们就发现,这种方式存在明显的瓶颈:不同的任务需要不同的能力侧重点,有的任务需要更强的上下文理解能力,有的则需要更精准的代码修改能力。一个 Agent 很难在所有方面都表现出色。

这让我们意识到,必须让多个 Agent 协同工作。但问题是,如何让不同公司的 AI 产品在同一个项目中和平共处?我们需要解决几个核心问题:

  1. 配置管理复杂性:每个 Agent 有不同的配置方式、API 接口和执行模式
  2. 通信协议统一:需要一种标准化的方式让不同 Agent 之间进行数据交换
  3. 任务分工协调:如何合理分配任务,让每个 Agent 发挥特长

带着这些问题,我们开始设计 HagiCode 的多 Agent 架构。其实也没那么复杂,只是想明白了而已。

经过多次迭代,我们最终确定的架构是这样的:

┌─────────────────────────────────────────────────────────────────┐
│ AIProviderFactory │
│ (工厂模式统一管理所有 AI Provider) │
├─────────────────────────────────────────────────────────────────┤
│ ClaudeCodeCli │ CodexCli │ CodebuddyCli │ IFlowCli │
│ (Anthropic) │ (OpenAI) │ (智谱 GLM) │ (智谱) │
└─────────────────────────────────────────────────────────────────┘

核心思路是:通过统一的 Provider 接口,让不同的 AI Agent 可以被同一套代码管理。同时使用工厂模式动态创建和配置这些 Provider,确保系统的扩展性和灵活性。

这就像生活中的分工,每个人都有自己的角色,只是这里把这种分工变成了代码架构而已。

根据 HagiCode 项目的实际使用经验,我们为每个 Agent 分配了不同的职责:

Agent提供商模型主要用途
ClaudeCodeCliAnthropicglm-5-turbo生成技术方案和Proposal
CodexCliOpenAI/Zedgpt-5.4执行精准的代码修改
CodebuddyCli智谱glm-4.7优化提案描述和文档
IFlowCli智谱glm-4.7归档提案和历史记录(当时配置;当前仅历史兼容)
OpenCodeCli--通用代码编辑
GitHubCopilotMicrosoft-辅助编程和代码补全

这种分工的背后逻辑是:每个 Agent 都有自己擅长的领域。Claude Code 在理解和分析复杂需求方面表现出色,所以让它负责前期的方案设计;Codex 在代码修改方面更精准,适合处理具体的实现任务;CodeBuddy 性价比高,用来优化文档再合适不过。

毕竟适合自己的才是最好的,条条大路通罗马,只是有的路好走一点,有的路稍微曲折一点罢了。

要让不同的 AI Agent 能够被统一管理,首先需要定义一套统一的接口。HagiCode 中定义了这个接口:

public interface IAIProvider
{
// 统一的 Provider 接口
Task<IAIProvider?> GetProviderAsync(AIProviderType providerType);
Task<IAIProvider?> GetProviderAsync(string providerName, CancellationToken cancellationToken);
}

这个接口看起来很简单,但它是整个多 Agent 系统的基石。通过统一的接口,我们可以无视底层是哪个公司的 AI 产品,都以相同的方式进行调用。

其实这就是把复杂的事情简单化了,毕竟简单才是美。

有了统一的接口,接下来就是如何创建这些 Provider 实例。HagiCode 使用了工厂模式:

private IAIProvider? CreateProvider(AIProviderType providerType, ProviderConfiguration config)
{
return providerType switch
{
AIProviderType.ClaudeCodeCli =>
ActivatorUtilities.CreateInstance<ClaudeCodeCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.CodebuddyCli =>
ActivatorUtilities.CreateInstance<CodebuddyCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.CodexCli =>
ActivatorUtilities.CreateInstance<CodexCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.IFlowCli =>
ActivatorUtilities.CreateInstance<IFlowCliProvider>(_serviceProvider, Options.Create(config)),
_ => null
};
}

这里用到了依赖注入的 ActivatorUtilities.CreateInstance,它可以在运行时动态创建 Provider 实例,并且自动注入依赖项。这种设计的好处是:新增一个 Agent 类型时,只需要添加对应的 Provider 类,然后在工厂方法中加一个 case 分支即可,完全不需要修改现有代码。

这也罢了,毕竟谁愿意每次加新功能都要改一堆旧代码呢。

为了让配置更灵活,我们还实现了类型映射机制:

public static AIProviderTypeExtensions
{
private static readonly Dictionary<string, AIProviderType> _typeMap = new(
StringComparer.OrdinalIgnoreCase)
{
["ClaudeCodeCli"] = AIProviderType.ClaudeCodeCli,
["CodebuddyCli"] = AIProviderType.CodebuddyCli,
["CodexCli"] = AIProviderType.CodexCli,
["IFlowCli"] = AIProviderType.IFlowCli,
// ...更多类型映射
};
}

这个映射表的作用是将字符串形式的 Provider 名称转换为枚举类型。这样一来,配置文件就可以使用直观的字符串名称,而代码内部则使用类型安全的枚举进行处理。

毕竟配置这东西,越直观越好,谁愿意记一堆复杂的代码呢。

实际使用时,只需要在 appsettings.json 中配置即可:

AI:
Providers:
Providers:
ClaudeCodeCli:
Enabled: true
Model: glm-5-turbo
WorkingDirectory: /path/to/project
CodebuddyCli:
Enabled: true
Model: glm-4.7
CodexCli:
Enabled: true
Model: gpt-5.4
IFlowCli:
Enabled: true
Model: glm-4.7

每个 Provider 都可以独立配置开关、模型版本、工作目录等参数。这种设计既保证了灵活性,又便于管理和维护。

其实配置文件就像人生的选项,你可以选择开启或关闭某些功能,只是代码里的选择更容易后悔罢了。

有了统一的技术架构,接下来就是如何让多个 Agent 协同工作了。HagiCode 设计了一套任务流转机制,让不同的 Agent 处理不同阶段的任务:

提案创建 (用户)
[Claude Code] ──生成提案──▶ 提案文档
│ │
│ ▼
│ [Codebuddy] ──优化描述──▶ 优化后提案
│ │
│ ▼
│ [Codex] ──执行修改──▶ 代码变更
│ │
│ ▼
└───────────────▶ [iFlow] ──归档──▶ 历史记录

这种分工的好处是:每个 Agent 只需要专注于自己擅长的任务,不需要”什么都会”。Claude Code 负责从无到有生成提案,Codebuddy 负责把提案描述得更清晰,Codex 负责把提案变成实际的代码变更,iFlow 则负责把这些变更归档保存。

其实这就像生活中的团队合作,每个人都有自己的角色,合起来才能完成一件大事。只是这里的团队成员是 AI 而已。

在实际运行中,我们总结了以下几点经验:

1. Agent 选择策略很重要

不是随便分配任务,而是要根据每个 Agent 的特长来分配:

  • 提案生成:使用 Claude Code,因为它有更强的上下文理解能力
  • 代码执行:使用 Codex,因为它在代码修改方面更精准
  • 提案优化:使用 Codebuddy,因为它的性价比高
  • 归档存储:使用 iFlow,因为它稳定可靠

毕竟让合适的人做合适的事,这是千古不变的道理。

2. 配置隔离确保稳定性

每个 Agent 的配置独立管理,支持环境变量覆盖,工作目录也相互独立。这样一来,一个 Agent 的配置出错不会影响到其他 Agent。

这就像生活中的界限,每个人都有自己的空间,互不干扰才能和谐共处。

3. 错误处理机制

单个 Agent 失败不应该影响整体流程。我们实现了降级策略:当某个 Agent 执行失败时,系统可以自动切换到备用方案,或者直接跳过该步骤继续执行后续任务。同时,完整的日志记录也便于事后排查问题。

毕竟谁也不能保证永远不会出错,关键是怎么处理错误。这就像人生,总会遇到挫折,重要的是怎么走出来。

4. 监控与可观测性

通过 ACP 协议(我们自定义的通信协议,基于 JSON-RPC 2.0),可以追踪每个 Agent 的执行状态。会话隔离确保了并发安全,动态缓存则优化了性能表现。

毕竟看不见的东西最容易出问题,有点监控总好过两眼一抹黑。

采用这套多 Agent 协作配置后,HagiCode 项目的开发效率有了明显提升。具体表现在:

  1. 任务处理能力翻倍:以前一个 Agent 需要同时处理多种任务,现在可以并行处理,吞吐量翻倍不止
  2. 输出质量更稳定:每个 Agent 只专注于自己擅长的任务,输出结果的一致性和质量都更高
  3. 维护成本降低:统一的接口和配置管理,让整个系统更容易维护和扩展
  4. 新增 Agent 简单:如果要接入新的 AI 产品,只需要实现接口、添加配置,不需要修改核心逻辑

这套方案不仅解决了 HagiCode 自身的问题,也证明了多 Agent 协作确实是一种可行的架构选择。

其实效果还挺明显的,只是过程有点折腾罢了。

本文分享了 HagiCode 项目在多 Agent 协作配置方面的实践经验。核心要点包括:

  1. 标准化接口:通过 IAIProvider 统一不同 Agent 的行为,让代码可以无视底层是哪个公司的产品
  2. 工厂模式:使用 ActivatorUtilities.CreateInstance 动态创建 Provider 实例,支持运行时配置和依赖注入
  3. 协议统一:ACP 协议实现 Agent 间的标准化通信,基于 JSON-RPC 2.0 的双向通信机制
  4. 任务分流:合理分配任务给不同的 Agent,让它们各展所长,而不是试图让一个 Agent 做所有事情

这种设计不仅解决了”多 Agent 打架”的问题,还通过冒险团的任务流转机制,实现了开发流程的自动化和专业化。

如果你也在考虑引入多个 AI 助手,希望本文能给你一些参考。当然,每个项目的情况不同,具体方案还需要根据实际情况调整。毕竟没有放之四海而皆准的方案,适合自己的才是最好的。

美的事物或人,不一定要占有,只要她还是美的,自己好好看着她的美就好了。技术方案也是如此,适合自己的,就是最好的…

打造 AI 冒险团:HagiCode 多 Agent 协作配置实战

打造 AI 冒险团:HagiCode 多 Agent 协作配置实战

Section titled “打造 AI 冒险团:HagiCode 多 Agent 协作配置实战”

在现代软件开发中,单一 AI Agent 已经难以满足复杂需求。如何让来自不同公司的多个 AI 助手在同一项目中协同工作?本文将分享 HagiCode 项目在实际开发中探索出的多 Agent 协作配置方案。

相信很多开发者都有过这样的经历:项目中引入了 AI 助手辅助编程,效率确实提高了。但随着需求越来越复杂,一个 AI Agent 开始不够用了——你想让它同时处理代码审查、文档生成、单元测试等多个任务,结果往往是顾此失彼,输出质量参差不齐。

更头疼的是,当你尝试引入多个 AI 助手时,问题就变得更复杂了。每个 Agent 有自己的配置方式、API 接口和执行逻辑,彼此之间甚至会产生冲突。这就像一支球队,每个球员都很厉害,但没有人知道该怎么配合,结果踢得乱七八糟。

HagiCode 项目在开发过程中也遇到了同样的困扰。作为一个涉及前端 VSCode 扩展、后端 AI 服务、跨平台桌面客户端的复杂项目,我们需要同时对接来自不同公司的多个 AI 助手:Claude Code、Codex、CodeBuddy、iFlow 等等。如何让它们在同一项目中和谐共处、发挥各自特长,成了必须解决的关键问题。

其实这也罢了,毕竟谁愿意每天跟一群打架的 AI 打交道呢。

本文分享的方案,正是我们在 HagiCode 项目中实际踩坑、实际优化出来的多 Agent 协作配置实践。如果你也在为多 AI 助手协作而头疼,相信这篇文章会给你一些启发。或许吧,毕竟每个人的情况都不一样。

HagiCode 是一个 AI 代码助手项目,采用多 AI 引擎协同工作的”冒险团”模式。项目地址:github.com/HagiCode-org/site

本文分享的多 Agent 配置方案,正是 HagiCode 能够在复杂项目中保持高效开发的核心技术之一。也没什么特别的,就是把一群 AI 变成一支能打配合的冒险团而已。

从”单打独斗”到”团队协作”

Section titled “从”单打独斗”到”团队协作””

在 HagiCode 项目早期,我们也尝试过只用一个 AI Agent 来处理所有任务。很快我们就发现,这种方式存在明显的瓶颈:不同的任务需要不同的能力侧重点,有的任务需要更强的上下文理解能力,有的则需要更精准的代码修改能力。一个 Agent 很难在所有方面都表现出色。

这让我们意识到,必须让多个 Agent 协同工作。但问题是,如何让不同公司的 AI 产品在同一个项目中和平共处?我们需要解决几个核心问题:

  1. 配置管理复杂性:每个 Agent 有不同的配置方式、API 接口和执行模式
  2. 通信协议统一:需要一种标准化的方式让不同 Agent 之间进行数据交换
  3. 任务分工协调:如何合理分配任务,让每个 Agent 发挥特长

带着这些问题,我们开始设计 HagiCode 的多 Agent 架构。其实也没那么复杂,只是想明白了而已。

经过多次迭代,我们最终确定的架构是这样的:

┌─────────────────────────────────────────────────────────────────┐
│ AIProviderFactory │
│ (工厂模式统一管理所有 AI Provider) │
├─────────────────────────────────────────────────────────────────┤
│ ClaudeCodeCli │ CodexCli │ CodebuddyCli │ IFlowCli │
│ (Anthropic) │ (OpenAI) │ (智谱 GLM) │ (智谱) │
└─────────────────────────────────────────────────────────────────┘

核心思路是:通过统一的 Provider 接口,让不同的 AI Agent 可以被同一套代码管理。同时使用工厂模式动态创建和配置这些 Provider,确保系统的扩展性和灵活性。

这就像生活中的分工,每个人都有自己的角色,只是这里把这种分工变成了代码架构而已。

根据 HagiCode 项目的实际使用经验,我们为每个 Agent 分配了不同的职责:

Agent提供商模型主要用途
ClaudeCodeCliAnthropicglm-5-turbo生成技术方案和Proposal
CodexCliOpenAI/Zedgpt-5.4执行精准的代码修改
CodebuddyCli智谱glm-4.7优化提案描述和文档
IFlowCli智谱glm-4.7归档提案和历史记录
OpenCodeCli--通用代码编辑
GitHubCopilotMicrosoft-辅助编程和代码补全

这种分工的背后逻辑是:每个 Agent 都有自己擅长的领域。Claude Code 在理解和分析复杂需求方面表现出色,所以让它负责前期的方案设计;Codex 在代码修改方面更精准,适合处理具体的实现任务;CodeBuddy 性价比高,用来优化文档再合适不过。

毕竟适合自己的才是最好的,条条大路通罗马,只是有的路好走一点,有的路稍微曲折一点罢了。

要让不同的 AI Agent 能够被统一管理,首先需要定义一套统一的接口。HagiCode 中定义了这个接口:

public interface IAIProvider
{
// 统一的 Provider 接口
Task<IAIProvider?> GetProviderAsync(AIProviderType providerType);
Task<IAIProvider?> GetProviderAsync(string providerName, CancellationToken cancellationToken);
}

这个接口看起来很简单,但它是整个多 Agent 系统的基石。通过统一的接口,我们可以无视底层是哪个公司的 AI 产品,都以相同的方式进行调用。

其实这就是把复杂的事情简单化了,毕竟简单才是美。

有了统一的接口,接下来就是如何创建这些 Provider 实例。HagiCode 使用了工厂模式:

private IAIProvider? CreateProvider(AIProviderType providerType, ProviderConfiguration config)
{
return providerType switch
{
AIProviderType.ClaudeCodeCli =>
ActivatorUtilities.CreateInstance<ClaudeCodeCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.CodebuddyCli =>
ActivatorUtilities.CreateInstance<CodebuddyCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.CodexCli =>
ActivatorUtilities.CreateInstance<CodexCliProvider>(_serviceProvider, Options.Create(config)),
AIProviderType.IFlowCli =>
ActivatorUtilities.CreateInstance<IFlowCliProvider>(_serviceProvider, Options.Create(config)),
_ => null
};
}

这里用到了依赖注入的 ActivatorUtilities.CreateInstance,它可以在运行时动态创建 Provider 实例,并且自动注入依赖项。这种设计的好处是:新增一个 Agent 类型时,只需要添加对应的 Provider 类,然后在工厂方法中加一个 case 分支即可,完全不需要修改现有代码。

这也罢了,毕竟谁愿意每次加新功能都要改一堆旧代码呢。

为了让配置更灵活,我们还实现了类型映射机制:

public static AIProviderTypeExtensions
{
private static readonly Dictionary<string, AIProviderType> _typeMap = new(
StringComparer.OrdinalIgnoreCase)
{
["ClaudeCodeCli"] = AIProviderType.ClaudeCodeCli,
["CodebuddyCli"] = AIProviderType.CodebuddyCli,
["CodexCli"] = AIProviderType.CodexCli,
["IFlowCli"] = AIProviderType.IFlowCli,
// ...更多类型映射
};
}

这个映射表的作用是将字符串形式的 Provider 名称转换为枚举类型。这样一来,配置文件就可以使用直观的字符串名称,而代码内部则使用类型安全的枚举进行处理。

毕竟配置这东西,越直观越好,谁愿意记一堆复杂的代码呢。

实际使用时,只需要在 appsettings.json 中配置即可:

AI:
Providers:
Providers:
ClaudeCodeCli:
Enabled: true
Model: glm-5-turbo
WorkingDirectory: /path/to/project
CodebuddyCli:
Enabled: true
Model: glm-4.7
CodexCli:
Enabled: true
Model: gpt-5.4
IFlowCli:
Enabled: true
Model: glm-4.7

每个 Provider 都可以独立配置开关、模型版本、工作目录等参数。这种设计既保证了灵活性,又便于管理和维护。

其实配置文件就像人生的选项,你可以选择开启或关闭某些功能,只是代码里的选择更容易后悔罢了。

有了统一的技术架构,接下来就是如何让多个 Agent 协同工作了。HagiCode 设计了一套任务流转机制,让不同的 Agent 处理不同阶段的任务:

提案创建 (用户)
[Claude Code] ──生成提案──▶ 提案文档
│ │
│ ▼
│ [Codebuddy] ──优化描述──▶ 优化后提案
│ │
│ ▼
│ [Codex] ──执行修改──▶ 代码变更
│ │
│ ▼
└───────────────▶ [iFlow] ──归档──▶ 历史记录

这种分工的好处是:每个 Agent 只需要专注于自己擅长的任务,不需要”什么都会”。Claude Code 负责从无到有生成提案,Codebuddy 负责把提案描述得更清晰,Codex 负责把提案变成实际的代码变更,iFlow 则负责把这些变更归档保存。

其实这就像生活中的团队合作,每个人都有自己的角色,合起来才能完成一件大事。只是这里的团队成员是 AI 而已。

在实际运行中,我们总结了以下几点经验:

1. Agent 选择策略很重要

不是随便分配任务,而是要根据每个 Agent 的特长来分配:

  • 提案生成:使用 Claude Code,因为它有更强的上下文理解能力
  • 代码执行:使用 Codex,因为它在代码修改方面更精准
  • 提案优化:使用 Codebuddy,因为它的性价比高
  • 归档存储:使用 iFlow,因为它稳定可靠

毕竟让合适的人做合适的事,这是千古不变的道理。

2. 配置隔离确保稳定性

每个 Agent 的配置独立管理,支持环境变量覆盖,工作目录也相互独立。这样一来,一个 Agent 的配置出错不会影响到其他 Agent。

这就像生活中的界限,每个人都有自己的空间,互不干扰才能和谐共处。

3. 错误处理机制

单个 Agent 失败不应该影响整体流程。我们实现了降级策略:当某个 Agent 执行失败时,系统可以自动切换到备用方案,或者直接跳过该步骤继续执行后续任务。同时,完整的日志记录也便于事后排查问题。

毕竟谁也不能保证永远不会出错,关键是怎么处理错误。这就像人生,总会遇到挫折,重要的是怎么走出来。

4. 监控与可观测性

通过 ACP 协议(我们自定义的通信协议,基于 JSON-RPC 2.0),可以追踪每个 Agent 的执行状态。会话隔离确保了并发安全,动态缓存则优化了性能表现。

毕竟看不见的东西最容易出问题,有点监控总好过两眼一抹黑。

采用这套多 Agent 协作配置后,HagiCode 项目的开发效率有了明显提升。具体表现在:

  1. 任务处理能力翻倍:以前一个 Agent 需要同时处理多种任务,现在可以并行处理,吞吐量翻倍不止
  2. 输出质量更稳定:每个 Agent 只专注于自己擅长的任务,输出结果的一致性和质量都更高
  3. 维护成本降低:统一的接口和配置管理,让整个系统更容易维护和扩展
  4. 新增 Agent 简单:如果要接入新的 AI 产品,只需要实现接口、添加配置,不需要修改核心逻辑

这套方案不仅解决了 HagiCode 自身的问题,也证明了多 Agent 协作确实是一种可行的架构选择。

其实效果还挺明显的,只是过程有点折腾罢了。

本文分享了 HagiCode 项目在多 Agent 协作配置方面的实践经验。核心要点包括:

  1. 标准化接口:通过 IAIProvider 统一不同 Agent 的行为,让代码可以无视底层是哪个公司的产品
  2. 工厂模式:使用 ActivatorUtilities.CreateInstance 动态创建 Provider 实例,支持运行时配置和依赖注入
  3. 协议统一:ACP 协议实现 Agent 间的标准化通信,基于 JSON-RPC 2.0 的双向通信机制
  4. 任务分流:合理分配任务给不同的 Agent,让它们各展所长,而不是试图让一个 Agent 做所有事情

这种设计不仅解决了”多 Agent 打架”的问题,还通过冒险团的任务流转机制,实现了开发流程的自动化和专业化。

如果你也在考虑引入多个 AI 劏手,希望本文能给你一些参考。当然,每个项目的情况不同,具体方案还需要根据实际情况调整。毕竟没有放之四海而皆准的方案,适合自己的才是最好的。

美的事物或人,不一定要占有,只要她还是美的,自己好好看着她的美就好了。技术方案也是如此,适合自己的,就是最好的…


如果本文对你有帮助,欢迎来 GitHub 给个 Star,您的支持是我们继续分享的动力。公测已开始,欢迎安装体验。


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。

HagiCode 平台的多 AI Provider 架构实践

HagiCode 平台的多 AI Provider 架构实践

Section titled “HagiCode 平台的多 AI Provider 架构实践”

本文分享了在 Orleans Grain 架构下,如何通过统一的 IAIProvider 接口集成 iflow 和 OpenCode 两个 AI 工具的技术方案,并详细对比了 WebSocket 和 HTTP 两种通信方式的实现差异。

其实也没什么特别的,就是做 HagiCode 的时候遇到了个挺实际的问题——用户想用不同的 AI 工具,这倒也不奇怪,毕竟每个人都有自己的习惯。有的喜欢 Claude Code,有的钟爱 GitHub Copilot,还有些团队用自己开发的工具。

最开始的方案也挺简单粗暴的,就是给每个 AI 工具写专门的对接代码。可后来问题就来了——代码里全是 if-else,改一个地方要到处测试,新工具接入还得重新写一堆逻辑,想想都觉得累。

后来我想明白了,不如做一个统一的 IAIProvider 接口,把所有 AI 提供者的能力都抽象出来。这样,不管底层用的是哪个工具,对上层来说都是一样的调用方式,岂不美哉?

最近项目要接入两个新工具:iflow 和 OpenCode。这两个都支持 ACP 协议,但通信方式不太一样——iflow 用 WebSocket,OpenCode 用 HTTP API。这也算是种考验吧,要在统一的接口下适配两种不同的通信模式,不过想想也挺有意思的。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。HagiCode 是一个基于 Orleans Grain 架构的 AI 辅助开发平台,通过统一的 IAIProvider 接口与不同的 AI 提供者集成,让用户可以灵活选择自己喜欢的 AI 工具。

首先,定义了 IAIProvider 接口,把所有 AI 提供者需要实现的能力都抽象出来:

public interface IAIProvider
{
string Name { get; }
bool SupportsStreaming { get; }
ProviderCapabilities Capabilities { get; }
Task<AIResponse> ExecuteAsync(AIRequest request, CancellationToken cancellationToken = default);
IAsyncEnumerable<AIStreamingChunk> StreamAsync(AIRequest request, CancellationToken cancellationToken = default);
Task<ProviderTestResult> PingAsync(CancellationToken cancellationToken = default);
IAsyncEnumerable<AIStreamingChunk> SendMessageAsync(AIRequest request, string? embeddedCommandPrompt = null, CancellationToken cancellationToken = default);
}

这个接口有几个关键方法:

  • ExecuteAsync:执行一次性的 AI 请求
  • StreamAsync:流式获取响应,支持实时展示
  • PingAsync:健康检查,验证 provider 是否可用
  • SendMessageAsync:发送消息,支持嵌入式命令

IFlowCliProvider:基于 WebSocket 的实现

Section titled “IFlowCliProvider:基于 WebSocket 的实现”

iflow 使用 WebSocket 进行 ACP 通信,整体架构是这样的:

IFlowCliProvider → ACPSessionManager → WebSocketAcpTransport → iflow CLI
动态端口分配 + 进程管理

核心流程也挺简单:

  1. ACPSessionManager 负责创建和管理 ACP 会话
  2. WebSocketAcpTransport 处理 WebSocket 通信
  3. 动态分配一个端口,用 iflow —experimental-acp —port 启动 iflow 进程
  4. 通过 IAIRequestToAcpMapper 和 IAcpToAIResponseMapper 做请求/响应的转换

来看看核心代码:

private async IAsyncEnumerable<AIStreamingChunk> StreamCoreAsync(
AIRequest request,
string? embeddedCommandPrompt,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// 解析工作目录
var resolvedWorkingDirectory = ResolveWorkingDirectory(request);
var effectiveRequest = ApplyEmbeddedCommandPrompt(request, embeddedCommandPrompt);
// 创建 ACP 会话
await using var session = await _sessionManager.CreateSessionAsync(
Name,
resolvedWorkingDirectory,
cancellationToken,
request.SessionId);
// 发送提示词
var prompt = _requestMapper.ToPromptString(effectiveRequest);
var promptResponse = await session.SendPromptAsync(prompt, cancellationToken);
// 接收流式响应
await foreach (var notification in session.ReceiveUpdatesAsync(cancellationToken))
{
if (_responseMapper.TryConvertToStreamingChunk(notification, out var chunk))
{
if (chunk.Type == StreamingChunkType.Metadata && chunk.IsComplete)
{
yield return chunk;
yield break;
}
yield return chunk;
}
}
}

这里有几个设计上的注意点,也算是一些小心得:

  • 用 await using 确保会话正确释放,避免资源泄漏,毕竟资源这东西,不用了就该放归自然
  • 流式响应通过 IAsyncEnumerable 返回,天然支持异步流
  • Metadata 类型的 chunk 判断是否完成,确保完整接收响应

OpenCodeCliProvider:基于 HTTP API 的实现

Section titled “OpenCodeCliProvider:基于 HTTP API 的实现”

OpenCode 用 HTTP API 方式提供服务,架构略有不同:

OpenCodeCliProvider → OpenCodeRuntimeManager → OpenCodeClient → OpenCode HTTP API
OpenCodeProcessManager → opencode 进程管理

OpenCode 的特点是用 SQLite 数据库持久化会话绑定关系,这样可以支持会话恢复和提示词响应恢复,这倒是挺贴心的设计:

private async Task<OpenCodePromptExecutionResult> ExecutePromptAsync(
AIRequest request,
string? embeddedCommandPrompt,
CancellationToken cancellationToken)
{
var prompt = BuildPrompt(request, embeddedCommandPrompt);
var resolvedWorkingDirectory = ResolveWorkingDirectory(request.WorkingDirectory);
var client = await _runtimeManager.GetClientAsync(resolvedWorkingDirectory, cancellationToken);
var bindingSessionId = request.SessionId;
var boundSession = TryGetBinding(bindingSessionId, resolvedWorkingDirectory);
// 尝试使用已绑定的会话
if (boundSession is not null)
{
try
{
return await PromptSessionAsync(
client,
boundSession,
BuildPromptRequest(request, prompt, CreatePromptMessageId()),
request.Model ?? _settings.Model,
cancellationToken);
}
catch (OpenCodeApiException ex) when (IsStaleBinding(ex))
{
// 会话已过期,移除绑定
RemoveBinding(bindingSessionId);
}
}
// 创建新会话
var session = await client.Session.CreateAsync(new OpenCodeSessionCreateRequest
{
Title = BuildSessionTitle(request)
}, cancellationToken);
BindSession(bindingSessionId, session.Id, resolvedWorkingDirectory);
return await PromptSessionAsync(client, session.Id, ...);
}

这个实现有几个亮点,或者说几个有趣的地方:

  • 会话绑定机制:同一个 SessionId 会复用 OpenCode 会话,避免重复创建,省得浪费资源
  • 过期处理:检测到会话过期时自动清理绑定,旧的不去,新的不来
  • 数据库持久化:通过 SQLite 存储绑定关系,重启后仍然有效,有些东西记住了就是记住了
方面IFlowCliProviderOpenCodeCliProvider
通信方式WebSocket (ACP)HTTP API
进程管理ACPSessionManagerOpenCodeProcessManager
端口分配动态端口无端口(使用 HTTP)
会话管理ACPSessionOpenCodeSession
持久化内存缓存SQLite 数据库
启动命令iflow —experimental-acp —portopencode
延迟更低(长连接)相对较高(HTTP 请求)

选择哪种方式主要看你的需求:WebSocket 适合实时性要求高的场景,HTTP API 则更简单、更容易调试。这就像选路一样,有的路快一点,有的路好走一点罢了。

先在配置文件里启用这两个 provider:

AI:
Providers:
IFlowCli:
Type: "IFlowCli"
Enabled: true
ExecutablePath: "iflow"
Model: null
WorkingDirectory: null
OpenCodeCli:
Type: "OpenCodeCli"
Enabled: true
ExecutablePath: "opencode"
Model: "anthropic/claude-sonnet-4"
WorkingDirectory: null
OpenCode:
Enabled: true
BaseUrl: "http://localhost:38376"
ExecutablePath: "opencode"
StartupTimeoutSeconds: 30
RequestTimeoutSeconds: 120
// 通过 Factory 获取 provider
var provider = await _providerFactory.GetProviderAsync(AIProviderType.IFlowCli);
// 执行 AI 请求
var request = new AIRequest
{
Prompt = "请帮我重构这个函数",
WorkingDirectory = "/path/to/project",
Model = "claude-sonnet-4"
};
// 获取完整响应
var response = await provider.ExecuteAsync(request, cancellationToken);
Console.WriteLine(response.Content);
// 或者用流式响应
await foreach (var chunk in provider.StreamAsync(request, cancellationToken))
{
if (chunk.Type == StreamingChunkType.ContentDelta)
{
Console.Write(chunk.Content);
}
}
// 通过 Factory 获取 provider
var provider = await _providerFactory.GetProviderAsync(AIProviderType.OpenCodeCli);
var request = new AIRequest
{
Prompt = "请帮我分析这个错误",
WorkingDirectory = "/path/to/project",
Model = "anthropic/claude-sonnet-4"
};
var response = await provider.ExecuteAsync(request, cancellationToken);
Console.WriteLine(response.Content);

在启动或使用前,可以先检查 provider 是否可用:

var iflowResult = await iflowProvider.PingAsync(cancellationToken);
if (!iflowResult.Success)
{
Console.WriteLine($"IFlow 不可用: {iflowResult.ErrorMessage}");
return;
}
var openCodeResult = await openCodeProvider.PingAsync(cancellationToken);
if (!openCodeResult.Success)
{
Console.WriteLine($"OpenCode 不可用: {openCodeResult.ErrorMessage}");
return;
}

两个 provider 都支持嵌入式命令,比如 /file:xxx 这样的命令:

var request = new AIRequest
{
Prompt = "分析这个文件的问题",
SystemMessage = "你是一个代码分析专家"
};
await foreach (var chunk in provider.SendMessageAsync(
request,
embeddedCommandPrompt: "/file:src/main.cs",
cancellationToken))
{
Console.Write(chunk.Content);
}

IFlow 用 WebSocket 长连接,所以资源管理要特别注意:

  • 用 await using 确保会话正确释放,不用了就放手
  • 取消操作会触发进程清理
  • ACPSessionManager 支持最大会话数限制

OpenCode 的进程管理相对简单,OpenCodeRuntimeManager 会自动处理,省心不少。

两个 provider 都有完善的错误处理:

  • IFlow 的错误通过 ACP 会话更新传播
  • OpenCode 的错误通过 OpenCodeApiException 抛出
  • 建议在调用方捕获并处理这些异常,毕竟错误总会发生的
  • IFlow 的 WebSocket 通信比 HTTP 有更低的延迟
  • OpenCode 的会话复用可以减少 HTTP 请求开销
  • Factory 的缓存机制可以避免重复创建 provider
  • 高并发场景下,要关注进程数和连接数的限制,别到时候撑不住了

启动时会验证可执行文件路径,但运行时也可能出问题。PingAsync 是个好工具,可以验证配置是否正确:

// 启动时检查
var provider = await _providerFactory.GetProviderAsync(providerType);
var result = await provider.PingAsync(cancellationToken);
if (!result.Success)
{
_logger.LogError("Provider {ProviderType} 不可用: {Error}", providerType, result.ErrorMessage);
}

本文分享了 HagiCode 平台在集成 iflow 和 OpenCode 两个 AI 工具时的技术方案。通过统一的 IAIProvider 接口,实现了对不同通信方式(WebSocket 和 HTTP)的适配,同时保持了上层调用的一致性。

核心思路其实挺简单的:

  1. 定义统一的接口抽象
  2. 对不同实现做适配层
  3. 通过工厂模式统一管理

这样扩展性就很好,以后有新的 AI 工具要接入,只需要实现 IAIProvider 接口就行,不用改动太多现有代码。想想也挺合理的,就像搭积木一样,有统一的接口,想怎么拼都行。

如果你也在做多 AI 工具的集成,希望本文对你有帮助。不过话说回来,技术这东西,能帮到人就好,其他的也不必太在意…


如果本文对你有帮助:

AI Compose Commit:用 AI 智能重构 Git 提交工作流

AI Compose Commit:用 AI 智能重构 Git 提交工作流

Section titled “AI Compose Commit:用 AI 智能重构 Git 提交工作流”

在软件开发过程中,提交代码是程序员每天都要面对的日常工作。可是你有没有经历过这样的场景:一天工作结束后,打开 Git 看到几十个未暂存的修改文件,却不知道该如何将它们组织成合理的提交?

传统的方式是手动将文件分批暂存、逐个提交、撰写提交信息,这个过程既耗时又容易出错。咱们就常常在这上面浪费了不少时间,毕竟谁也不想在已经疲惫的晚上还要为这些琐事烦心。

我们在 HagiCode 项目中推出了一项新功能——AI Compose Commit,旨在彻底改变这个工作流程。它通过 AI 智能分析工作区中的所有未提交变更,自动将它们分组为多个逻辑提交,并执行符合规范的提交操作。本文将深入探讨这个功能的实现原理、技术架构以及我们在实践中遇到的挑战与解决方案。

本文分享的方案来自我们在 HagiCode 项目中的实践经验。

Git 作为版本控制系统,为开发者提供了强大的代码管理能力。但在实际使用中,提交操作往往成为开发流程中的瓶颈:

  1. 手动分组耗时: 当有大量文件变更时,开发者需要逐个检查文件内容,判断哪些属于同一个功能,这需要耗费大量脑力
  2. 提交信息质量参差: 撰写符合 Conventional Commits 规范的提交信息需要经验和技巧,新手常常写出不规范的提交
  3. 多仓库管理复杂: 在 monorepo 环境中,需要在不同仓库间切换,增加了操作复杂度
  4. 工作流被打断: 提交代码会打断开发思路,影响编码效率

这些问题在大型项目和团队协作环境中尤为明显。一个优秀的开发工具应该让开发者专注于核心的编码工作,而不是被繁琐的提交流程所困扰。

近年来,AI 技术在软件开发领域的应用日益广泛。从代码补全、错误检测到自动生成文档,AI 正在逐步渗透到开发的各个环节。在 Git 工作流方面,虽然已有一些工具提供提交信息生成的功能,但大多局限于单次提交的场景,缺乏对整个工作区变更的智能分析和分组能力。

其实 HagiCode 在开发过程中也遇到了这些痛点,我们曾尝试过多种工具,但都或多或少存在一些局限性。要么是功能不够完善,要么是用户体验不够好。这也是为什么我们最终决定自己实现 AI Compose Commit 功能的原因。

HagiCode 的 AI Compose Commit 功能正是为了填补这一空白而生,它不仅是生成提交信息,而是完整接管从文件分析到执行提交的整个流程。

在实现 AI Compose Commit 功能的过程中,我们面临了多个技术挑战:

  1. 文件语义理解: AI 需要理解文件变更的语义关系,判断哪些文件属于同一个功能模块。这需要深入分析文件内容、目录结构以及变更的上下文。

  2. 提交分组策略: 如何定义合理的分组标准?是按功能、按模块,还是按文件类型?不同的项目可能适用不同的策略。

  3. 实时反馈与异步处理: Git 操作可能需要较长时间,特别是处理大量文件时。如何在保证用户体验的同时完成复杂操作?

  4. 多仓库支持: 在 monorepo 架构下,需要在主仓库和子仓库之间正确路由操作。

  5. 错误处理与回滚: 如果某个提交失败,如何处理已执行的提交?是否需要回滚已暂存的文件?

  6. 提交信息一致性: 生成的提交信息需要符合项目现有的风格,保持历史提交的格式一致。

AI 处理大量文件变更会消耗显著的时间和计算资源。我们需要在以下方面进行优化:

  • 减少不必要的 AI 调用
  • 优化文件上下文的构建方式
  • 实现高效的 Git 操作批处理

这些问题在 HagiCode 的实际使用中都真实出现过,我们通过不断的迭代和优化才找到了相对完美的解决方案。如果你也在开发类似的工具,希望我们的经验能给你一些启发。

我们采用了分层架构来实现 AI Compose Commit 功能,确保系统具有良好的可扩展性和可维护性:

GitController 提供了 POST /api/git/auto-compose-commit 端点,作为功能入口。为了优化用户体验,我们采用了 Fire-and-Forget 异步模式:

  • 客户端发起请求后,服务器立即返回 HTTP 202 Accepted
  • 实际的 AI 处理在后台异步执行
  • 处理完成后通过 SignalR 通知客户端

这种设计确保了即使 AI 处理需要几分钟,用户也能立即得到响应,不会感觉系统卡顿。

GitAppService 负责核心业务逻辑:

  • 仓库检测:支持 monorepo 中的多仓库管理
  • 锁管理:防止并发操作导致的冲突
  • 文件暂存协调:与 AI 处理流程的交互
  • 错误回滚:处理失败场景下的状态恢复

AIGrain 作为 AI 操作的核心执行单元,实现了 IAIGrain 接口中的 AutoComposeCommitAsync 方法:

// 定义 AI 自动组合提交的接口方法
// 参数说明:
// - projectId: 项目唯一标识符
// - unstagedFiles: 未暂存文件列表,包含文件路径和状态信息
// - projectPath: 项目根目录路径(可选),用于访问项目上下文
// 返回值: 包含执行结果的响应对象,包括成功/失败状态和详细信息
[Alias("AutoComposeCommitAsync")]
[ResponseTimeout("00:20:00")] // 20 分钟超时,适用于处理大型变更集
Task<AutoComposeCommitResponseDto> AutoComposeCommitAsync(
string projectId,
GitFileStatusDto[] unstagedFiles,
string? projectPath = null);

这个方法设置了 20 分钟的超时时间,以处理大型变更集。HagiCode 在实际使用中发现,有些项目的单次变更可能涉及上百个文件,需要更长的处理时间。

通过抽象的 IAIService 接口,我们实现了 AI 服务的可插拔架构。目前使用 Claude Helper 服务,但可以轻松切换到其他 AI 提供商。

AI 需要了解每个文件的状态才能做出智能决策。我们通过 BuildFileChangesXml 方法构建文件上下文:

/// <summary>
/// 构建文件变更的 XML 表示形式,用于为 AI 提供完整的文件上下文信息
/// </summary>
/// <param name="stagedFiles">已暂存的文件列表,包含文件路径、状态和旧路径(针对重命名操作)</param>
/// <returns>格式化的 XML 字符串,包含所有文件的元数据信息</returns>
private static string BuildFileChangesXml(GitFileStatusDto[] stagedFiles)
{
var sb = new StringBuilder();
sb.AppendLine("<files>");
foreach (var file in stagedFiles)
{
sb.AppendLine(" <file>");
// 使用 XML 转义确保特殊字符不会破坏 XML 结构
sb.AppendLine($" <path>{System.Security.SecurityElement.Escape(file.Path)}</path>");
sb.AppendLine($" <status>{System.Security.SecurityElement.Escape(file.Status)}</status>");
// 处理文件重命名场景,记录旧路径以便 AI 理解变更关系
if (!string.IsNullOrEmpty(file.OldPath))
{
sb.AppendLine($" <oldPath>{System.Security.SecurityElement.Escape(file.OldPath)}</oldPath>");
}
sb.AppendLine(" </file>");
}
sb.AppendLine("</files>");
return sb.ToString();
}

这个 XML 格式的上下文包含文件路径、状态和旧路径(针对重命名操作),为 AI 提供了完整的元数据。通过结构化的 XML 格式,我们确保了 AI 能够准确理解每个文件的状态和变更类型。

为了让 AI 能够直接执行 Git 操作,我们配置了全面的工具权限:

// 定义 AI 可以使用的工具集合,包括文件操作和 Git 命令执行权限
// Read/Write/Edit: 文件读写和编辑能力
// Bash(git:*): 执行所有 Git 命令的权限
// 其他 Bash 命令: 用于查看文件内容和目录结构,辅助 AI 理解上下文
var allowedTools = new[]
{
"Read", "Write", "Edit",
"Bash(git:*)", "Bash(cat:*)", "Bash(ls:*)", "Bash(find:*)",
"Bash(grep:*)", "Bash(head:*)", "Bash(tail:*)", "Bash(wc:*)"
};
// 构建完整的 AI 请求对象
var request = new AIRequest
{
Prompt = prompt, // 完整的 Prompt 模板,包含任务指令和约束条件
WorkingDirectory = projectPath ?? GetTempDirectory(), // 工作目录,确保 AI 在正确的项目上下文中执行
AllowedTools = allowedTools, // 允许使用的工具集合
PermissionMode = PermissionMode.bypassPermissions, // 绕过权限检查,允许直接执行 Git 操作
LanguagePreference = languagePreference // 语言偏好设置,确保生成符合用户期望的提交信息
};

这里使用了 PermissionMode.bypassPermissions 模式,允许 AI 直接执行 Git 命令而无需用户确认。这是功能设计的核心,但同时也需要严格的输入验证来防止滥用。HagiCode 在实际部署中,通过后端的参数验证和日志监控,确保了这个机制的安全性。

AI 执行完成后,会返回结构化的结果。我们实现了双重解析策略以确保兼容性:

/// <summary>
/// 解析 AI 返回的提交执行结果,支持分隔符格式和正则表达式格式
/// </summary>
/// <param name="aiResponse">AI 返回的原始响应内容</param>
/// <returns>解析后的提交结果列表,每个结果包含提交哈希和执行状态</returns>
private List<CommitResultDto> ParseCommitExecutionResults(string aiResponse)
{
var results = new List<CommitResultDto>();
// 优先使用分隔符解析(新格式),这种格式更加明确和可靠
if (aiResponse.Contains("---"))
{
logger.LogDebug("Using delimiter-based parsing for AI response");
results = ParseDelimitedFormat(aiResponse);
if (results.Count > 0)
{
return results; // 成功解析,直接返回结果
}
logger.LogWarning("Delimiter-based parsing produced no results, falling back to regex");
}
else
{
logger.LogDebug("No delimiter found, using legacy regex-based parsing");
}
// 回退到正则表达式解析(旧格式),确保向后兼容性
return ParseLegacyFormat(aiResponse);
}

分隔符格式使用 --- 作为提交之间的分隔,格式清晰且易于解析:

---
Commit 1: abc123def456
feat(auth): add user login functionality
Implement JWT-based authentication with login form and API endpoints.
Co-Authored-By: Hagicode <noreply@hagicode.com>
---
Commit 2: 789ghi012jkl
docs(readme): update installation instructions
Add new setup steps for Docker environment.
Co-Authored-By: Hagicode <noreply@hagicode.com>
---

这种格式设计让解析变得简单可靠,同时人类阅读也很清晰。

为了防止并发操作导致的状态冲突,我们实现了仓库锁机制:

// 获取仓库锁,防止并发操作
// 参数说明:
// - fullPath: 仓库的完整路径,用于标识不同的仓库实例
// - requestedBy: 请求者标识,用于追踪和日志记录
await _autoComposeLockService.AcquireLockAsync(fullPath, requestedBy);
try
{
// 执行 AI Compose Commit 操作
// 这部分代码会调用 Orleans Grain 的方法,执行实际的 AI 处理和 Git 操作
await aiGrain.AutoComposeCommitAsync(projectId, unstagedFiles, projectPath);
}
finally
{
// 确保锁被释放,无论操作成功或失败
// 使用 finally 块可以保证异常情况下也能释放锁,避免死锁
await _autoComposeLockService.ReleaseLockAsync(fullPath);
}

锁具有 20 分钟的超时时间,与 AI 操作的超时设置保持一致。如果操作失败或超时,系统会自动释放锁,避免永久阻塞。HagiCode 在实际使用中发现,这个锁机制非常重要,特别是在团队协作环境中,多个开发者可能同时触发 AI Compose Commit 操作。

处理完成后,系统通过 SignalR 向前端发送通知:

/// <summary>
/// 发送自动组合提交完成的通知
/// </summary>
/// <param name="projectId">项目标识符,用于路由通知到正确的客户端</param>
/// <param name="totalCount">总提交数量,包括成功和失败</param>
/// <param name="successCount">成功提交的数量</param>
/// <param name="failureCount">失败提交的数量</param>
/// <param name="success">整体操作是否成功标志</param>
/// <param name="error">错误信息(如果操作失败)</param>
private async Task SendAutoComposeCommitNotificationAsync(
string projectId,
int totalCount,
int successCount,
int failureCount,
bool success,
string? error)
{
try
{
// 构建通知数据传输对象,包含详细的执行结果
var notification = new AutoComposeCommitCompletedDto
{
ProjectId = projectId,
TotalCount = totalCount,
SuccessCount = successCount,
FailureCount = failureCount,
Success = success,
Error = error
};
// 通过 SignalR Hub 广播通知到所有连接的客户端
await messageService.SendAutoComposeCommitCompletedAsync(notification);
logger.LogInformation(
"Auto compose commit notification sent for project {ProjectId}: {SuccessCount}/{TotalCount} succeeded",
projectId, successCount, totalCount);
}
catch (Exception ex)
{
// 记录通知错误但不影响主操作流程
// 通知失败不应该导致整个操作失败
logger.LogError(ex, "Failed to send auto compose commit notification for project {ProjectId}", projectId);
}
}

前端收到通知后可以更新 UI,显示提交成功或失败的状态,提升用户体验。这种实时反馈机制在 HagiCode 的使用中获得了很好的用户反馈,用户可以清楚地知道操作何时完成以及结果如何。

AI 的行为完全由 Prompt 决定,我们精心设计了 Auto Compose Commit 的 Prompt 模板。以中文版本为例(auto-compose-commit.zh-CN.hbs):

Prompt 开头明确声明支持非交互式运行模式,这是 CI/CD 和自动化脚本的关键需求:

**重要提示**:此提示词可能在非交互式环境中运行(如 CI/CD、自动化脚本)。
**非交互式模式**:
- 禁止使用 AskUserQuestion 或任何交互式工具
- 当需要用户输入时:
- 使用合理的默认值(如提交类型使用 feat)
- 跳过可选的确认步骤
- 记录所做的假设

这个设计确保了 AI Compose Commit 功能不仅能在交互式 IDE 环境中使用,也能集成到 CI/CD 流程中,实现完全自动化的提交流程。

为了防止 AI 执行危险操作,我们在 Prompt 中添加了严格的分支保护规则:

**分支保护**:
- 禁止执行任何分支切换操作(git checkout、git switch)
- 所有 git commit 命令必须在当前分支上执行
- 不得创建、删除或重命名分支
- 不得修改未跟踪文件或未暂存变更
- 如果需要分支切换才能完成操作,应返回错误而非执行

这些规则通过约束 AI 的工具使用范围,确保操作的安全性。HagiCode 在实际测试中验证了这些约束的有效性,AI 在遇到需要分支切换的场景时会安全地返回错误,而不是执行危险操作。

Prompt 中详细定义了文件分组的决策逻辑:

**文件分组决策树**:
├── 是否为配置文件(package.json、tsconfig.json、.env 等)?
│ ├── 是 → 独立提交(类型:chore 或 build)
│ └── 否 → 继续
├── 是否为文档文件(README.md、*.md、docs/**)?
│ ├── 是 → 独立提交(类型:docs)
│ └── 否 → 继续
├── 是否与同一功能相关?
│ ├── 是 → 合并到同一提交
│ └── 否 → 分别提交
└── 是否为跨模块变更?
├── 是 → 按模块分组
└── 否 → 按功能分组

这个决策树为 AI 提供了清晰的分组逻辑,确保生成的提交符合语义合理性。HagiCode 在实际使用中发现,这个决策树能够处理绝大多数常见场景,生成的分组结果符合开发者预期。

为了让提交信息与项目历史保持一致,Prompt 要求 AI 在生成前分析最近的提交历史:

**历史格式一致性**:在生成提交信息之前,你**必须**分析当前仓库的提交历史以匹配现有风格
1. 使用 git log -n 15 --pretty=format:"%H|%s|%b%n---%n" 获取最近的提交历史
2. 分析提交以识别:
- 结构模式:项目是否使用多段落?是否有 "Changes:" 或 "Capabilities:" 部分?
- 语言模式:提交信息是英文、中文还是混合?
- 常用类型:最常使用哪些提交类型(feat、fix、docs 等)?
- 特殊格式:是否有 Co-Authored-By 行?其他项目特定的约定?
3. 生成遵循检测到的模式的提交信息

这个分析确保了 AI 生成的提交信息不会显得突兀,而是与项目的提交历史保持风格一致。在 HagiCode 的多语言项目中,这个功能特别重要,它能够根据项目的提交历史自动选择合适的语言和格式。

每个提交必须包含 Co-Authored-By 信息:

**重要**:每个提交必须添加 Co-Authored-By 信息
- 使用以下格式:git commit -m "type(scope): subject" -m "" -m "Co-Authored-By: Hagicode <noreply@hagicode.com>"
- 或者直接在提交信息中包含 Co-Authored-By 行

这不仅是为了贡献规范,也是为了追踪 AI 辅助的提交历史。HagiCode 将这个要求作为强制规则,确保所有 AI 生成的提交都带有明确的来源标识。

完整的 AI Compose Commit 工作流程如下:

  1. 用户触发: 用户在 Git Status 面板或 Quick Actions Zone 点击”AI Auto Compose Commit”按钮
  2. API 请求: 前端发送 POST 请求到 /api/git/auto-compose-commit 端点
  3. 立即响应: 服务器返回 HTTP 202 Accepted,不等待处理完成
  4. 后台处理:
    • GitAppService 获取仓库锁
    • 调用 AIGrain 的 AutoComposeCommitAsync 方法
    • 构建文件上下文 XML
    • 执行 AI Prompt,让 AI 分析并执行提交
  5. AI 执行:
    • 使用 Git 命令获取所有未暂存变更
    • 读取文件内容理解变更性质
    • 按语义关系对文件分组
    • 对每组执行 git addgit commit 操作
  6. 结果解析: 解析 AI 返回的执行结果
  7. 通知发送: 通过 SignalR 通知前端
  8. 锁释放: 无论成功或失败,都释放仓库锁

这个流程的设计确保了用户可以在发起操作后立即继续其他工作,而不需要等待 AI 处理完成。HagiCode 的用户反馈表明,这种异步处理方式大大提升了工作流体验。

我们实现了多层级的错误处理:

// 验证请求参数的有效性,防止无效请求到达后端处理逻辑
if (request.UnstagedFiles == null || request.UnstagedFiles.Count == 0)
{
return BadRequest(new
{
message = "No unstaged files provided. Please make changes in the working directory first.",
status = "validation_failed"
});
}

如果 AI 处理过程中出现错误,系统会执行回滚操作,将已暂存的文件取消暂存,避免留下不一致的状态。这个机制在 HagiCode 的实际使用中挽救了多次意外中断,确保了仓库状态的完整性。

20 分钟的超时设置确保了长时间运行的操作不会无限期阻塞资源。超时后,系统会释放锁并通知用户操作失败。HagiCode 在实际使用中发现,大部分操作能够在 2-5 分钟内完成,只有处理超大型变更集时才会接近超时限制。

AI Compose Commit 最适合以下场景:

  • 一天工作结束后,批量处理多个文件的变更
  • 重构操作后,多个相关文件需要分别提交
  • 功能开发完成,需要将相关变更分组提交

不适合以下场景:

  • 单个文件的快速提交(直接使用普通提交更快)
  • 需要精确控制提交内容的场景
  • 包含敏感信息的提交(需要人工审核)

虽然 AI 智能分组很强大,但开发者仍应审查生成的提交:

  • 检查提交的分组是否符合预期
  • 验证提交信息的准确性
  • 确认没有遗漏或错误包含文件

如果发现不合理的分组,可以使用 git reset --soft HEAD~N 撤销后重新分组。HagiCode 的经验表明,即使 AI 分组很智能,人工审查仍然是有价值的,特别是在重要的功能提交时。

确保项目的 Git 配置支持 Conventional Commits:

Terminal window
# 安装 commitlint
npm install -g @commitlint/cli @commitlint/config-conventional
# 配置 commitlint
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

这样可以在 CI/CD 流程中验证提交信息格式,与 AI Compose Commit 生成的格式保持一致。

如果你想在项目中实现类似的 AI 辅助提交功能,以下是我们的建议:

先实现单次提交信息生成,再逐步扩展到多提交分组功能。这样更容易验证和迭代。HagiCode 也是按照这个路径逐步完善功能的,早期版本只支持单次提交,后来才扩展到多提交智能分组。

不要自己实现 AI 调用逻辑,使用现有的 SDK 可以减少开发时间和潜在 bug。我们使用了 Claude Helper 服务,它提供了稳定的接口和完善的错误处理。

Prompt 的质量直接决定了 AI 输出的质量。投入时间设计详细的 Prompt,包括:

  • 明确的任务描述
  • 具体的输出格式要求
  • 边界情况的处理规则
  • 示例说明

HagiCode 在 Prompt 设计上投入了大量时间,这是功能成功的关键因素之一。

AI 操作可能因为各种原因失败(网络问题、API 限流、内容审查等)。确保你的系统能够优雅地处理这些错误,并提供有意义的错误信息。

不要完全自动化,给用户保留控制权。提供查看分组结果、调整分组、手动编辑提交信息等选项,平衡自动化与灵活性。HagiCode 虽然实现了自动执行,但仍然保留了预览和调整的能力。

在构建文件上下文时,过滤掉不需要 AI 分析的文件:

// 过滤掉自动生成的文件和过大的文件,减少 AI 处理负担
var relevantFiles = stagedFiles
.Where(f => !IsGeneratedFile(f.Path))
.Where(f => !IsLargeFile(f.Path))
.ToArray();

如果支持多个独立仓库,可以并行处理不同仓库的提交,提高整体效率。

缓存项目提交历史分析结果,避免每次都重新分析。可以在配置文件中存储历史格式偏好,减少 AI 调用次数。

AI Compose Commit 功能代表了 AI 技术在软件开发工具中的深度应用。通过智能分析文件变更、自动分组提交、生成规范的提交信息,它显著提升了 Git 工作流的效率,让开发者能够更专注于核心的编码工作。

在实现过程中,我们学到了几个重要的经验:

  1. 用户反馈是关键: 早期版本采用同步等待方式,用户反馈体验不佳,改为 Fire-and-Forget 模式后满意度大幅提升
  2. Prompt 设计决定质量: 一个精心设计的 Prompt 比复杂的算法更能保证 AI 输出的质量
  3. 安全永远是第一位的: 虽然赋予 AI 直接执行 Git 命令的权限带来了效率提升,但必须配合严格的约束和验证
  4. 渐进式改进: 从简单场景开始,逐步增加复杂度,比一次性实现所有功能更容易成功

未来,我们计划进一步优化 AI Compose Commit 功能,包括:

  • 支持更多提交分组策略(按时间、按开发者等)
  • 集成代码审查流程,在提交前自动触发审查
  • 支持自定义提交信息模板,满足不同项目的个性化需求

如果你觉得本文分享的方案有价值,不妨也试试 HagiCode,体验一下这个功能在实际开发中的效果。毕竟实践是检验真理的唯一标准嘛。


感谢您的阅读,如果您觉得本文有用,快点击下方点赞按钮,让更多的人看到本文。

本内容采用人工智能辅助协作,经本人审核,符合本人观点与立场。