Files
nofx/mcp/intro/BUILDER_PATTERN_BENEFITS.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

717 lines
17 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 模块中的应用价值
## 📋 目录
1. [当前实现的局限性](#当前实现的局限性)
2. [构建器模式的好处](#构建器模式的好处)
3. [实际应用场景](#实际应用场景)
4. [对比示例](#对比示例)
5. [是否需要引入](#是否需要引入)
---
## 当前实现的局限性
### 现状分析
**当前 buildMCPRequestBody 实现**:
```go
func (client *Client) buildMCPRequestBody(systemPrompt, userPrompt string) map[string]any {
messages := []map[string]string{}
if systemPrompt != "" {
messages = append(messages, map[string]string{
"role": "system",
"content": systemPrompt,
})
}
messages = append(messages, map[string]string{
"role": "user",
"content": userPrompt,
})
return map[string]interface{}{
"model": client.Model,
"messages": messages,
"temperature": client.config.Temperature,
"max_tokens": client.MaxTokens,
}
}
```
### 存在的限制
1. **只支持简单对话**
- ❌ 无法添加多轮对话历史
- ❌ 无法添加 assistant 回复
- ❌ 无法构建复杂的对话上下文
2. **参数固定**
- ❌ 无法动态添加可选参数(如 top_p、frequency_penalty
- ❌ 无法为单次请求自定义 temperature会影响全局配置
- ❌ 无法添加 function calling、tools 等高级功能
3. **扩展性差**
- ❌ 每次添加新参数都需要修改方法签名
- ❌ 参数列表会越来越长
- ❌ 子类重写时需要处理所有参数
---
## 构建器模式的好处
### 1. 🎯 **灵活性和可读性**
#### 当前方式(参数传递)
```go
// 问题:参数多了会很混乱
client.CallWithCustomParams(
"system prompt",
"user prompt",
0.8, // temperature - 这是什么?
2000, // max_tokens - 这是什么?
0.9, // top_p - 这是什么?
0.5, // frequency_penalty
nil, // stop sequences
false, // stream
)
```
#### 构建器方式
```go
// 清晰、自解释
request := NewRequestBuilder().
WithSystemPrompt("You are a helpful assistant").
WithUserPrompt("Tell me about Go").
WithTemperature(0.8).
WithMaxTokens(2000).
WithTopP(0.9).
Build()
result, err := client.CallWithRequest(request)
```
---
### 2. 📚 **支持复杂场景**
#### 场景1: 多轮对话
**当前方式**: 😢 不支持
```go
// ❌ 无法实现
client.CallWithMessages("system", "user prompt")
```
**构建器方式**: ✅ 支持
```go
request := NewRequestBuilder().
AddSystemMessage("You are a helpful assistant").
AddUserMessage("What is the weather?").
AddAssistantMessage("It's sunny today").
AddUserMessage("What about tomorrow?"). // 继续对话
WithTemperature(0.7).
Build()
```
#### 场景2: 函数调用Function Calling
**当前方式**: 😢 不支持
```go
// ❌ 无法添加 tools/functions
```
**构建器方式**: ✅ 支持
```go
request := NewRequestBuilder().
WithUserPrompt("What's the weather in Beijing?").
AddTool(Tool{
Type: "function",
Function: FunctionDef{
Name: "get_weather",
Description: "Get current weather",
Parameters: weatherParamsSchema,
},
}).
WithToolChoice("auto").
Build()
```
#### 场景3: 流式响应
**当前方式**: 😢 需要修改整个架构
```go
// ❌ CallWithMessages 不支持流式
```
**构建器方式**: ✅ 易于扩展
```go
request := NewRequestBuilder().
WithUserPrompt("Write a long story").
WithStream(true).
Build()
stream, err := client.CallStream(request)
for chunk := range stream {
fmt.Print(chunk)
}
```
---
### 3. 🔧 **易于扩展和维护**
#### 添加新参数
**当前方式**: 😢 破坏性修改
```go
// 需要修改方法签名(破坏现有代码)
func (client *Client) buildMCPRequestBody(
systemPrompt, userPrompt string,
// 新增参数会导致所有调用处都要修改
topP float64,
presencePenalty float64,
) map[string]any
```
**构建器方式**: ✅ 向后兼容
```go
// 只需添加新方法,不影响现有代码
func (b *RequestBuilder) WithPresencePenalty(p float64) *RequestBuilder {
b.presencePenalty = p
return b
}
// 旧代码不受影响
request := builder.WithUserPrompt("Hello").Build()
// 新代码可以使用新功能
request := builder.
WithUserPrompt("Hello").
WithPresencePenalty(0.6). // 新参数
Build()
```
---
### 4. 🎨 **可选参数处理**
**当前方式**: 😢 难以处理可选参数
```go
// 方案1: 传 nil/0 值(不优雅)
client.CallWithParams(system, user, 0, 0, nil, nil)
// 方案2: 使用选项模式(但每次调用都要传)
client.CallWithParams(system, user, WithTopP(0.9), WithPenalty(0.5))
// 方案3: 配置对象(需要创建临时对象)
config := &RequestConfig{
SystemPrompt: system,
UserPrompt: user,
TopP: 0.9,
}
```
**构建器方式**: ✅ 优雅处理
```go
// 只设置需要的参数,其他使用默认值
request := NewRequestBuilder().
WithUserPrompt("Hello").
// 不设置 temperature使用默认值
// 不设置 topP使用默认值
Build()
// 也可以全部自定义
request := NewRequestBuilder().
WithUserPrompt("Hello").
WithTemperature(0.8).
WithTopP(0.9).
WithMaxTokens(2000).
Build()
```
---
### 5. ✅ **类型安全和验证**
**当前方式**: 😢 运行时才发现错误
```go
// ❌ 编译时无法发现问题
client.CallWithMessages("", "") // 空 prompt
client.CallWithMessages("system", "user") // temperature 可能不合法
```
**构建器方式**: ✅ 提前验证
```go
type RequestBuilder struct {
messages []Message
temperature float64
maxTokens int
}
func (b *RequestBuilder) WithTemperature(t float64) *RequestBuilder {
if t < 0 || t > 2 {
panic("temperature must be between 0 and 2") // 或返回 error
}
b.temperature = t
return b
}
func (b *RequestBuilder) Build() (*Request, error) {
if len(b.messages) == 0 {
return nil, errors.New("at least one message is required")
}
if b.maxTokens <= 0 {
return nil, errors.New("maxTokens must be positive")
}
return &Request{...}, nil
}
```
---
## 实际应用场景
### 场景1: 量化交易 AI 顾问(多轮对话)
```go
// 构建包含市场数据的上下文对话
request := NewRequestBuilder().
AddSystemMessage("You are a quantitative trading advisor").
AddUserMessage("Analyze BTC trend").
AddAssistantMessage("BTC is in an upward trend based on...").
AddUserMessage("What about entry points?"). // 继续对话
WithTemperature(0.3). // 低温度,更精确
WithMaxTokens(1000).
Build()
analysis, err := client.CallWithRequest(request)
```
### 场景2: 代码生成(需要精确控制)
```go
request := NewRequestBuilder().
WithSystemPrompt("You are a Go expert").
WithUserPrompt("Generate a HTTP server").
WithTemperature(0.2). // 低温度,更确定性
WithTopP(0.1). // 低 top_p更聚焦
WithMaxTokens(2000).
WithStopSequences([]string{"```"}). // 遇到代码块结束符停止
Build()
```
### 场景3: 创意写作(需要随机性)
```go
request := NewRequestBuilder().
WithSystemPrompt("You are a creative writer").
WithUserPrompt("Write a sci-fi story").
WithTemperature(1.2). // 高温度,更创意
WithTopP(0.95). // 高 top_p更多样性
WithPresencePenalty(0.6). // 避免重复
WithFrequencyPenalty(0.5).
WithMaxTokens(4000).
Build()
```
### 场景4: 函数调用(工具使用)
```go
// 定义工具
weatherTool := Tool{
Type: "function",
Function: FunctionDef{
Name: "get_weather",
Description: "Get current weather for a location",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"location": map[string]any{
"type": "string",
"description": "City name",
},
},
"required": []string{"location"},
},
},
}
request := NewRequestBuilder().
WithUserPrompt("What's the weather in Beijing?").
AddTool(weatherTool).
WithToolChoice("auto").
Build()
response, err := client.CallWithRequest(request)
// 解析 response.ToolCalls 并执行实际的天气查询
```
---
## 对比示例
### 示例1: 基础用法
#### 当前实现
```go
result, err := client.CallWithMessages(
"You are a helpful assistant",
"What is Go?",
)
```
#### 构建器模式
```go
request := NewRequestBuilder().
WithSystemPrompt("You are a helpful assistant").
WithUserPrompt("What is Go?").
Build()
result, err := client.CallWithRequest(request)
```
**分析**: 基础用法下,构建器稍显冗长,但更清晰。
---
### 示例2: 复杂用法
#### 当前实现(假设扩展后)
```go
// 😢 参数太多,难以理解
result, err := client.CallWithMessagesAdvanced(
"system prompt",
"user prompt",
nil, // messages history?
0.8, // temperature
2000, // max_tokens
0.9, // top_p
0.5, // frequency_penalty
0.6, // presence_penalty
nil, // stop sequences
false, // stream
nil, // tools
"", // tool_choice
)
```
#### 构建器模式
```go
// ✅ 清晰、自解释
request := NewRequestBuilder().
WithSystemPrompt("system prompt").
WithUserPrompt("user prompt").
WithTemperature(0.8).
WithMaxTokens(2000).
WithTopP(0.9).
WithFrequencyPenalty(0.5).
WithPresencePenalty(0.6).
Build()
result, err := client.CallWithRequest(request)
```
**分析**: 复杂场景下,构建器模式优势明显。
---
## 是否需要引入?
### ✅ 建议引入的情况
1. **需要支持多轮对话**
- 聊天机器人
- 上下文相关的 AI 助手
2. **需要精细控制 AI 参数**
- 不同任务需要不同 temperature
- 需要使用 top_p、penalty 等高级参数
3. **需要使用 AI 高级功能**
- Function Calling / Tools
- 流式响应
- Vision API图片输入
4. **API 接口可能频繁变化**
- AI 提供商经常添加新参数
- 需要向后兼容
### ⚠️ 可以暂缓的情况
1. **只有简单的单轮对话**
- 当前 `CallWithMessages` 已足够
2. **参数固定不变**
- 所有请求使用相同配置
3. **团队规模小,代码量少**
- 引入新模式的学习成本 > 收益
---
## 推荐方案
### 方案1: 渐进式引入(推荐)
**第一阶段**: 保留现有 API新增构建器
```go
// 旧 API 继续工作(向后兼容)
result, err := client.CallWithMessages("system", "user")
// 新 API 提供高级功能
request := NewRequestBuilder().
WithUserPrompt("user").
WithTemperature(0.8).
Build()
result, err := client.CallWithRequest(request)
```
**第二阶段**: 逐步迁移
```go
// 在文档中推荐使用构建器
// 旧 API 标记为 Deprecated但不删除
```
### 方案2: 仅用于高级场景
只在需要复杂功能时使用构建器:
```go
// 简单场景:使用现有 API
client.CallWithMessages("system", "user")
// 复杂场景:使用构建器
client.CallWithRequest(
NewRequestBuilder().
AddConversationHistory(history).
AddUserMessage("new question").
WithTools(tools).
Build(),
)
```
---
## 实现示例
### 完整的构建器实现
```go
package mcp
type Message struct {
Role string `json:"role"`
Content string `json:"content"`
}
type Tool struct {
Type string `json:"type"`
Function FunctionDef `json:"function"`
}
type Request struct {
Model string `json:"model"`
Messages []Message `json:"messages"`
Temperature float64 `json:"temperature,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
TopP float64 `json:"top_p,omitempty"`
FrequencyPenalty float64 `json:"frequency_penalty,omitempty"`
PresencePenalty float64 `json:"presence_penalty,omitempty"`
Stop []string `json:"stop,omitempty"`
Tools []Tool `json:"tools,omitempty"`
ToolChoice string `json:"tool_choice,omitempty"`
Stream bool `json:"stream,omitempty"`
}
type RequestBuilder struct {
model string
messages []Message
temperature *float64
maxTokens *int
topP *float64
frequencyPenalty *float64
presencePenalty *float64
stop []string
tools []Tool
toolChoice string
stream bool
}
func NewRequestBuilder() *RequestBuilder {
return &RequestBuilder{
messages: make([]Message, 0),
}
}
func (b *RequestBuilder) WithModel(model string) *RequestBuilder {
b.model = model
return b
}
func (b *RequestBuilder) WithSystemPrompt(prompt string) *RequestBuilder {
if prompt != "" {
b.messages = append(b.messages, Message{
Role: "system",
Content: prompt,
})
}
return b
}
func (b *RequestBuilder) WithUserPrompt(prompt string) *RequestBuilder {
b.messages = append(b.messages, Message{
Role: "user",
Content: prompt,
})
return b
}
func (b *RequestBuilder) AddUserMessage(content string) *RequestBuilder {
return b.WithUserPrompt(content)
}
func (b *RequestBuilder) AddSystemMessage(content string) *RequestBuilder {
return b.WithSystemPrompt(content)
}
func (b *RequestBuilder) AddAssistantMessage(content string) *RequestBuilder {
b.messages = append(b.messages, Message{
Role: "assistant",
Content: content,
})
return b
}
func (b *RequestBuilder) AddMessage(role, content string) *RequestBuilder {
b.messages = append(b.messages, Message{
Role: role,
Content: content,
})
return b
}
func (b *RequestBuilder) AddConversationHistory(history []Message) *RequestBuilder {
b.messages = append(b.messages, history...)
return b
}
func (b *RequestBuilder) WithTemperature(t float64) *RequestBuilder {
if t < 0 || t > 2 {
panic("temperature must be between 0 and 2")
}
b.temperature = &t
return b
}
func (b *RequestBuilder) WithMaxTokens(tokens int) *RequestBuilder {
b.maxTokens = &tokens
return b
}
func (b *RequestBuilder) WithTopP(p float64) *RequestBuilder {
b.topP = &p
return b
}
func (b *RequestBuilder) WithFrequencyPenalty(p float64) *RequestBuilder {
b.frequencyPenalty = &p
return b
}
func (b *RequestBuilder) WithPresencePenalty(p float64) *RequestBuilder {
b.presencePenalty = &p
return b
}
func (b *RequestBuilder) WithStopSequences(sequences []string) *RequestBuilder {
b.stop = sequences
return b
}
func (b *RequestBuilder) AddTool(tool Tool) *RequestBuilder {
b.tools = append(b.tools, tool)
return b
}
func (b *RequestBuilder) WithToolChoice(choice string) *RequestBuilder {
b.toolChoice = choice
return b
}
func (b *RequestBuilder) WithStream(stream bool) *RequestBuilder {
b.stream = stream
return b
}
func (b *RequestBuilder) Build() (*Request, error) {
if len(b.messages) == 0 {
return nil, errors.New("at least one message is required")
}
req := &Request{
Model: b.model,
Messages: b.messages,
Stop: b.stop,
Tools: b.tools,
ToolChoice: b.toolChoice,
Stream: b.stream,
}
// 只设置非 nil 的可选参数
if b.temperature != nil {
req.Temperature = *b.temperature
}
if b.maxTokens != nil {
req.MaxTokens = *b.maxTokens
}
if b.topP != nil {
req.TopP = *b.topP
}
if b.frequencyPenalty != nil {
req.FrequencyPenalty = *b.frequencyPenalty
}
if b.presencePenalty != nil {
req.PresencePenalty = *b.presencePenalty
}
return req, nil
}
```
### Client 集成
```go
// 新增方法(不影响现有代码)
func (client *Client) CallWithRequest(req *Request) (string, error) {
// 使用 req 中的参数发送请求
// ...
}
```
---
## 总结
### 核心优势
1.**灵活性** - 轻松支持复杂场景
2.**可读性** - 代码自解释,易于理解
3.**可扩展性** - 添加新功能不破坏现有代码
4.**类型安全** - 编译时检查,提前发现错误
5.**向后兼容** - 可以与现有 API 共存
### 建议
- **当前阶段**: 如果只需要简单对话,现有实现已足够
- **未来扩展**: 当需要以下功能时再引入
- 多轮对话
- Function Calling
- 流式响应
- 精细参数控制
### 最佳实践
采用**渐进式引入**策略:
1. 保留现有 `CallWithMessages` API
2. 新增 `CallWithRequest` + 构建器
3. 在文档中推荐新 API但不强制迁移
4. 根据实际需求逐步完善构建器功能
这样既能保持向后兼容,又能为未来的功能扩展做好准备。