Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d33802c
feat: 增加工具统一输出截断与错误格式助手
Cai-Tang-www Mar 30, 2026
0f3dcf4
feat: 统一工具注册表错误输出格式
Cai-Tang-www Mar 30, 2026
ea592a6
feat: 调整工具注册表错误断言
Cai-Tang-www Mar 30, 2026
ab75710
feat: bash 工具统一错误输出与截断标记
Cai-Tang-www Mar 30, 2026
7e20c2e
feat: read_file 增加输出限制与统一错误格式
Cai-Tang-www Mar 30, 2026
0e2cfe5
feat: write_file 统一错误输出格式
Cai-Tang-www Mar 30, 2026
b1aedbd
feat: edit 工具统一错误输出格式
Cai-Tang-www Mar 30, 2026
7f1bd7f
feat: grep 输出截断与统一错误格式
Cai-Tang-www Mar 30, 2026
f889613
feat: glob 输出截断与统一错误格式
Cai-Tang-www Mar 30, 2026
69e286f
feat: webfetch 统一错误输出格式
Cai-Tang-www Mar 30, 2026
991297c
feat: 更新 webfetch 错误输出测试
Cai-Tang-www Mar 30, 2026
b56a1ef
pref(provider):优化provider架构
phantom5099 Mar 30, 2026
78bd2d0
test: 补齐工具与内置 provider 覆盖率
Cai-Tang-www Mar 30, 2026
46b9364
test: 补齐工具 patch 覆盖率
Cai-Tang-www Mar 30, 2026
84cc47e
feat:实现了多行输入
wynxing Mar 31, 2026
2fa603b
docs(guides):重新编辑两份provider指导文档
phantom5099 Mar 31, 2026
08d04ef
Merge pull request #93 from phantom5099/main
minorcell Mar 31, 2026
a489ccc
Merge pull request #90 from Cai-Tang-www/main
minorcell Mar 31, 2026
f70e0f9
fix:改成Enter发送,ctrl+j换行了
wynxing Mar 31, 2026
11798ef
feat:实现了多行输入
wynxing Mar 31, 2026
858dda9
fix:改成Enter发送,ctrl+j换行了
wynxing Mar 31, 2026
def862e
Merge branch 'feat' of https://github.com/Yumiue/neo-code into feat
wynxing Mar 31, 2026
ac28f24
fix:修复test
wynxing Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 185 additions & 85 deletions docs/guides/adding-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,121 +4,143 @@

## 架构概览

NeoCode 的 provider 分为两层
NeoCode 的 provider 架构采用**集中式配置 + 驱动复用**的设计

- **配置层**(`ProviderConfig`):提供 `base_url`、模型列表、API Key 环境变量等元数据
- **驱动层**(`DriverDefinition`):负责实际的 API 协议差异
### 两层分离

一个驱动可被多个 provider 复用(如 `gemini` 和 `openll` 都复用 `openai` 驱动)。
- **配置层**(`internal/config/builtin_providers.go`):集中管理所有 provider 的元数据
- Provider 名称、Driver 名称
- Base URL、默认模型、模型列表
- API Key 环境变量名

## 方式一:复用已有驱动(推荐)
- **驱动层**(`internal/provider/openai/` 等):负责实际的 API 协议实现
- 请求构造、响应解析
- 流式输出、Tool Call 处理

适用于 OpenAI 兼容接口。只需添加配置文件,无需编写新的驱动代码。
### 驱动复用

### 示例:添加 DeepSeek

**1. 创建 `internal/provider/deepseek/deepseek.go`:**
一个驱动可被多个 provider 复用。当前所有内置 provider 都复用 `openai` 驱动:

```go
package deepseek
// internal/config/builtin_providers.go
OpenAIProvider() // Driver: "openai"
GeminiProvider() // Driver: "openai" (OpenAI-compatible API)
OpenLLProvider() // Driver: "openai" (OpenAI-compatible API)
```

import (
"neo-code/internal/config"
)
## 方式一:添加 OpenAI 兼容 Provider(推荐)

适用于 OpenAI 兼容接口(大多数第三方服务)。只需在配置层添加,无需编写新驱动。

### 步骤:添加 DeepSeek

**1. 在 `internal/config/builtin_providers.go` 添加常量和配置:**

```go
const (
Name = "deepseek"
DriverName = "openai" // 复用 openai 驱动
DefaultBaseURL = "https://api.deepseek.com/v1"
DefaultModel = "deepseek-chat"
DefaultAPIKeyEnv = "DEEPSEEK_API_KEY"
// ... 现有常量 ...

DeepSeekName = "deepseek"
DeepSeekDefaultBaseURL = "https://api.deepseek.com/v1"
DeepSeekDefaultModel = "deepseek-chat"
DeepSeekDefaultAPIKeyEnv = "DEEPSEEK_API_KEY"
)

var builtinModels = []string{
DefaultModel,
var deepSeekModels = []string{
DeepSeekDefaultModel,
"deepseek-coder",
}

// BuiltinConfig 返回该 provider 的内建配置
func BuiltinConfig() config.ProviderConfig {
return config.ProviderConfig{
Name: Name,
Driver: DriverName,
BaseURL: DefaultBaseURL,
Model: DefaultModel,
Models: append([]string(nil), builtinModels...),
APIKeyEnv: DefaultAPIKeyEnv,
// DeepSeekProvider 返回 DeepSeek provider 的默认配置
func DeepSeekProvider() ProviderConfig {
return ProviderConfig{
Name: DeepSeekName,
Driver: "openai", // 复用 openai 驱动
BaseURL: DeepSeekDefaultBaseURL,
Model: DeepSeekDefaultModel,
Models: append([]string(nil), deepSeekModels...),
APIKeyEnv: DeepSeekDefaultAPIKeyEnv,
}
}
```

**2. 在 `internal/provider/builtin/builtin.go` 中添加该 provider:**
**2. 在 `DefaultProviders()` 中注册:**

```go
import "neo-code/internal/provider/deepseek"

func DefaultConfig() *config.Config {
cfg := config.Default()
defaultProvider := openai.BuiltinConfig()
cfg.Providers = []config.ProviderConfig{
defaultProvider,
gemini.BuiltinConfig(),
openll.BuiltinConfig(),
deepseek.BuiltinConfig(), // 新增
func DefaultProviders() []ProviderConfig {
return []ProviderConfig{
OpenAIProvider(),
GeminiProvider(),
OpenLLProvider(),
DeepSeekProvider(), // 新增
}
// ...
}
```

**3. 设置环境变量并测试:**

```bash
export DEEPSEEK_API_KEY="your-api-key"
go run ./cmd/neocode
```

### 优点

- ✅ 无需编写新代码,只需配置
- ✅ 自动继承 openai 驱动的所有功能(流式、Tool Call)
- ✅ 配置集中管理,易于维护
- ✅ 用户无需修改 YAML 文件

## 方式二:实现新驱动

适用于协议不兼容的厂商(如 Anthropic、Google 原生 API)。

### 示例:添加 Anthropic
### 步骤:添加 Anthropic

**1. 在 `internal/provider/anthropic/anthropic.go` 中实现核心类型:**
**1. 在 `internal/provider/anthropic/anthropic.go` 实现驱动:**

```go
package anthropic

import (
"context"
"net/http"

"neo-code/internal/config"
domain "neo-code/internal/provider"
)

const (
Name = "anthropic"
DriverName = "anthropic"
DefaultBaseURL = "https://api.anthropic.com/v1"
DefaultModel = "claude-sonnet-4-20250514"
DefaultAPIKeyEnv = "ANTHROPIC_API_KEY"
Name = "anthropic"
DefaultBaseURL = "https://api.anthropic.com/v1"
)

// Provider 实现 domain.Provider 接口
type Provider struct {
cfg config.ResolvedProviderConfig
client *http.Client
}

// New 构造函数,接收已解析的配置。
// New 构造函数
func New(cfg config.ResolvedProviderConfig) (*Provider, error) {
if err := cfg.Validate(); err != nil {
return nil, fmt.Errorf("anthropic provider: %w", err)
}
// ...
return &Provider{cfg: cfg, client: &http.Client{}}, nil
return &Provider{
cfg: cfg,
client: &http.Client{Timeout: 60 * time.Second},
}, nil
}

// Chat 实现 domain.Provider 接口。
// 必须支持流式输出(通过 events channel)和 tool calls。
// Chat 实现流式对话接口
func (p *Provider) Chat(ctx context.Context, req domain.ChatRequest, events chan<- domain.StreamEvent) (domain.ChatResponse, error) {
// 1. 将 domain.ChatRequest 转换为厂商特定的请求格式
// 2. 调用厂商 API(流式 SSE)
// 1. 将 domain.ChatRequest 转换为 Anthropic API 格式
// 2. 调用 Anthropic API(流式 SSE)
// 3. 解析响应,推送 StreamEventTextDelta / StreamEventToolCallStart
// 4. 返回 domain.ChatResponse
}

// Driver 返回驱动定义,供 Registry 注册使用。
// Driver 返回驱动定义
func Driver() domain.DriverDefinition {
return domain.DriverDefinition{
Name: Name,
Expand All @@ -127,24 +149,15 @@ func Driver() domain.DriverDefinition {
},
}
}

// BuiltinConfig 返回内建配置。
func BuiltinConfig() config.ProviderConfig {
return config.ProviderConfig{
Name: Name,
Driver: DriverName,
BaseURL: DefaultBaseURL,
Model: DefaultModel,
Models: []string{DefaultModel, "claude-opus-4-20250514"},
APIKeyEnv: DefaultAPIKeyEnv,
}
}
```

**2. 在 `internal/provider/builtin/builtin.go` 中注册驱动:**
**2. 在 `internal/provider/builtin/builtin.go` 注册驱动:**

```go
import "neo-code/internal/provider/anthropic"
import (
"neo-code/internal/provider/anthropic"
"neo-code/internal/provider/openai"
)

func Register(registry *provider.Registry) error {
if registry == nil {
Expand All @@ -153,33 +166,120 @@ func Register(registry *provider.Registry) error {
if err := registry.Register(openai.Driver()); err != nil {
return err
}
return registry.Register(anthropic.Driver()) // 新增驱动注册
return registry.Register(anthropic.Driver()) // 新增
}
```

**3. 在 `internal/config/builtin_providers.go` 添加配置:**

```go
const (
// ... 现有常量 ...

AnthropicName = "anthropic"
AnthropicDefaultBaseURL = "https://api.anthropic.com/v1"
AnthropicDefaultModel = "claude-sonnet-4-20250514"
AnthropicDefaultAPIKeyEnv = "ANTHROPIC_API_KEY"
)

var anthropicModels = []string{
AnthropicDefaultModel,
"claude-opus-4-20250514",
"claude-3-5-sonnet-20241022",
}

func AnthropicProvider() ProviderConfig {
return ProviderConfig{
Name: AnthropicName,
Driver: "anthropic", // 使用新的 anthropic 驱动
BaseURL: AnthropicDefaultBaseURL,
Model: AnthropicDefaultModel,
Models: append([]string(nil), anthropicModels...),
APIKeyEnv: AnthropicDefaultAPIKeyEnv,
}
}

func DefaultProviders() []ProviderConfig {
return []ProviderConfig{
OpenAIProvider(),
GeminiProvider(),
OpenLLProvider(),
AnthropicProvider(), // 新增
}
}
```

## 关键接口与类型

### 核心接口

| 类型 | 位置 | 说明 |
|------|------|------|
| `Provider` 接口 | `internal/provider/provider.go` | 核心接口,定义 `Chat` 方法 |
| `ChatRequest` | `internal/provider/types.go` | 请求结构:`Model`、`SystemPrompt`、`Messages`、`Tools` |
| `ChatResponse` | `internal/provider/types.go` | 响应结构:`Message`、`FinishReason`、`Usage` |
| `StreamEvent` | `internal/provider/provider.go` | 流式事件:`text_delta` 和 `tool_call_start` |
| `ProviderConfig` | `internal/config/model.go` | 配置层:`Name`、`Driver`、`BaseURL`、`Model`、`Models`、`APIKeyEnv` |
| `DriverDefinition` | `internal/provider/registry.go` | 驱动层:`Name` + `Build` 构造函数 |
| `Provider` | `internal/provider/types.go` | 核心接口,定义 `Chat` 方法 |
| `DriverDefinition` | `internal/provider/registry.go` | 驱动定义:`Name` + `Build` 构造函数 |
| `Registry` | `internal/provider/registry.go` | 驱动注册中心 |

### 数据结构

| 类型 | 位置 | 说明 |
|------|------|------|
| `ChatRequest` | `internal/provider/types.go` | 请求:`Model`、`SystemPrompt`、`Messages`、`Tools` |
| `ChatResponse` | `internal/provider/types.go` | 响应:`Message`、`FinishReason`、`Usage` |
| `StreamEvent` | `internal/provider/types.go` | 流式事件:`TextDelta`、`ToolCallStart` |
| `ProviderConfig` | `internal/config/model.go` | 配置:`Name`、`Driver`、`BaseURL`、`Model`、`Models`、`APIKeyEnv` |

## 设计约束

**必须遵守**:
### 必须遵守

✅ **配置集中管理**
- 所有内置 provider 配置统一在 `internal/config/builtin_providers.go`
- 不再为每个 provider 创建独立的包

✅ **API Key 安全**
- 只从环境变量读取,不写入 `config.yaml`
- 不硬编码在源码中

✅ **驱动职责清晰**
- 驱动只负责协议构造与响应解析
- 不持有模型列表、base_url 等配置信息

✅ **架构分层**
- 厂商差异收敛在 `internal/provider/` 内
- `runtime`、`tui` 等上层模块只依赖统一的 `Provider` 接口
- `base_url` 不在 TUI 中展示给用户

### 最佳实践

1. **优先复用现有驱动**
- 大多数 OpenAI 兼容服务无需编写新驱动
- 只需在配置层添加即可

2. **配置即代码**
- provider 配置随代码版本发布
- 用户无需手动配置 providers 列表

3. **测试覆盖**
- 新驱动必须添加完整的单元测试
- 使用 `httptest.NewServer` 模拟 HTTP 调用
- 不使用真实 API Key

## 示例:当前内置 Provider

```go
// internal/config/builtin_providers.go

func DefaultProviders() []ProviderConfig {
return []ProviderConfig{
OpenAIProvider(), // OpenAI 官方 API
GeminiProvider(), // Google Gemini (OpenAI-compatible)
OpenLLProvider(), // OpenLL 服务 (OpenAI-compatible)
}
}
```

- **API Key 只从环境变量读取**,不写入 `config.yaml`,不硬编码在源码中
- **驱动层不持有模型列表**,`Models` 完全由配置层(`ProviderConfig.Models`)控制
- **厂商差异收敛在 `internal/provider/` 内**,`runtime`、`tui` 等上层模块只依赖统一的 `Provider` 接口
- **`base_url` 不向用户暴露**,用户在 TUI 中只能看到 provider 名称和模型列表
所有内置 provider 都复用 `openai` 驱动,配置集中在 `builtin_providers.go`。

## 相关文档

- [Provider Interface Target Design](../provider-interface-target-design.md)
- [Provider Module Interface](../provider-module-interface.md)
- [Provider Schema Strategy](../provider-schema-strategy.md)
- [Provider Architecture Optimization PR](../provider-architecture-optimization-pr.md)
Loading
Loading