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

8.0 KiB
Raw Permalink Blame History

MCP 模块重构迁移指南

📋 重构概览

本次重构采用渐进式、向前兼容的设计,现有代码无需修改即可继续使用,同时提供了更强大的新 API。

重构目标

  • 100% 向前兼容 - 所有现有 API 继续工作
  • 模块独立 - 可作为独立 Go module 发布
  • 依赖可替换 - 日志、HTTP 客户端都可自定义
  • 易于测试 - 支持依赖注入和 mock
  • 配置灵活 - 支持选项模式 (Functional Options)

🔄 向前兼容保证

所有现有代码继续工作

// ✅ 这些代码无需修改,继续正常工作
mcpClient := mcp.New()
mcpClient.SetAPIKey(apiKey, url, model)

// ✅ 这些也继续工作
dsClient := mcp.NewDeepSeekClient()
qwenClient := mcp.NewQwenClient()

重要:虽然标记为 Deprecated,但这些函数会一直保留,不会被删除。


🆕 新特性使用指南

1. 基础用法(推荐)

// 新的推荐用法
client := mcp.NewClient(
    mcp.WithDeepSeekConfig("sk-xxx"),
    mcp.WithTimeout(60 * time.Second),
)

2. 自定义日志

// 使用自定义日志器(如 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 客户端

// 添加代理、追踪、自定义 TLS 等
customHTTP := &http.Client{
    Timeout: 30 * time.Second,
    Transport: &http.Transport{
        Proxy: http.ProxyFromEnvironment,
        TLSClientConfig: &tls.Config{/* ... */},
    },
}

client := mcp.NewClient(
    mcp.WithHTTPClient(customHTTP),
)

4. 测试场景

func TestMyCode(t *testing.T) {
    // Mock HTTP 客户端
    mockHTTP := &MockHTTPClient{
        // 返回预设的响应
    }

    // 禁用日志
    client := mcp.NewClient(
        mcp.WithHTTPClient(mockHTTP),
        mcp.WithLogger(mcp.NewNoopLogger()),
    )

    // 测试...
}

5. 组合多个选项

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: 继续使用现有代码(无需改动)

// 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(推荐)

// 升级后的代码(可选)
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: 添加自定义配置(高级)

// 添加自定义日志
customLogger := &MyZapLogger{zap.NewProduction()}

mcpClient := mcp.NewDeepSeekClientWithOptions(
    mcp.WithAPIKey(config.DeepSeekKey),
    mcp.WithLogger(customLogger),        // 自定义日志
    mcp.WithTimeout(90 * time.Second),   // 自定义超时
    mcp.WithMaxRetries(5),               // 自定义重试次数
)

🎯 实际使用场景

场景 1: 开发环境详细日志

// 开发环境:使用详细日志
devClient := mcp.NewClient(
    mcp.WithDeepSeekConfig(apiKey),
    mcp.WithLogger(&defaultLogger{}), // 详细日志
)

场景 2: 生产环境结构化日志

// 生产环境:使用 zap 结构化日志
zapLogger, _ := zap.NewProduction()
prodClient := mcp.NewClient(
    mcp.WithDeepSeekConfig(apiKey),
    mcp.WithLogger(&ZapLogger{zapLogger}),
)

场景 3: 测试环境 Mock

// 测试环境:Mock HTTP 响应
mockHTTP := &MockHTTPClient{
    Response: `{"choices":[{"message":{"content":"test"}}]}`,
}

testClient := mcp.NewClient(
    mcp.WithHTTPClient(mockHTTP),
    mcp.WithLogger(mcp.NewNoopLogger()), // 禁用日志
)

场景 4: 需要代理的网络环境

// 使用代理
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

module github.com/yourorg/mcp

go 1.21

// 无外部依赖!

使用方

import "github.com/yourorg/mcp"

client := mcp.NewClient(
    mcp.WithDeepSeekConfig("sk-xxx"),
)

🧪 测试支持

Mock 示例

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() 禁用日志

📚 进一步阅读


🤝 贡献

欢迎提交 issue 和 PR

如有问题,请联系:[your-email@example.com]