# 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/)