From fcac878cc6efc2f54fb541fe52e939a9e688ae37 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 11 Jun 2025 05:50:21 +0000 Subject: [PATCH] feat: Add web search functionality with -search model suffix - Add support for search models (e.g., gpt-4o-search, claude-3.5-sonnet-search) - Automatically detect -search suffix and enable You.com web search features - Extend model list API to include all search model variants - Maintain full OpenAI API compatibility - Return original search model name in responses Users can now select search models directly from the model list at /v1/models and get AI responses enhanced with real-time web search results. --- SEARCH_FEATURE.md | 153 ++++++++++++++++++++++++++++++++++++++++++++++ api/main.go | 64 ++++++++++++++++++- 2 files changed, 214 insertions(+), 3 deletions(-) create mode 100644 SEARCH_FEATURE.md diff --git a/SEARCH_FEATURE.md b/SEARCH_FEATURE.md new file mode 100644 index 0000000..7f1241a --- /dev/null +++ b/SEARCH_FEATURE.md @@ -0,0 +1,153 @@ +# You2API 搜索功能说明 + +## 功能概述 + +You2API 现在支持网络搜索功能!当您使用带有 `-search` 后缀的模型名称时,系统会自动启用 You.com 的网络搜索功能,让AI能够获取最新的网络信息来回答您的问题。 + +## 使用方法 + +### 1. 可用的搜索模型 + +所有基础模型都有对应的搜索版本,只需在模型名后添加 `-search` 后缀: + +- `gpt-4o-search` +- `gpt-4o-mini-search` +- `claude-3.5-sonnet-search` +- `deepseek-chat-search` +- `gemini-1.5-pro-search` +- 等等... + +### 2. API 调用示例 + +#### 普通聊天(无搜索) +```bash +curl -X POST http://localhost:8080/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_DS_TOKEN" \ + -d '{ + "model": "gpt-4o", + "messages": [ + { + "role": "user", + "content": "介绍一下人工智能" + } + ] + }' +``` + +#### 启用搜索功能 +```bash +curl -X POST http://localhost:8080/v1/chat/completions \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_DS_TOKEN" \ + -d '{ + "model": "gpt-4o-search", + "messages": [ + { + "role": "user", + "content": "今天的最新科技新闻有哪些?" + } + ] + }' +``` + +### 3. Python 客户端示例 + +```python +import openai + +# 配置客户端 +client = openai.OpenAI( + api_key="YOUR_DS_TOKEN", + base_url="http://localhost:8080/v1" +) + +# 普通聊天 +response = client.chat.completions.create( + model="gpt-4o", + messages=[ + {"role": "user", "content": "什么是机器学习?"} + ] +) + +# 启用搜索的聊天 +search_response = client.chat.completions.create( + model="gpt-4o-search", + messages=[ + {"role": "user", "content": "2024年最新的AI发展趋势是什么?"} + ] +) +``` + +## 技术实现 + +### 工作原理 + +1. **模型检测**:系统检测到模型名包含 `-search` 后缀 +2. **参数映射**:将 `gpt-4o-search` 映射为基础模型 `gpt-4o` +3. **搜索启用**:在向 You.com API 发送请求时添加搜索参数: + - `selectedChatMode=default` + - `use_search=true` + - `search_focus=web` + - `enable_web_results=true` +4. **响应处理**:保持原始模型名 `gpt-4o-search` 在响应中 + +### 关键代码修改 + +1. **模型检测函数**: +```go +func isSearchModel(modelID string) bool { + return strings.HasSuffix(modelID, "-search") +} + +func getBaseModelName(modelID string) string { + if isSearchModel(modelID) { + return strings.TrimSuffix(modelID, "-search") + } + return modelID +} +``` + +2. **搜索参数设置**: +```go +if isSearch { + q.Add("selectedAiModel", mapModelName(baseModel)) + q.Add("selectedChatMode", "default") + q.Add("use_search", "true") + q.Add("search_focus", "web") + q.Add("enable_web_results", "true") +} +``` + +## 适用场景 + +### 推荐使用搜索功能的场景: +- 获取最新新闻和时事 +- 查询实时数据(股价、天气等) +- 了解最新的技术发展 +- 搜索特定事件或人物的最新信息 +- 获取产品价格和评价 + +### 推荐使用普通模式的场景: +- 一般知识问答 +- 代码编写和调试 +- 文本创作和编辑 +- 逻辑推理和分析 +- 不需要最新信息的任务 + +## 注意事项 + +1. **Token 要求**:需要有效的 You.com DS token +2. **响应时间**:搜索模式可能比普通模式稍慢,因为需要进行网络搜索 +3. **搜索质量**:搜索结果的质量取决于 You.com 的搜索能力 +4. **模型兼容性**:所有支持的基础模型都可以使用搜索功能 + +## 获取模型列表 + +您可以通过以下 API 获取所有可用的模型(包括搜索模型): + +```bash +curl http://localhost:8080/v1/models +``` + +这将返回包含所有基础模型和对应搜索模型的完整列表。 \ No newline at end of file diff --git a/api/main.go b/api/main.go index d4b7989..a9b66cd 100644 --- a/api/main.go +++ b/api/main.go @@ -55,6 +55,19 @@ func isAgentModel(modelID string) bool { return false } +// 新增:检查模型是否为搜索模型(带-search后缀) +func isSearchModel(modelID string) bool { + return strings.HasSuffix(modelID, "-search") +} + +// 新增:获取基础模型名(移除-search后缀) +func getBaseModelName(modelID string) string { + if isSearchModel(modelID) { + return strings.TrimSuffix(modelID, "-search") + } + return modelID +} + // TokenCount 定义了 token 计数的结构 type TokenCount struct { PromptTokens int `json:"prompt_tokens"` @@ -195,6 +208,16 @@ func getReverseModelMap() map[string]string { // mapModelName 将 OpenAI 模型名称映射到 You.com 模型名称。 func mapModelName(openAIModel string) string { + // 检查是否为搜索模型(带-search后缀) + if strings.HasSuffix(openAIModel, "-search") { + // 移除-search后缀,获取基础模型名 + baseModel := strings.TrimSuffix(openAIModel, "-search") + if mappedModel, exists := modelMap[baseModel]; exists { + return mappedModel + } + return "deepseek_v3" // 默认模型 + } + if mappedModel, exists := modelMap[openAIModel]; exists { return mappedModel } @@ -210,6 +233,21 @@ func reverseMapModelName(youModel string) string { return "deepseek-chat" // 默认模型 } +// reverseMapModelNameWithSearch 将 You.com 模型名称映射回 OpenAI 模型名称,保持搜索后缀 +func reverseMapModelNameWithSearch(youModel string, isSearchRequest bool) string { + reverseMap := getReverseModelMap() + baseModel := "deepseek-chat" // 默认模型 + + if mappedModel, exists := reverseMap[youModel]; exists { + baseModel = mappedModel + } + + if isSearchRequest { + return baseModel + "-search" + } + return baseModel +} + // originalModel 存储原始的 OpenAI 模型名称。 var originalModel string @@ -486,15 +524,23 @@ func Handler(w http.ResponseWriter, r *http.Request) { return } - models := make([]ModelDetail, 0, len(modelMap)) + models := make([]ModelDetail, 0, len(modelMap)*2) // 预留空间给搜索模型 created := time.Now().Unix() for modelID := range modelMap { + // 添加普通模型 models = append(models, ModelDetail{ ID: modelID, Object: "model", Created: created, OwnedBy: "organization-owner", }) + // 添加对应的搜索模型 + models = append(models, ModelDetail{ + ID: modelID + "-search", + Object: "model", + Created: created, + OwnedBy: "organization-owner", + }) } // 新增:添加agent模型到模型列表 @@ -780,10 +826,22 @@ func Handler(w http.ResponseWriter, r *http.Request) { // 新增:根据模型类型设置不同的参数 isAgent := isAgentModel(openAIReq.Model) + isSearch := isSearchModel(openAIReq.Model) + baseModel := getBaseModelName(openAIReq.Model) + if isAgent { // 新增:Agent模型: 只使用selectedChatMode=agent模型ID fmt.Printf("使用Agent模型: %s\n", openAIReq.Model) q.Add("selectedChatMode", openAIReq.Model) // 修改:直接使用模型ID作为chatMode + } else if isSearch { + // 新增:搜索模型: 使用基础模型但启用搜索功能 + fmt.Printf("使用搜索模型: %s (基础模型: %s, 映射为: %s)\n", openAIReq.Model, baseModel, mapModelName(baseModel)) + q.Add("selectedAiModel", mapModelName(baseModel)) + q.Add("selectedChatMode", "default") // 使用default模式并启用搜索 + // 根据you.com的实际API,可能需要以下参数来启用搜索 + q.Add("use_search", "true") + q.Add("search_focus", "web") + q.Add("enable_web_results", "true") } else { // 修改:默认模型: 使用selectedAiModel和selectedChatMode=custom fmt.Printf("使用默认模型: %s (映射为: %s)\n", openAIReq.Model, mapModelName(openAIReq.Model)) @@ -1008,7 +1066,7 @@ func handleNonStreamingResponse(w http.ResponseWriter, youReq *http.Request) { ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()), Object: "chat.completion", Created: time.Now().Unix(), - Model: reverseMapModelName(mapModelName(originalModel)), // 映射回 OpenAI 模型名称 + Model: originalModel, // 直接返回用户原始请求的模型名(包括-search后缀) Choices: []OpenAIChoice{ { Message: Message{ @@ -1060,7 +1118,7 @@ func handleStreamingResponse(w http.ResponseWriter, youReq *http.Request) { ID: "chatcmpl-" + fmt.Sprintf("%d", time.Now().Unix()), Object: "chat.completion.chunk", Created: time.Now().Unix(), - Model: reverseMapModelName(mapModelName(originalModel)), // 映射回 OpenAI 模型名称 + Model: originalModel, // 直接返回用户原始请求的模型名(包括-search后缀) Choices: []Choice{ { Delta: Delta{