Files
yoyo/AGENTS.md

767 lines
20 KiB
Markdown
Raw Normal View History

# AGENTS.md - YOYO翻译工具开发指南
## 项目概述
YOYO是一个命令行翻译工具使用Go语言编写采用面向对象设计模式。它通过调用在线大模型API结合不同的Prompt配置实现多样化的翻译特色。
## OOP设计模式
### 核心类设计
项目采用以下面向对象设计,结合工厂模式和策略模式:
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Config │ │ Provider │ │ Translator │
│ (全局配置) │──────│ (厂商接口) │──────│ (核心翻译) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ConfigLoader │ │ ProviderFactory │ │ TranslationTask │
│ (配置加载) │ │ (工厂模式) │ │ (任务管理) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### 1. 全局配置类 (Config)
负责读取YAML配置文件提供默认值。
```go
// internal/config/config.go
package config
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// Config 全局配置结构
type Config struct {
// 全局设置
DefaultProvider string `yaml:"default_provider"`
DefaultModel string `yaml:"default_model"`
Timeout int `yaml:"timeout"` // 秒
// 厂商配置
Providers map[string]ProviderConfig `yaml:"providers"`
// Prompt配置
Prompts map[string]string `yaml:"prompts"`
}
// ProviderConfig 厂商配置
type ProviderConfig struct {
APIHost string `yaml:"api_host"`
APIKey string `yaml:"api_key"`
Model string `yaml:"model"`
Enabled bool `yaml:"enabled"`
}
// ConfigLoader 配置加载器接口
type ConfigLoader interface {
Load(path string) (*Config, error)
Save(config *Config, path string) error
}
// YAMLConfigLoader YAML配置加载器实现
type YAMLConfigLoader struct{}
// Load 加载YAML配置文件
func (l *YAMLConfigLoader) Load(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("读取配置文件失败: %w", err)
}
config := &Config{}
if err := yaml.Unmarshal(data, config); err != nil {
return nil, fmt.Errorf("解析配置文件失败: %w", err)
}
// 设置默认值
config.setDefaults()
return config, nil
}
// setDefaults 设置默认值
func (c *Config) setDefaults() {
if c.DefaultProvider == "" {
c.DefaultProvider = "siliconflow"
}
if c.Timeout <= 0 {
c.Timeout = 30
}
// 为每个厂商设置默认值
for name, provider := range c.Providers {
if provider.Model == "" {
provider.Model = "gpt-3.5-turbo"
c.Providers[name] = provider
}
}
}
```
### 2. 大模型厂商接口 (Provider)
定义统一的厂商接口,采用策略模式。
```go
// internal/provider/provider.go
package provider
import (
"context"
)
// Provider 厂商接口
type Provider interface {
// Translate 调用厂商API进行翻译
Translate(ctx context.Context, req *TranslateRequest) (*TranslateResponse, error)
// Name 返回厂商名称
Name() string
// Validate 验证配置是否有效
Validate() error
}
// TranslateRequest 翻译请求
type TranslateRequest struct {
Text string `json:"text"`
FromLang string `json:"from_lang"`
ToLang string `json:"to_lang"`
Prompt string `json:"prompt"`
Model string `json:"model"`
Options map[string]interface{} `json:"options"`
}
// TranslateResponse 翻译响应
type TranslateResponse struct {
Text string `json:"text"`
FromLang string `json:"from_lang"`
ToLang string `json:"to_lang"`
Model string `json:"model"`
Usage *Usage `json:"usage"`
RawResponse []byte `json:"raw_response,omitempty"`
}
// Usage 用量统计
type Usage struct {
PromptTokens int `json:"prompt_tokens"`
CompletionTokens int `json:"completion_tokens"`
TotalTokens int `json:"total_tokens"`
}
```
### 3. 具体厂商实现示例
```go
// internal/provider/siliconflow.go
package provider
import (
"context"
"encoding/json"
"net/http"
)
// SiliconFlowProvider 硅基流动厂商实现
type SiliconFlowProvider struct {
config ProviderConfig
client *http.Client
}
// ProviderConfig 厂商配置从config包导入
type ProviderConfig struct {
APIHost string
APIKey string
Model string
}
// NewSiliconFlowProvider 创建硅基流动厂商实例
func NewSiliconFlowProvider(config ProviderConfig) *SiliconFlowProvider {
return &SiliconFlowProvider{
config: config,
client: &http.Client{},
}
}
// Name 返回厂商名称
func (p *SiliconFlowProvider) Name() string {
return "siliconflow"
}
// Validate 验证配置
func (p *SiliconFlowProvider) Validate() error {
if p.config.APIKey == "" {
return fmt.Errorf("siliconflow: API key 不能为空")
}
if p.config.APIHost == "" {
p.config.APIHost = "https://api.siliconflow.cn/v1"
}
return nil
}
// Translate 调用硅基流动API
func (p *SiliconFlowProvider) Translate(ctx context.Context, req *TranslateRequest) (*TranslateResponse, error) {
// 实现具体的API调用逻辑
// 1. 构建请求体
// 2. 发送HTTP请求
// 3. 解析响应
// 示例代码(简化)
url := p.config.APIHost + "/chat/completions"
requestBody := map[string]interface{}{
"model": p.config.Model,
"messages": []map[string]string{
{"role": "user", "content": req.Text},
},
}
// 实际实现需要完整的HTTP客户端代码
return &TranslateResponse{
Text: "翻译结果",
FromLang: req.FromLang,
ToLang: req.ToLang,
Model: p.config.Model,
}, nil
}
```
### 4. 工厂模式 - ProviderFactory
```go
// internal/provider/factory.go
package provider
import (
"fmt"
)
// ProviderFactory 厂商工厂
type ProviderFactory struct {
providers map[string]func(ProviderConfig) Provider
}
// NewProviderFactory 创建工厂实例
func NewProviderFactory() *ProviderFactory {
factory := &ProviderFactory{
providers: make(map[string]func(ProviderConfig) Provider),
}
// 注册所有厂商
factory.Register("siliconflow", NewSiliconFlowProvider)
factory.Register("volcano", NewVolcanoEngineProvider)
factory.Register("national", NewNationalSupercomputingProvider)
factory.Register("qwen", NewQwenProvider)
factory.Register("openai", NewOpenAICompatibleProvider)
return factory
}
// Register 注册厂商构造函数
func (f *ProviderFactory) Register(name string, creator func(ProviderConfig) Provider) {
f.providers[name] = creator
}
// Create 创建厂商实例
func (f *ProviderFactory) Create(name string, config ProviderConfig) (Provider, error) {
creator, exists := f.providers[name]
if !exists {
return nil, fmt.Errorf("不支持的厂商: %s", name)
}
provider := creator(config)
if err := provider.Validate(); err != nil {
return nil, fmt.Errorf("厂商配置验证失败: %w", err)
}
return provider, nil
}
```
### 5. 核心翻译类 (Translator)
```go
// internal/translator/translator.go
package translator
import (
"context"
"time"
)
// Translator 核心翻译类
type Translator struct {
config *config.Config
provider provider.Provider
prompt *PromptManager
}
// NewTranslator 创建翻译器实例
func NewTranslator(config *config.Config, provider provider.Provider) *Translator {
return &Translator{
config: config,
provider: provider,
prompt: NewPromptManager(config.Prompts),
}
}
// Translate 执行翻译
func (t *Translator) Translate(ctx context.Context, text string, options *TranslateOptions) (*TranslateResult, error) {
// 设置超时
timeoutCtx, cancel := context.WithTimeout(ctx, time.Duration(t.config.Timeout)*time.Second)
defer cancel()
// 构建请求
req := &provider.TranslateRequest{
Text: text,
FromLang: options.FromLang,
ToLang: options.ToLang,
Prompt: t.prompt.GetPrompt(options.PromptName),
Model: t.selectModel(options.Model),
}
// 调用厂商API
resp, err := t.provider.Translate(timeoutCtx, req)
if err != nil {
return nil, fmt.Errorf("翻译失败: %w", err)
}
// 构建结果
return &TranslateResult{
Original: text,
Translated: resp.Text,
FromLang: resp.FromLang,
ToLang: resp.ToLang,
Model: resp.Model,
Usage: resp.Usage,
}, nil
}
// selectModel 选择模型
func (t *Translator) selectModel(model string) string {
if model != "" {
return model
}
return t.config.DefaultModel
}
// TranslateOptions 翻译选项
type TranslateOptions struct {
FromLang string
ToLang string
PromptName string
Model string
Temperature float64
}
// TranslateResult 翻译结果
type TranslateResult struct {
Original string
Translated string
FromLang string
ToLang string
Model string
Usage *provider.Usage
}
```
### 6. 设计模式应用
#### 工厂模式
- `ProviderFactory` 用于创建不同厂商实例
- 统一创建接口,隐藏具体实现细节
#### 策略模式
- `Provider` 接口定义统一行为
- 每个厂商实现不同的API调用策略
- 运行时动态选择策略
#### 单例模式
- 全局配置通常只需要一个实例
- 可通过包级变量或sync.Once实现
#### 依赖注入
- `Translator` 依赖 `Provider` 接口,而不是具体实现
- 通过构造函数注入依赖
## 项目结构
```
yoyo/
├── cmd/
│ └── yoyo/ # CLI入口
├── internal/
│ ├── config/ # 全局配置
│ │ ├── config.go
│ │ └── loader.go
│ ├── provider/ # 厂商实现
│ │ ├── provider.go # 接口定义
│ │ ├── factory.go # 工厂模式
│ │ ├── siliconflow.go
│ │ ├── volcano.go
│ │ ├── national.go
│ │ ├── qwen.go
│ │ └── openai.go
│ ├── translator/ # 核心翻译
│ │ ├── translator.go
│ │ └── prompt.go
│ └── prompt/ # Prompt管理
├── pkg/ # 公共工具
├── configs/ # 配置文件目录
│ └── config.yaml
├── go.mod
└── go.sum
```
## 配置文件示例 (YAML)
```yaml
# config.yaml
default_provider: "siliconflow"
default_model: "gpt-3.5-turbo"
timeout: 30
providers:
siliconflow:
api_host: "https://api.siliconflow.cn/v1"
api_key: "${SILICONFLOW_API_KEY}"
model: "siliconflow-base"
enabled: true
volcano:
api_host: "https://api.volcengine.com/v1"
api_key: "${VOLCANO_API_KEY}"
model: "volcano-chat"
enabled: true
national:
api_host: "https://api.nsc.gov.cn/v1"
api_key: "${NATIONAL_API_KEY}"
model: "nsc-base"
enabled: false
qwen:
api_host: "https://dashscope.aliyuncs.com/compatible-mode/v1"
api_key: "${QWEN_API_KEY}"
model: "qwen-turbo"
enabled: true
openai:
api_host: "https://api.openai.com/v1"
api_key: "${OPENAI_API_KEY}"
model: "gpt-3.5-turbo"
enabled: true
prompts:
technical: "你是一位专业的技术翻译,请准确翻译以下技术文档,保持专业术语的准确性。"
creative: "你是一位富有创造力的翻译家,请用优美流畅的语言翻译以下内容。"
academic: "你是一位学术翻译专家,请用严谨的学术语言翻译以下内容。"
simple: "请用简单易懂的语言翻译以下内容。"
```
## 开发顺序建议
1. 实现 `Config``ConfigLoader`
2. 实现 `Provider` 接口和工厂
3. 实现至少一个厂商如SiliconFlow
4. 实现 `Translator` 核心类
5. 集成测试
6. 实现其他厂商
## 测试策略
```go
// 内部测试示例
func TestTranslator_Translate(t *testing.T) {
// Mock provider
mockProvider := &MockProvider{
TranslateFunc: func(ctx context.Context, req *provider.TranslateRequest) (*provider.TranslateResponse, error) {
return &provider.TranslateResponse{Text: "翻译结果"}, nil
},
}
translator := NewTranslator(testConfig, mockProvider)
result, err := translator.Translate(context.Background(), "Hello", &TranslateOptions{
ToLang: "zh",
})
if err != nil {
t.Fatalf("翻译失败: %v", err)
}
if result.Translated != "翻译结果" {
t.Errorf("期望 '翻译结果', 得到 '%s'", result.Translated)
}
}
```
## 错误处理最佳实践
- 每个厂商实现应定义具体的错误类型
- 使用 `%w` 包装错误以保留原始错误信息
- 提供清晰的错误消息帮助调试
## 安全注意事项
- API密钥使用环境变量
- 配置文件中不存储真实密钥
- 使用 `os.Getenv` 读取敏感信息
- 定期轮换API密钥
## 开发规范
### 文档管理
#### 文档文件列表
1. **why.md** - 项目初衷文档(仅用户编辑)
2. **taolun.md** - 讨论记录(时间轴格式)
3. **changelog.md** - 版本记录(包含讨论链接)
4. **memory.md** - 知识纠正(踩坑记录)
#### 文档协作规范
| 文档 | 编辑者 | 内容 | 更新频率 |
|------|--------|------|----------|
| why.md | 用户 | 项目初衷、愿景、目标 | 随时 |
| taolun.md | AI+用户 | 讨论记录、决策过程 | 每次重要讨论后 |
| changelog.md | AI | 版本变更、任务状态 | 每次版本更新 |
| memory.md | AI | 经验总结、术语定义 | 遇到问题后 |
#### 特殊文档说明
**why.md**:
- 项目初衷文档,只能由用户编辑
- AI不应修改此文件内容
- 用于记录创始人的个人想法和项目愿景
- 位置:项目根目录
**AI责任边界**:
- ✅ 可以编辑taolun.md、changelog.md、memory.md
- ❌ 不应编辑why.md、用户个人配置文件
- ✅ 可以建议why.md的内容结构但不强制
### 文档链接
- 项目初衷: [why.md](why.md)
- 讨论记录: [taolun.md](taolun.md)
- 版本记录: [changelog.md](changelog.md)
- 知识纠正: [memory.md](memory.md)
### 版本号管理
- 格式:主版本.次版本.修订版本(00-99)
- 更新时机测试完成后git操作前
- 递增规则:小修复第三位+1新功能第二位+1重大变更第一位+1
### 分支策略
- main: 稳定上线版
- dev: 开发分支
- 功能开发从dev创建功能分支
### 提交规范
- 提交前更新相关文档
- 使用清晰的提交信息
- 版本更新时打标签
## 开发命令
### 构建
```bash
# 构建二进制文件
go build -o yoyo ./cmd/yoyo
# 交叉编译Linux
GOOS=linux GOARCH=amd64 go build -o yoyo-linux ./cmd/yoyo
# 交叉编译macOS
GOOS=darwin GOARCH=amd64 go build -o yoyo-mac ./cmd/yoyo
```
### 测试
```bash
# 运行所有测试
go test ./...
# 运行特定包的测试
go test ./internal/translator
# 运行单个测试函数
go test -run TestTranslate ./internal/translator
# 运行测试并显示详细输出
go test -v ./...
# 运行基准测试
go test -bench=. ./...
```
### 代码检查与格式化
```bash
# 代码格式化(必须)
gofmt -w .
# 代码检查
go vet ./...
# 使用golangci-lint推荐
golangci-lint run
# 生成依赖图
go mod graph
```
### 运行工具
```bash
# 直接运行
go run ./cmd/yoyo "This is translation content..."
# 使用构建的二进制
./yoyo "This is translation content..."
# 指定翻译模式
./yoyo --mode=technical "API documentation text"
```
## 代码风格指南
### 命名约定
- **包名**:小写单词,简洁明了(如`translator``config`
- **函数/方法名**:驼峰命名,动词开头(如`TranslateText``LoadConfig`
- **变量名**:驼峰命名,简洁(如`inputText``apiResponse`
- **常量**:全大写加下划线(如`MAX_RETRY_COUNT`
- **接口名**:以`-er`结尾或描述性名称(如`Translator``ConfigLoader`
### 导入顺序
```go
import (
// 标准库
"context"
"fmt"
// 第三方包
"github.com/spf13/cobra"
// 项目内部包
"github.com/username/yoyo/internal/api"
"github.com/username/yoyo/internal/config"
)
```
### 错误处理
```go
// 包装错误以提供更多上下文
if err := loadConfig(); err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// 定义哨兵错误
var (
ErrInvalidAPIKey = errors.New("invalid API key")
ErrNetworkFailed = errors.New("network request failed")
)
// 错误检查使用errors.Is
if errors.Is(err, ErrInvalidAPIKey) {
// 处理特定错误
}
```
### 类型使用
- 优先使用具体类型,必要时使用接口
- 避免使用`interface{}`,使用泛型或具体类型
- 结构体字段使用大写开头(导出)或小写开头(私有)
- 为复杂类型添加文档注释
### 并发处理
```go
// 使用context传递取消信号
func Translate(ctx context.Context, text string) (string, error) {
select {
case <-ctx.Done():
return "", ctx.Err()
default:
// 继续翻译
}
}
// 使用goroutine和channel时注意资源清理
go func() {
defer close(doneCh)
// 执行任务
}()
```
### 注释规范
- 包注释:描述包的功能和用途
- 函数注释:描述函数功能、参数、返回值
- 导出类型/函数必须有注释
- 使用Godoc格式
## 测试指南
- 每个公共函数都应有对应的测试
- 测试函数命名:`TestFunctionName`
- 使用表格驱动测试
- Mock外部依赖
- 测试文件放在同一目录,以`_test.go`结尾
```go
// 示例测试
func TestTranslator_Translate(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"simple text", "hello", "你好"},
{"empty string", "", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := translator.Translate(tt.input)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if result != tt.expected {
t.Errorf("got %v, want %v", result, tt.expected)
}
})
}
}
```
## 依赖管理
- 使用Go Modules管理依赖
- 保持`go.mod``go.sum`整洁
- 定期更新依赖:`go get -u ./...`
- 移除未使用的依赖:`go mod tidy`
## 提交前检查清单
1. ✅ 运行`gofmt -w .`格式化代码
2. ✅ 运行`go vet ./...`检查代码
3. ✅ 运行`go test ./...`确保测试通过
4. ✅ 确保所有导出函数有注释
5. ✅ 检查错误处理是否完善
6. ✅ 验证API密钥等敏感信息已忽略
## 敏感信息处理
- API密钥使用环境变量或配置文件
-`.env`添加到`.gitignore`
- 不要在日志中打印敏感信息
- 使用`os.Getenv`读取环境变量
## 构建和发布
```bash
# 发布版本
git tag v1.0.0
git push origin v1.0.0
# 使用GoReleaser推荐
goreleaser release --clean
```
## 常见问题
### Q: 如何添加新的翻译模式?
A: 在`internal/prompt/`目录下创建新的Prompt配置然后在`internal/translator/`中注册。
### Q: 如何处理API限流
A: 使用指数退避重试,并在`internal/api/`中实现限流器。
### Q: 如何支持更多语言?
A: 在配置文件中添加语言映射,并更新翻译逻辑。
## 参考资源
- [Effective Go](https://go.dev/doc/effective_go)
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments)
- [Go Style Guide](https://google.github.io/styleguide/go/)