Files
nofx/mcp/intro/MIGRATION_GUIDE.md
Shui b60383f22b refactor(mcp) (#1042)
* improve(interface): replace with interface
* feat(mcp): 添加构建器模式支持
新增功能:
- RequestBuilder 构建器,支持流式 API
- 多轮对话支持(AddAssistantMessage)
- Function Calling / Tools 支持
- 精细参数控制(temperature, top_p, penalties 等)
- 3个预设场景(Chat, CodeGen, CreativeWriting)
- 完整的测试套件(19个新测试)
修复问题:
- Config 字段未使用(MaxRetries、Temperature 等)
- DeepSeek/Qwen SetAPIKey 的冗余 nil 检查
向后兼容:
- 保留 CallWithMessages API
- 新增 CallWithRequest API
测试:
- 81 个测试全部通过
- 覆盖率 80.6%
Co-Authored-By: tinkle-community <tinklefund@gmail.com>
---------
Co-authored-by: zbhan <zbhan@freewheel.tv>
Co-authored-by: tinkle-community <tinklefund@gmail.com>
2025-11-15 23:04:53 -05:00

362 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MCP 模块重构迁移指南
## 📋 重构概览
本次重构采用**渐进式、向前兼容**的设计,现有代码**无需修改**即可继续使用,同时提供了更强大的新 API。
### 重构目标
-**100% 向前兼容** - 所有现有 API 继续工作
-**模块独立** - 可作为独立 Go module 发布
-**依赖可替换** - 日志、HTTP 客户端都可自定义
-**易于测试** - 支持依赖注入和 mock
-**配置灵活** - 支持选项模式 (Functional Options)
---
## 🔄 向前兼容保证
### ✅ 所有现有代码继续工作
```go
// ✅ 这些代码无需修改,继续正常工作
mcpClient := mcp.New()
mcpClient.SetAPIKey(apiKey, url, model)
// ✅ 这些也继续工作
dsClient := mcp.NewDeepSeekClient()
qwenClient := mcp.NewQwenClient()
```
**重要**:虽然标记为 `Deprecated`,但这些函数会一直保留,不会被删除。
---
## 🆕 新特性使用指南
### 1. 基础用法(推荐)
```go
// 新的推荐用法
client := mcp.NewClient(
mcp.WithDeepSeekConfig("sk-xxx"),
mcp.WithTimeout(60 * time.Second),
)
```
### 2. 自定义日志
```go
// 使用自定义日志器(如 zap, logrus
type MyLogger struct {
zapLogger *zap.Logger
}
func (l *MyLogger) Info(msg string, args ...any) {
l.zapLogger.Sugar().Infof(msg, args...)
}
// 注入自定义日志器
client := mcp.NewClient(
mcp.WithLogger(&MyLogger{zapLogger}),
)
```
### 3. 自定义 HTTP 客户端
```go
// 添加代理、追踪、自定义 TLS 等
customHTTP := &http.Client{
Timeout: 30 * time.Second,
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{/* ... */},
},
}
client := mcp.NewClient(
mcp.WithHTTPClient(customHTTP),
)
```
### 4. 测试场景
```go
func TestMyCode(t *testing.T) {
// Mock HTTP 客户端
mockHTTP := &MockHTTPClient{
// 返回预设的响应
}
// 禁用日志
client := mcp.NewClient(
mcp.WithHTTPClient(mockHTTP),
mcp.WithLogger(mcp.NewNoopLogger()),
)
// 测试...
}
```
### 5. 组合多个选项
```go
client := mcp.NewDeepSeekClientWithOptions(
mcp.WithAPIKey("sk-xxx"),
mcp.WithLogger(customLogger),
mcp.WithTimeout(60 * time.Second),
mcp.WithMaxRetries(5),
mcp.WithMaxTokens(4000),
)
```
---
## 📊 API 对比表
### 构造函数对比
| 旧 API (仍可用) | 新 API (推荐) | 说明 |
|----------------|--------------|------|
| `mcp.New()` | `mcp.NewClient(opts...)` | 支持选项模式 |
| `mcp.NewDeepSeekClient()` | `mcp.NewDeepSeekClientWithOptions(opts...)` | 支持自定义配置 |
| `mcp.NewQwenClient()` | `mcp.NewQwenClientWithOptions(opts...)` | 支持自定义配置 |
### 配置选项
| 选项函数 | 说明 | 使用示例 |
|---------|------|---------|
| `WithLogger(logger)` | 自定义日志器 | `WithLogger(zapLogger)` |
| `WithHTTPClient(client)` | 自定义 HTTP 客户端 | `WithHTTPClient(customHTTP)` |
| `WithTimeout(duration)` | 设置超时 | `WithTimeout(60*time.Second)` |
| `WithMaxRetries(n)` | 设置重试次数 | `WithMaxRetries(5)` |
| `WithMaxTokens(n)` | 设置最大 token | `WithMaxTokens(4000)` |
| `WithTemperature(t)` | 设置温度参数 | `WithTemperature(0.7)` |
| `WithAPIKey(key)` | 设置 API Key | `WithAPIKey("sk-xxx")` |
| `WithDeepSeekConfig(key)` | 快速配置 DeepSeek | `WithDeepSeekConfig("sk-xxx")` |
| `WithQwenConfig(key)` | 快速配置 Qwen | `WithQwenConfig("sk-xxx")` |
---
## 🔧 迁移步骤
### Phase 1: 继续使用现有代码(无需改动)
```go
// trader/auto_trader.go 中的现有代码
mcpClient := mcp.New()
if config.AIModel == "qwen" {
mcpClient = mcp.NewQwenClient()
mcpClient.SetAPIKey(config.QwenKey, config.CustomAPIURL, config.CustomModelName)
} else {
mcpClient = mcp.NewDeepSeekClient()
mcpClient.SetAPIKey(config.DeepSeekKey, config.CustomAPIURL, config.CustomModelName)
}
// ✅ 继续工作,无需修改
```
### Phase 2: 可选升级到新 API推荐
```go
// 升级后的代码(可选)
var mcpClient mcp.AIClient
if config.AIModel == "qwen" {
mcpClient = mcp.NewQwenClientWithOptions(
mcp.WithAPIKey(config.QwenKey),
mcp.WithBaseURL(config.CustomAPIURL),
mcp.WithModel(config.CustomModelName),
)
} else {
mcpClient = mcp.NewDeepSeekClientWithOptions(
mcp.WithAPIKey(config.DeepSeekKey),
mcp.WithBaseURL(config.CustomAPIURL),
mcp.WithModel(config.CustomModelName),
)
}
```
### Phase 3: 添加自定义配置(高级)
```go
// 添加自定义日志
customLogger := &MyZapLogger{zap.NewProduction()}
mcpClient := mcp.NewDeepSeekClientWithOptions(
mcp.WithAPIKey(config.DeepSeekKey),
mcp.WithLogger(customLogger), // 自定义日志
mcp.WithTimeout(90 * time.Second), // 自定义超时
mcp.WithMaxRetries(5), // 自定义重试次数
)
```
---
## 🎯 实际使用场景
### 场景 1: 开发环境详细日志
```go
// 开发环境:使用详细日志
devClient := mcp.NewClient(
mcp.WithDeepSeekConfig(apiKey),
mcp.WithLogger(&defaultLogger{}), // 详细日志
)
```
### 场景 2: 生产环境结构化日志
```go
// 生产环境:使用 zap 结构化日志
zapLogger, _ := zap.NewProduction()
prodClient := mcp.NewClient(
mcp.WithDeepSeekConfig(apiKey),
mcp.WithLogger(&ZapLogger{zapLogger}),
)
```
### 场景 3: 测试环境 Mock
```go
// 测试环境Mock HTTP 响应
mockHTTP := &MockHTTPClient{
Response: `{"choices":[{"message":{"content":"test"}}]}`,
}
testClient := mcp.NewClient(
mcp.WithHTTPClient(mockHTTP),
mcp.WithLogger(mcp.NewNoopLogger()), // 禁用日志
)
```
### 场景 4: 需要代理的网络环境
```go
// 使用代理
proxyURL, _ := url.Parse("http://proxy.company.com:8080")
proxyClient := &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
}
client := mcp.NewClient(
mcp.WithDeepSeekConfig(apiKey),
mcp.WithHTTPClient(proxyClient),
)
```
---
## 📦 作为独立模块发布
重构后mcp 模块可以独立发布:
### go.mod
```go
module github.com/yourorg/mcp
go 1.21
// 无外部依赖!
```
### 使用方
```go
import "github.com/yourorg/mcp"
client := mcp.NewClient(
mcp.WithDeepSeekConfig("sk-xxx"),
)
```
---
## 🧪 测试支持
### Mock 示例
```go
package mypackage_test
import (
"testing"
"github.com/stretchr/testify/assert"
"nofx/mcp"
)
type MockHTTPClient struct {
Response string
Error error
}
func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
if m.Error != nil {
return nil, m.Error
}
return &http.Response{
StatusCode: 200,
Body: io.NopCloser(strings.NewReader(m.Response)),
}, nil
}
func TestAIIntegration(t *testing.T) {
// Arrange
mockHTTP := &MockHTTPClient{
Response: `{"choices":[{"message":{"content":"success"}}]}`,
}
client := mcp.NewClient(
mcp.WithHTTPClient(mockHTTP),
mcp.WithLogger(mcp.NewNoopLogger()),
)
// Act
result, err := client.CallWithMessages("system", "user")
// Assert
assert.NoError(t, err)
assert.Equal(t, "success", result)
}
```
---
## ⚠️ 注意事项
1. **向前兼容性**
- 所有 `Deprecated` 的 API 会永久保留
- 现有代码可以继续使用,不会被破坏
2. **渐进式迁移**
- 不需要一次性迁移所有代码
- 可以逐步采用新 API
3. **配置优先级**
- 用户传入的选项优先级最高
- 环境变量次之
- 默认配置最低
4. **日志器接口**
- 可以适配任何日志库zap, logrus, etc.
- 测试时可以使用 `NewNoopLogger()` 禁用日志
---
## 📚 进一步阅读
- [选项模式详解](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
- [依赖注入最佳实践](https://go.dev/blog/wire)
- [Go 接口设计原则](https://go.dev/blog/laws-of-reflection)
---
## 🤝 贡献
欢迎提交 issue 和 PR
如有问题,请联系:[your-email@example.com]