设计决策¶
本文档解释了 clinvoker 开发过程中的关键架构决策及其背后的原理。理解这些决策有助于开发者有效贡献,并帮助用户理解系统的行为。
为什么选择 Go¶
决策¶
clinvoker 使用 Go(Golang)实现。
原理¶
-
单二进制部署:Go 编译为单个静态二进制文件,没有运行时依赖,使分发变得简单。
-
出色的并发性:Go 的 goroutine 和 channel 提供了轻量级并发原语,非常适合处理多个后端和请求。
-
标准库:丰富的标准库包括 HTTP 服务器、JSON 处理和子进程管理,无需外部依赖。
-
跨平台:原生支持 Windows、macOS 和 Linux,平台特定代码最少。
-
快速编译:快速的构建时间提高了开发者生产力。
考虑的替代方案¶
| 语言 | 优点 | 缺点 |
|---|---|---|
| Python | 生态系统、AI 库 | 部署复杂性、GIL 限制 |
| Rust | 性能、安全性 | 学习曲线陡峭、编译时间长 |
| Node.js | JavaScript 熟悉度 | 运行时依赖、回调复杂性 |
| Java | 成熟的生态系统 | 需要 JVM、冗长 |
为什么选择 Cobra 作为 CLI 框架¶
决策¶
Cobra 用作 CLI 框架。
原理¶
-
行业标准:被 Kubernetes、Hugo 和许多其他主要 Go 项目使用。
-
丰富功能:内置帮助生成、shell 补全和标志解析。
-
命令层次结构:对子命令的持久化和本地标志的自然支持。
-
文档:从代码自动生成文档。
-
验证:内置标志验证和错误处理。
实现模式¶
var rootCmd = &cobra.Command{
Use: "clinvk",
Short: "Unified AI CLI wrapper",
Long: `A unified interface for Claude Code, Codex CLI, and Gemini CLI.`,
RunE: runRoot,
}
func init() {
rootCmd.PersistentFlags().String("backend", "", "AI backend to use")
rootCmd.PersistentFlags().String("model", "", "Model to use")
// ...
}
为什么选择 Chi 作为 HTTP 路由器¶
决策¶
Chi 用作 HTTP 路由器和中间件框架。
原理¶
-
轻量级:最小开销,惯用 Go 设计。
-
中间件链:使用
Use()模式的优雅中间件组合。 -
上下文感知:基于
context.Context的请求范围值。 -
URL 参数:干净的 URL 参数提取。
-
兼容性:与标准
http.Handler无缝协作。
中间件栈¶
router := chi.NewRouter()
router.Use(middleware.RequestID)
router.Use(middleware.RealIP)
router.Use(middleware.Recoverer)
router.Use(middleware.Logger)
router.Use(middleware.Timeout(60 * time.Second))
为什么选择 Huma 作为 OpenAPI 工具¶
决策¶
Huma 用于 OpenAPI 生成和请求/响应验证。
原理¶
-
代码优先:从 Go 代码生成 OpenAPI 规范,而不是相反。
-
类型安全:请求/响应类型在编译时验证。
-
自动文档:从代码生成交互式文档。
-
验证:基于结构体标签的自动请求验证。
-
多适配器:与 Chi、Gin 和其他路由器一起工作。
示例用法¶
huma.Register(api, huma.Operation{
OperationID: "create-chat-completion",
Method: http.MethodPost,
Path: "/openai/v1/chat/completions",
}, func(ctx context.Context, input *ChatRequest) (*ChatResponse, error) {
// 处理器实现
})
为什么使用子进程执行而不是 SDK¶
决策¶
clinvoker 将 AI CLI 工具作为子进程执行,而不是直接使用它们的 SDK。
原理¶
-
零配置:CLI 工具自动处理认证、API 密钥和配置。
-
始终最新:SDK API 变更时无需更新 clinvoker。
-
功能对等:CLI 工具通常有 SDK 中没有的功能。
-
会话管理:利用 CLI 工具内置的会话处理。
-
简洁性:一个抽象层而不是多个 SDK 集成。
权衡¶
| 方面 | 子进程方式 | SDK 方式 |
|---|---|---|
| 启动时间 | 略慢 | 更快 |
| 依赖 | 更少 | 更多库 |
| 维护 | 更低 | 更高 |
| 功能访问 | 完整 CLI 功能 | SDK 受限 |
| 认证 | CLI 处理 | 需要代码 |
SDK 兼容性方法¶
决策¶
除了原生 REST API 外,还提供 OpenAI 和 Anthropic 兼容的 API 端点。
原理¶
-
生态系统兼容性:使用 OpenAI SDK 的现有工具无需修改即可工作。
-
迁移路径:从云 API 轻松过渡到本地 CLI 工具。
-
框架支持:LangChain、LangGraph 和类似框架开箱即用。
-
熟悉的接口:开发者已经了解这些 API。
实现策略¶
flowchart TB
subgraph 输入["客户端请求"]
OPENAI[OpenAI 格式]
ANTH[Anthropic 格式]
NATIVE[原生格式]
end
subgraph 转换["转换层"]
MAP[统一选项映射器]
end
subgraph 内部["内部处理"]
EXEC[执行器]
BACKEND[后端]
end
OPENAI --> MAP
ANTH --> MAP
NATIVE --> MAP
MAP --> EXEC
EXEC --> BACKEND
会话持久化权衡¶
决策¶
会话持久化到本地文件系统,使用 JSON 格式。
原理¶
-
简洁性:不需要外部数据库。
-
可移植性:易于备份、迁移和检查。
-
人类可读:JSON 格式允许手动检查和调试。
-
版本控制:如果需要,会话可以进行版本控制。
基于文件 vs 数据库存储¶
| 方面 | 基于文件 | 数据库 |
|---|---|---|
| 设置 | 无需设置 | 需要安装 |
| 复杂性 | 低 | 更高 |
| 查询 | 有限 | 丰富 |
| 并发 | 文件锁定 | ACID 事务 |
| 可扩展性 | 单机 | 分布式 |
| 备份 | 文件复制 | 数据库备份 |
为什么不用 SQLite?¶
SQLite 曾被考虑但被拒绝,因为: - JSON 文件更容易检查和调试 - 没有模式迁移复杂性 - 更简单的备份和恢复 - 使用文件锁定的跨进程访问简单明了
后端抽象设计选择¶
决策¶
使用通用的 Backend 接口和统一选项映射。
原理¶
-
多态性:在核心代码中统一处理所有后端。
-
可扩展性:无需修改核心即可轻松添加新后端。
-
可测试性:用于测试的模拟后端。
-
一致性:无论后端如何,API 都相同。
接口设计¶
type Backend interface {
Name() string
IsAvailable() bool
BuildCommand(prompt string, opts *Options) *exec.Cmd
ResumeCommand(sessionID, prompt string, opts *Options) *exec.Cmd
ParseOutput(rawOutput string) string
ParseJSONResponse(rawOutput string) (*UnifiedResponse, error)
}
并发模型选择¶
决策¶
使用 sync.RWMutex 进行进程内并发,使用文件锁定进行跨进程同步。
原理¶
-
读密集型工作负载:大多数操作是读取(列出、获取会话)。
-
Go 惯用:Go 中并发访问的标准模式。
-
跨进程安全:文件锁支持 CLI 和服务器共存。
-
简洁性:比基于 channel 的方法更容易理解。
并发模式¶
// 读操作
func (s *Store) Get(id string) (*Session, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.getLocked(id)
}
// 写操作
func (s *Store) Save(sess *Session) error {
s.mu.Lock()
defer s.mu.Unlock()
return s.saveLocked(sess)
}
配置级联设计¶
决策¶
配置遵循级联:CLI 参数 -> 环境变量 -> 配置文件 -> 默认值。
原理¶
-
可预测的覆盖:高优先级来源总是获胜。
-
环境友好:在容器和 CI/CD 中表现良好。
-
用户可控:无需更改文件即可轻松覆盖。
-
安全默认值:无指定时使用安全配置。
解析示例¶
# 配置文件:~/.clinvk/config.yaml
default_backend: claude
backends:
codex:
model: o3
# 环境变量
CLINVK_BACKEND=codex
CLINVK_CODEX_MODEL=o3-mini
# CLI
clinvk --backend codex "prompt"
# 结果:backend=codex (CLI), model=o3-mini (env)
HTTP 服务器设计¶
决策¶
单个二进制提供所有端点服务,支持优雅关闭。
关键特性¶
-
标准 HTTP/1.1:最大兼容性。
-
SSE 流式传输:服务器发送事件实现实时输出。
-
CORS 可配置:支持浏览器客户端。
-
健康端点:用于负载均衡器的
/health。
为什么不用 gRPC?¶
- HTTP 得到普遍支持
- 浏览器兼容性很重要
- 使用 curl 调试更简单
- 大多数 AI SDK 使用 HTTP/REST
错误处理哲学¶
决策¶
传播带上下文的错误,优雅地失败。
原则¶
-
保留 CLI 退出码:准确传播后端错误。
-
结构化错误:带错误详情的 JSON 格式。
-
优雅降级:并行模式下返回部分结果。
-
详细日志:需要时提供调试信息。
错误响应格式¶
{
"error": {
"code": "backend_error",
"message": "Claude CLI exited with code 1",
"backend": "claude",
"details": "rate limit exceeded"
}
}
总结表¶
| 决策 | 选择 | 关键原因 |
|---|---|---|
| 语言 | Go | 单二进制、出色的并发性 |
| CLI 框架 | Cobra | 行业标准、丰富功能 |
| HTTP 路由器 | Chi | 轻量级、惯用 Go |
| OpenAPI | Huma | 代码优先、类型安全 |
| 执行 | 子进程 | 零配置、始终最新 |
| API 格式 | 多种 | 框架兼容性 |
| 会话 | 基于文件的 JSON | 简洁性、可移植性 |
| 并发 | RWMutex + FileLock | 读密集型、跨进程安全 |
| 配置 | 级联 | 可预测、环境友好 |
| 服务器 | HTTP/SSE | 通用兼容性 |
未来考虑¶
MCP 服务器支持¶
MCP 支持已通过 clinvk mcp 落地,支持 stdio 与 http 两种 transport。
当前能力包括:
- 将 prompt、parallel、chain、compare、session 操作暴露为 MCP tools
- 面向本地与服务端部署场景的 transport 切换
- 在 HTTP transport 下可选暴露
/health端点
后续 MCP 方向:
- 增加面向远程部署的认证模式
- 扩展 MCP 生态互操作测试
- 提供更细粒度的 tool 暴露策略控制
额外后端¶
后端抽象允许在新 AI CLI 可用时添加它们。新后端的要求:
- CLI 支持非交互模式
- 结构化输出(首选 JSON)
- 会话管理(可选但首选)