HagiCode 平台的多 AI Provider 架构实践

简介: 本文分享HagiCode平台基于Orleans Grain的多AI Provider架构实践:通过统一IAIProvider接口,优雅集成iflow(WebSocket/ACP)与OpenCode(HTTP API)两大工具,实现通信协议解耦、会话管理差异化适配及上层调用一致性,显著提升扩展性与可维护性。(239字)

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 项目中的实践经验。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 的实现

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

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

核心流程也挺简单:

  1. ACPSessionManager 负责创建和管理 ACP 会话
  2. WebSocketAcpTransport 处理 WebSocket 通信
  3. 动态分配一个端口,用 iflow --experimental-acp --port {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 的实现

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 存储绑定关系,重启后仍然有效,有些东西记住了就是记住了

两种方式的对比

方面 IFlowCliProvider OpenCodeCliProvider
通信方式 WebSocket (ACP) HTTP API
进程管理 ACPSessionManager OpenCodeProcessManager
端口分配 动态端口 无端口(使用 HTTP)
会话管理 ACPSession OpenCodeSession
持久化 内存缓存 SQLite 数据库
启动命令 iflow --experimental-acp --port {port} opencode
延迟 更低(长连接) 相对较高(HTTP 请求)

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

实践指南

配置 Provider

先在配置文件里启用这两个 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

使用 IFlowCliProvider

// 通过 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);
    }
}

使用 OpenCodeCliProvider

// 通过 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 工具的集成,希望本文对你有帮助。不过话说回来,技术这东西,能帮到人就好,其他的也不必太在意......

参考资料


如果本文对你有帮助:

目录
相关文章
|
1月前
|
人工智能 大数据
AI赋能,短剧行业迎来创新发展新热潮
随着AI技术的快速迭代,短剧行业正经历一场深刻的创新变革,从创作、制作到传播全流程被重构。AI技术大幅降低创作门槛、压缩制作周期、控制生产成本,推动短剧从“量的积累”向“质的提升”转型,催生AI仿真人漫剧等新形态。然而热潮之下,内容同质化、情感表达不足、版权风险等问题也随之凸显。唯有平衡技术赋能与内容初心,才能让AI真正助力短剧行业实现可持续创新发展。
|
1月前
|
弹性计算 云计算 对象存储
阿里云轻量应用服务器38元一年抢购地址,云服务器性能、适用场景与抢购规则
阿里云2026年特价活动中,轻量应用服务器以38元/年的价格成为个人开发者、初创企业及学生群体的理想选择。该服务器配置为2核2G 200M带宽,具备极速部署、预置丰富环境、管理便捷及成本极致优势,适用于个人博客、企业展示官网、电商独立站等场景。用户需定时抢购,并遵循相关购买与续费规则,整体上,这是一款高性价比的入门级云服务器产品。
|
5月前
|
机器学习/深度学习 人工智能 前端开发
终端里的 AI 编程助手:OpenCode 使用指南
OpenCode 是开源的终端 AI 编码助手,支持 Claude、GPT-4 等模型,可在命令行完成代码编写、Bug 修复、项目重构。提供原生终端界面和上下文感知能力,适合全栈开发者和终端用户使用。
47867 11
|
1月前
|
安全 Linux API
OpenClaw(Clawdbot)保姆级教程:无影云电脑/本地部署+免费大模型API配置+Skills集成与常见问题解答
2026年,OpenClaw(曾用名Clawdbot、Moltbot)作为开源AI自动化代理框架,凭借“自然语言驱动+全场景任务执行+插件化扩展”的核心优势,成为个人与轻量团队提升效率的核心工具,被称为“24小时在岗的私人数字助理”。它无需手动编写脚本,仅需口语化指令,即可自主完成文件处理、网页操作、办公自动化、数据监控、内容创作等各类重复性任务,数据存储在本地或自有云端,隐私安全可控,完美适配零基础用户的使用需求。
879 1
|
1月前
|
数据采集 JSON API
从踩坑到高效落地:关键词搜索京东商品列表API的实操心得
本指南聚焦京东商品列表API实操,详解jd.item_search接口调用要点:涵盖必填参数(app_key、timestamp、sign等)、关键词/分页/价格筛选配置及核心响应字段(SKU、标题、售价、销量等),助开发者快速对接,高效获取合规商品数据。(239字)
260 22
|
1月前
|
人工智能 安全 API
OpenClaw(小龙虾)新手保姆级攻略:计算巢+本地部署+Skills集成+阿里云Coding Plan 等免费大模型API配置+避坑指南
2026年,开源AI智能体OpenClaw(曾用名Clawdbot、Moltbot,因Logo酷似小龙虾被网友亲切称为“小龙虾”)持续爆火,GitHub星标数量突破18.6万,成为兼具实用性与扩展性的现象级AI工具。其核心优势在于“本地优先”的架构设计与插件化扩展能力——默认状态下的OpenClaw仅能满足基础聊天需求,而通过阿里云计算巢一键部署、本地多系统部署,搭配阿里云百炼Coding Plan免费大模型API,再集成各类Skills插件,就能解锁自动化办公、多平台协同、数据处理等全场景能力,真正实现“自然语言指令→AI规划→任务落地”的闭环。
1658 1
|
1月前
|
安全 Linux API
免费Token用出付费效果:OpenClaw全平台部署(阿里云/Win11/Mac/Linux)+6大省钱技能合集(Token省95%)+FAQ
“Token消耗太快”“免费额度几天就用完”“明明没做多少事,账单却超预期”——这是很多OpenClaw(昵称“小龙虾”)用户的共同困扰。作为开源AI代理框架,OpenClaw的核心功能依赖大模型API驱动,而Token消耗直接与使用成本挂钩:暴力读取全文件、网页搜索返回冗余HTML、对话记忆无限制膨胀等行为,都会导致Token快速耗尽,让免费额度“捉襟见肘”。
3187 3
|
1月前
|
人工智能 安全 Linux
龙虾AI 🦞 OpenClaw保姆级教程:3分钟阿里云/Win11/Mac/Linux部署+ 10大必装Skill + FAQ
“OpenClaw部署完、模型也配置好了,却不知道该装什么技能”——这是无数新手的共性困境。ClawHub上13729个技能密密麻麻,从编程工具到浏览器自动化,从图像生成到安全审计,90%的技能实用性极低,盲目安装不仅占用资源,还可能因恶意技能导致安全风险。
1870 1
|
1月前
|
人工智能 API 数据安全/隐私保护
OpenClaw(“小龙虾”)保姆级教程:3分钟阿里云/本地部署+API配置+5个必装Skill及常见问题解答
2026年,OpenClaw(曾用名Clawdbot)以“GitHub星标之王”的姿态席卷开源圈,但其基础功能仅能满足简单对话需求。对新手而言,真正解锁其生产力价值的关键,在于安装适配场景的Skill(技能插件)——如同给天才宝宝搭配乐高组件,装上信息检索Skill就能实时洞察世界,配上办公管理Skill就能变身金牌秘书,组合使用更能实现“搜索-提炼-创作”全流程自动化。
1539 6

热门文章

最新文章

下一篇
开通oss服务