feat: ds搜索功能初步完成,但是时间不是实时的
This commit is contained in:
parent
ecff6edd61
commit
7569e588b9
|
|
@ -10,6 +10,7 @@ from typing import Dict, List
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
||||||
|
from .plugins import get_web_search_mode
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
@ -29,7 +30,7 @@ DASHSCOPE_MODELS = [
|
||||||
max_tokens=8192,
|
max_tokens=8192,
|
||||||
provider="Aliyun",
|
provider="Aliyun",
|
||||||
supports_thinking=True,
|
supports_thinking=True,
|
||||||
supports_web_search=False,
|
supports_web_search=True,
|
||||||
supports_vision=False,
|
supports_vision=False,
|
||||||
supports_files=False,
|
supports_files=False,
|
||||||
),
|
),
|
||||||
|
|
@ -40,7 +41,7 @@ DASHSCOPE_MODELS = [
|
||||||
max_tokens=8192,
|
max_tokens=8192,
|
||||||
provider="Aliyun",
|
provider="Aliyun",
|
||||||
supports_thinking=True,
|
supports_thinking=True,
|
||||||
supports_web_search=False,
|
supports_web_search=True,
|
||||||
supports_vision=True,
|
supports_vision=True,
|
||||||
supports_files=False,
|
supports_files=False,
|
||||||
),
|
),
|
||||||
|
|
@ -51,7 +52,7 @@ DASHSCOPE_MODELS = [
|
||||||
max_tokens=8192,
|
max_tokens=8192,
|
||||||
provider="Aliyun",
|
provider="Aliyun",
|
||||||
supports_thinking=False,
|
supports_thinking=False,
|
||||||
supports_web_search=False,
|
supports_web_search=True,
|
||||||
supports_vision=False,
|
supports_vision=False,
|
||||||
supports_files=False,
|
supports_files=False,
|
||||||
),
|
),
|
||||||
|
|
@ -188,7 +189,7 @@ class DashScopeAdapter(BaseAdapter):
|
||||||
chunk_count = 0
|
chunk_count = 0
|
||||||
error_occurred = False
|
error_occurred = False
|
||||||
|
|
||||||
# 构建 API 调用参数
|
# 打印 API 调用参数
|
||||||
api_params = {
|
api_params = {
|
||||||
"model": request.model,
|
"model": request.model,
|
||||||
"messages": messages,
|
"messages": messages,
|
||||||
|
|
@ -198,6 +199,13 @@ class DashScopeAdapter(BaseAdapter):
|
||||||
"result_format": "message",
|
"result_format": "message",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 使用统一网络搜索配置
|
||||||
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
if web_search_mode:
|
||||||
|
api_params["enable_search"] = True
|
||||||
|
if web_search_mode == "deep":
|
||||||
|
api_params["search_options"] = {"enable_search_extension": True}
|
||||||
|
|
||||||
# 添加深度思考参数
|
# 添加深度思考参数
|
||||||
if thinking_enabled:
|
if thinking_enabled:
|
||||||
api_params["enable_thinking"] = True
|
api_params["enable_thinking"] = True
|
||||||
|
|
@ -331,6 +339,13 @@ class DashScopeAdapter(BaseAdapter):
|
||||||
"result_format": "message",
|
"result_format": "message",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 使用统一网络搜索配置
|
||||||
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
if web_search_mode:
|
||||||
|
api_params["enable_search"] = True
|
||||||
|
if web_search_mode == "deep":
|
||||||
|
api_params["search_options"] = {"enable_search_extension": True}
|
||||||
|
|
||||||
# 添加深度思考参数
|
# 添加深度思考参数
|
||||||
if thinking_enabled:
|
if thinking_enabled:
|
||||||
api_params["enable_thinking"] = True
|
api_params["enable_thinking"] = True
|
||||||
|
|
@ -532,6 +547,13 @@ class DashScopeAdapter(BaseAdapter):
|
||||||
"temperature": request.temperature,
|
"temperature": request.temperature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 使用统一网络搜索配置
|
||||||
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
if web_search_mode:
|
||||||
|
api_params["enable_search"] = True
|
||||||
|
if web_search_mode == "deep":
|
||||||
|
api_params["search_options"] = {"enable_search_extension": True}
|
||||||
|
|
||||||
# 添加深度思考参数
|
# 添加深度思考参数
|
||||||
if thinking_enabled:
|
if thinking_enabled:
|
||||||
api_params["enable_thinking"] = True
|
api_params["enable_thinking"] = True
|
||||||
|
|
@ -680,6 +702,13 @@ class DashScopeAdapter(BaseAdapter):
|
||||||
"temperature": request.temperature,
|
"temperature": request.temperature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 使用统一网络搜索配置
|
||||||
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
if web_search_mode:
|
||||||
|
api_params["enable_search"] = True
|
||||||
|
if web_search_mode == "deep":
|
||||||
|
api_params["search_options"] = {"enable_search_extension": True}
|
||||||
|
|
||||||
# 添加深度思考参数
|
# 添加深度思考参数
|
||||||
if thinking_enabled:
|
if thinking_enabled:
|
||||||
api_params["enable_thinking"] = True
|
api_params["enable_thinking"] = True
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from typing import Dict, List, Optional
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
||||||
|
from .plugins import get_web_search_mode, build_glm_search_tool
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
@ -24,7 +25,7 @@ GLM_MODELS = [
|
||||||
max_tokens=128000,
|
max_tokens=128000,
|
||||||
provider="ZhipuAI",
|
provider="ZhipuAI",
|
||||||
supports_thinking=True,
|
supports_thinking=True,
|
||||||
supports_web_search=False,
|
supports_web_search=True,
|
||||||
supports_vision=False,
|
supports_vision=False,
|
||||||
supports_files=False,
|
supports_files=False,
|
||||||
),
|
),
|
||||||
|
|
@ -64,11 +65,11 @@ GLM_MODELS = [
|
||||||
ModelInfo(
|
ModelInfo(
|
||||||
id="glm-z1-flash",
|
id="glm-z1-flash",
|
||||||
name="GLM-Z1 Flash",
|
name="GLM-Z1 Flash",
|
||||||
description="深度思考推理模型",
|
description="深度思考推理模型,默认开启深度思考",
|
||||||
max_tokens=128000,
|
max_tokens=128000,
|
||||||
provider="ZhipuAI",
|
provider="ZhipuAI",
|
||||||
supports_thinking=True,
|
supports_thinking=True,
|
||||||
supports_web_search=False,
|
supports_web_search=True,
|
||||||
supports_vision=False,
|
supports_vision=False,
|
||||||
supports_files=False,
|
supports_files=False,
|
||||||
),
|
),
|
||||||
|
|
@ -129,10 +130,10 @@ class GLMAdapter(BaseAdapter):
|
||||||
|
|
||||||
# 构建额外参数
|
# 构建额外参数
|
||||||
extra_kwargs = {}
|
extra_kwargs = {}
|
||||||
web_search = self._get_web_search_mode(request)
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
|
||||||
if web_search:
|
if web_search_mode:
|
||||||
extra_kwargs["tools"] = [self._build_web_search_tool(web_search)]
|
extra_kwargs["tools"] = [build_glm_search_tool(web_search_mode)]
|
||||||
extra_kwargs["tool_choice"] = "auto"
|
extra_kwargs["tool_choice"] = "auto"
|
||||||
|
|
||||||
# 深度思考:正向选择(True 时启用,False 时禁用)
|
# 深度思考:正向选择(True 时启用,False 时禁用)
|
||||||
|
|
@ -260,46 +261,6 @@ class GLMAdapter(BaseAdapter):
|
||||||
"""检查模型是否支持深度思考"""
|
"""检查模型是否支持深度思考"""
|
||||||
return model.lower() in THINKING_MODELS
|
return model.lower() in THINKING_MODELS
|
||||||
|
|
||||||
def _get_web_search_mode(self, request: ChatCompletionRequest) -> str:
|
|
||||||
"""获取联网搜索模式"""
|
|
||||||
if request.deep_search:
|
|
||||||
return "deep"
|
|
||||||
elif request.web_search:
|
|
||||||
return "simple"
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def _build_web_search_tool(self, mode: str) -> Dict:
|
|
||||||
"""构建联网搜索工具"""
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
today = datetime.now().strftime("%Y年%m月%d日")
|
|
||||||
|
|
||||||
if mode == "deep":
|
|
||||||
# 深度搜索:返回搜索结果详情
|
|
||||||
return {
|
|
||||||
"type": "web_search",
|
|
||||||
"web_search": {
|
|
||||||
"enable": True,
|
|
||||||
"search_engine": "search_pro",
|
|
||||||
"search_result": True,
|
|
||||||
"search_prompt": f"你是一位智能助手。请用简洁的语言总结网络搜索{{search_result}}中的关键信息,按重要性排序并引用来源日期。今天的日期是{today}。",
|
|
||||||
"count": 5,
|
|
||||||
"search_recency_filter": "noLimit",
|
|
||||||
"content_size": "high",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
# 简单搜索
|
|
||||||
return {
|
|
||||||
"type": "web_search",
|
|
||||||
"web_search": {
|
|
||||||
"enable": True,
|
|
||||||
"search_engine": "search_pro",
|
|
||||||
"search_result": True,
|
|
||||||
"count": 5,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def _stream_chat(
|
def _stream_chat(
|
||||||
self, client, messages, model, request, extra_kwargs
|
self, client, messages, model, request, extra_kwargs
|
||||||
) -> StreamingResponse:
|
) -> StreamingResponse:
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from typing import Dict, List, Optional
|
||||||
from fastapi.responses import JSONResponse, StreamingResponse
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||||||
|
|
||||||
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
from .base import BaseAdapter, ChatCompletionRequest, ModelInfo
|
||||||
|
from .plugins import get_web_search_mode, build_openai_search_tool, execute_tavily_search
|
||||||
from core import get_logger
|
from core import get_logger
|
||||||
|
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|
@ -168,6 +169,12 @@ class OpenAIAdapter(BaseAdapter):
|
||||||
"stream": request.stream,
|
"stream": request.stream,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 统一添加联网搜索插件参数
|
||||||
|
web_search_mode = get_web_search_mode(request)
|
||||||
|
if web_search_mode:
|
||||||
|
search_tool = build_openai_search_tool(web_search_mode)
|
||||||
|
kwargs["tools"] = [search_tool]
|
||||||
|
|
||||||
# DeepSeek 深度思考支持
|
# DeepSeek 深度思考支持
|
||||||
extra_body = None
|
extra_body = None
|
||||||
if self._provider_type == "deepseek" and request.deep_thinking:
|
if self._provider_type == "deepseek" and request.deep_thinking:
|
||||||
|
|
@ -220,16 +227,26 @@ class OpenAIAdapter(BaseAdapter):
|
||||||
def generator():
|
def generator():
|
||||||
from utils.helpers import generate_unique_id, get_current_timestamp
|
from utils.helpers import generate_unique_id, get_current_timestamp
|
||||||
|
|
||||||
resp = client.chat.completions.create(**kwargs)
|
nonlocal kwargs
|
||||||
|
|
||||||
|
# 可能需要执行多轮对话(当发生工具调用时)
|
||||||
|
while True:
|
||||||
|
resp = client.chat.completions.create(**kwargs)
|
||||||
|
full_content = ""
|
||||||
|
full_reasoning = ""
|
||||||
|
chunk_count = 0
|
||||||
|
|
||||||
|
tool_calls = []
|
||||||
|
current_tool_call = None
|
||||||
|
|
||||||
|
for chunk in resp:
|
||||||
|
if not chunk.choices:
|
||||||
|
continue
|
||||||
|
|
||||||
full_content = ""
|
|
||||||
full_reasoning = ""
|
|
||||||
chunk_count = 0
|
|
||||||
for chunk in resp:
|
|
||||||
if chunk.choices:
|
|
||||||
chunk_count += 1
|
chunk_count += 1
|
||||||
delta = chunk.choices[0].delta
|
delta = chunk.choices[0].delta
|
||||||
|
|
||||||
|
# 1. 收集可能有内容/推理
|
||||||
delta_content = {}
|
delta_content = {}
|
||||||
if hasattr(delta, "content") and delta.content:
|
if hasattr(delta, "content") and delta.content:
|
||||||
delta_content["content"] = delta.content
|
delta_content["content"] = delta.content
|
||||||
|
|
@ -238,7 +255,27 @@ class OpenAIAdapter(BaseAdapter):
|
||||||
delta_content["reasoning_content"] = delta.reasoning_content
|
delta_content["reasoning_content"] = delta.reasoning_content
|
||||||
full_reasoning += delta.reasoning_content
|
full_reasoning += delta.reasoning_content
|
||||||
|
|
||||||
if delta_content:
|
# 2. 收集可能产生的 tool_calls (流式)
|
||||||
|
if hasattr(delta, "tool_calls") and delta.tool_calls:
|
||||||
|
for tool_call_chunk in delta.tool_calls:
|
||||||
|
idx = tool_call_chunk.index
|
||||||
|
# 确保 tool_calls 列表足够长
|
||||||
|
while len(tool_calls) <= idx:
|
||||||
|
tool_calls.append({"id": "", "type": "function", "function": {"name": "", "arguments": ""}})
|
||||||
|
|
||||||
|
if tool_call_chunk.id:
|
||||||
|
tool_calls[idx]["id"] += tool_call_chunk.id
|
||||||
|
if tool_call_chunk.type:
|
||||||
|
# 对于 type, 因为 OpenAI 可能会传 chunks, 但通常只在第一块或者每块传, 为了避免 functionfunction, 使用赋值而非累加
|
||||||
|
tool_calls[idx]["type"] = tool_call_chunk.type
|
||||||
|
if tool_call_chunk.function:
|
||||||
|
if tool_call_chunk.function.name:
|
||||||
|
tool_calls[idx]["function"]["name"] += tool_call_chunk.function.name
|
||||||
|
if tool_call_chunk.function.arguments:
|
||||||
|
tool_calls[idx]["function"]["arguments"] += tool_call_chunk.function.arguments
|
||||||
|
|
||||||
|
# 3. 输出给前端普通文本
|
||||||
|
if delta_content and not tool_calls:
|
||||||
data = {
|
data = {
|
||||||
"id": f"chatcmpl-{generate_unique_id()}",
|
"id": f"chatcmpl-{generate_unique_id()}",
|
||||||
"object": "chat.completion.chunk",
|
"object": "chat.completion.chunk",
|
||||||
|
|
@ -254,27 +291,71 @@ class OpenAIAdapter(BaseAdapter):
|
||||||
}
|
}
|
||||||
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
|
yield f"data: {json.dumps(data, ensure_ascii=False)}\n\n"
|
||||||
|
|
||||||
finish = {
|
# 检查此轮请求是否收到了完整工具调用,若是则执行搜索逻辑并追加继续请求,不再让外部函数退出
|
||||||
"id": f"chatcmpl-{generate_unique_id()}",
|
if tool_calls:
|
||||||
"object": "chat.completion.chunk",
|
logger.info(f"[{provider_name}] 检测到流式中包含了工具调用进行拦截并处理: {json.dumps(tool_calls, ensure_ascii=False)}")
|
||||||
"created": get_current_timestamp(),
|
|
||||||
"model": kwargs["model"],
|
|
||||||
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
|
|
||||||
}
|
|
||||||
yield f"data: {json.dumps(finish, ensure_ascii=False)}\n\n"
|
|
||||||
yield "data: [DONE]\n\n"
|
|
||||||
|
|
||||||
# 打印流式响应结果
|
# 把大模型的工具调用请求也追加进去
|
||||||
logger.info(f"[{provider_name}] 流式响应完成:")
|
assistant_msg = {
|
||||||
logger.info(f" - chunks: {chunk_count}")
|
"role": "assistant",
|
||||||
logger.info(f" - content_length: {len(full_content)} 字符")
|
"content": full_content or None, # 如果工具和普通内容同时存在也保留
|
||||||
if full_reasoning:
|
"tool_calls": tool_calls
|
||||||
logger.info(f" - reasoning_length: {len(full_reasoning)} 字符")
|
}
|
||||||
logger.info(
|
if full_reasoning:
|
||||||
f" - content_preview: {full_content[:200]}..."
|
assistant_msg["reasoning_content"] = full_reasoning
|
||||||
if len(full_content) > 200
|
elif self._provider_type == "deepseek" and self._supports_thinking(kwargs["model"]):
|
||||||
else f" - content: {full_content}"
|
# DeepSeek 推理模型在有工具调用时必须有 reasoning_content 字段
|
||||||
)
|
assistant_msg["reasoning_content"] = ""
|
||||||
|
kwargs["messages"].append(assistant_msg)
|
||||||
|
|
||||||
|
for tc in tool_calls:
|
||||||
|
if tc["function"]["name"] == "web_search":
|
||||||
|
try:
|
||||||
|
args = json.loads(tc["function"]["arguments"])
|
||||||
|
query = args.get("query", "")
|
||||||
|
mode = "deep" if "advanced" in str(kwargs.get("tools", [])) else "simple"
|
||||||
|
logger.info(f"[{provider_name}] 执行搜索插件: {query}")
|
||||||
|
search_result = execute_tavily_search(query, mode=mode)
|
||||||
|
except Exception as e:
|
||||||
|
search_result = f"获取搜索参数或执行搜索失败: {str(e)}"
|
||||||
|
logger.error(search_result)
|
||||||
|
|
||||||
|
# 把执行结果告诉大模型
|
||||||
|
kwargs["messages"].append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": tc["id"],
|
||||||
|
"name": "web_search",
|
||||||
|
"content": search_result
|
||||||
|
})
|
||||||
|
|
||||||
|
# 工具执行完毕,继续发起下一轮请求大模型归纳总结输出
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果没有工具调用或者全部分发完毕,正常结束给前端
|
||||||
|
finish = {
|
||||||
|
"id": f"chatcmpl-{generate_unique_id()}",
|
||||||
|
"object": "chat.completion.chunk",
|
||||||
|
"created": get_current_timestamp(),
|
||||||
|
"model": kwargs["model"],
|
||||||
|
"choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}],
|
||||||
|
}
|
||||||
|
yield f"data: {json.dumps(finish, ensure_ascii=False)}\n\n"
|
||||||
|
yield "data: [DONE]\n\n"
|
||||||
|
|
||||||
|
# 打印流式响应结果
|
||||||
|
logger.info(f"[{provider_name}] 流式响应完成:")
|
||||||
|
logger.info(f" - chunks: {chunk_count}")
|
||||||
|
logger.info(f" - content_length: {len(full_content)} 字符")
|
||||||
|
if full_reasoning:
|
||||||
|
logger.info(f" - reasoning_length: {len(full_reasoning)} 字符")
|
||||||
|
logger.info(
|
||||||
|
f" - content_preview: {full_content[:200]}..."
|
||||||
|
if len(full_content) > 200
|
||||||
|
else f" - content: {full_content}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 结束外层循环退出生成器
|
||||||
|
break
|
||||||
|
|
||||||
return StreamingResponse(generator(), media_type="text/event-stream")
|
return StreamingResponse(generator(), media_type="text/event-stream")
|
||||||
|
|
||||||
|
|
@ -284,10 +365,58 @@ class OpenAIAdapter(BaseAdapter):
|
||||||
"""非流式聊天"""
|
"""非流式聊天"""
|
||||||
from utils.helpers import generate_unique_id, get_current_timestamp
|
from utils.helpers import generate_unique_id, get_current_timestamp
|
||||||
|
|
||||||
resp = client.chat.completions.create(**kwargs)
|
while True:
|
||||||
|
resp = client.chat.completions.create(**kwargs)
|
||||||
|
|
||||||
message = resp.choices[0].message
|
message = resp.choices[0].message
|
||||||
content = message.content or ""
|
|
||||||
|
# 判断是否涉及工具调用
|
||||||
|
if hasattr(message, "tool_calls") and message.tool_calls:
|
||||||
|
# 记录这轮的助手回复
|
||||||
|
assistant_msg = {"role": "assistant", "content": message.content or None}
|
||||||
|
# openai sdk 对象转 dict 存储 tool_calls
|
||||||
|
tool_calls_dict = []
|
||||||
|
for tc in message.tool_calls:
|
||||||
|
tc_dict = {
|
||||||
|
"id": tc.id,
|
||||||
|
"type": tc.type,
|
||||||
|
"function": {
|
||||||
|
"name": tc.function.name,
|
||||||
|
"arguments": tc.function.arguments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tool_calls_dict.append(tc_dict)
|
||||||
|
assistant_msg["tool_calls"] = tool_calls_dict
|
||||||
|
if hasattr(message, "reasoning_content") and message.reasoning_content:
|
||||||
|
assistant_msg["reasoning_content"] = message.reasoning_content
|
||||||
|
elif self._provider_type == "deepseek" and self._supports_thinking(kwargs["model"]):
|
||||||
|
# DeepSeek 推理模型在有工具调用时必须有 reasoning_content 字段
|
||||||
|
assistant_msg["reasoning_content"] = ""
|
||||||
|
kwargs["messages"].append(assistant_msg)
|
||||||
|
|
||||||
|
# 执行所有的工具调用
|
||||||
|
for tc in tool_calls_dict:
|
||||||
|
if tc["function"]["name"] == "web_search":
|
||||||
|
try:
|
||||||
|
args = json.loads(tc["function"]["arguments"])
|
||||||
|
query = args.get("query", "")
|
||||||
|
mode = "deep" if "advanced" in str(kwargs.get("tools", [])) else "simple"
|
||||||
|
search_result = execute_tavily_search(query, mode=mode)
|
||||||
|
except Exception as e:
|
||||||
|
search_result = f"执行搜索失败: {str(e)}"
|
||||||
|
|
||||||
|
# 把执行结果追加到消息中
|
||||||
|
kwargs["messages"].append({
|
||||||
|
"role": "tool",
|
||||||
|
"tool_call_id": tc["id"],
|
||||||
|
"name": "web_search",
|
||||||
|
"content": search_result
|
||||||
|
})
|
||||||
|
# 工具调用完成,发起下一轮请求获取归纳答案
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 处理普通的文本回复
|
||||||
|
content = message.content or ""
|
||||||
response = {
|
response = {
|
||||||
"id": f"chatcmpl-{generate_unique_id()}",
|
"id": f"chatcmpl-{generate_unique_id()}",
|
||||||
"object": "chat.completion",
|
"object": "chat.completion",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
from typing import Dict
|
||||||
|
from datetime import datetime
|
||||||
|
from .base import ChatCompletionRequest
|
||||||
|
|
||||||
|
def get_web_search_mode(request: ChatCompletionRequest) -> str:
|
||||||
|
"""获取联网搜索模式"""
|
||||||
|
if getattr(request, 'deep_search', False):
|
||||||
|
return "deep"
|
||||||
|
elif getattr(request, 'web_search', False):
|
||||||
|
return "simple"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def execute_tavily_search(query: str, mode: str = "simple") -> str:
|
||||||
|
"""真实调用 Tavily 搜索 API"""
|
||||||
|
api_key = os.getenv("TAVILY_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
return "本地环境变量 TAVILY_API_KEY 未配置,无法进行搜索。"
|
||||||
|
|
||||||
|
url = "https://api.tavily.com/search"
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
data = {
|
||||||
|
"api_key": api_key,
|
||||||
|
"query": query,
|
||||||
|
"search_depth": "advanced" if mode == "deep" else "basic",
|
||||||
|
"include_answer": False,
|
||||||
|
"max_results": 5 if mode == "deep" else 3
|
||||||
|
}
|
||||||
|
|
||||||
|
req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers, method='POST')
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as response:
|
||||||
|
result = json.loads(response.read().decode('utf-8'))
|
||||||
|
results = result.get("results", [])
|
||||||
|
|
||||||
|
if not results:
|
||||||
|
return "搜索未返回结果。"
|
||||||
|
|
||||||
|
formatted_res = []
|
||||||
|
for i, res in enumerate(results):
|
||||||
|
formatted_res.append(f"[{i+1}] {res.get('title')}\n{res.get('content')}\n链接: {res.get('url')}")
|
||||||
|
|
||||||
|
return "\n\n".join(formatted_res)
|
||||||
|
except Exception as e:
|
||||||
|
return f"搜索请求失败,错误: {str(e)}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def build_openai_search_tool(mode: str) -> Dict:
|
||||||
|
"""
|
||||||
|
构建兼容型联网搜索插件工具结构 (供 DeepSeek / OpenAI SDK 使用)
|
||||||
|
注意:此类提供标准的 Tool/Function Function calling 模板。
|
||||||
|
深度思考通常结合内置联网或者其他外挂流程实现。
|
||||||
|
"""
|
||||||
|
if mode == "deep":
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "web_search",
|
||||||
|
"description": "深度互联网搜索插件(查找并阅读网页内容)",
|
||||||
|
"parameters": {"type": "object", "properties": {"query": {"type": "string"}}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"type": "function",
|
||||||
|
"function": {
|
||||||
|
"name": "web_search",
|
||||||
|
"description": "进行互联网搜索并获取实时信息或资料以辅助回答。",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"query": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "要搜索的准确关键词或短语"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["query"]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_glm_search_tool(mode: str) -> Dict:
|
||||||
|
"""构建 GLM 联网搜索工具"""
|
||||||
|
today = datetime.now().strftime("%Y年%m月%d日")
|
||||||
|
|
||||||
|
if mode == "deep":
|
||||||
|
# 深度搜索:返回搜索结果详情
|
||||||
|
return {
|
||||||
|
"type": "web_search",
|
||||||
|
"web_search": {
|
||||||
|
"enable": True,
|
||||||
|
"search_engine": "search_pro",
|
||||||
|
"search_result": True,
|
||||||
|
"search_prompt": f"你是一位智能助手。请用简洁的语言总结网络搜索{{search_result}}中的关键信息,按重要性排序并引用来源日期。今天的日期是{today}。",
|
||||||
|
"count": 5,
|
||||||
|
"search_recency_filter": "noLimit",
|
||||||
|
"content_size": "high",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# 简单搜索
|
||||||
|
return {
|
||||||
|
"type": "web_search",
|
||||||
|
"web_search": {
|
||||||
|
"enable": True,
|
||||||
|
"search_engine": "search_pro",
|
||||||
|
"search_result": True,
|
||||||
|
"count": 5,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
import json
|
||||||
|
|
||||||
|
def test_tavily(api_key: str):
|
||||||
|
url = "https://api.tavily.com/search"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"api_key": api_key,
|
||||||
|
"query": "武汉明天的天气",
|
||||||
|
"search_depth": "basic",
|
||||||
|
"include_answer": False,
|
||||||
|
"max_results": 3
|
||||||
|
}
|
||||||
|
|
||||||
|
# 模拟请求
|
||||||
|
req = urllib.request.Request(url, data=json.dumps(data).encode('utf-8'), headers=headers, method='POST')
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as response:
|
||||||
|
result = json.loads(response.read().decode('utf-8'))
|
||||||
|
print("✅ Tavily API Key 测试成功!成功获取以下搜索结果:\n")
|
||||||
|
|
||||||
|
for i, res in enumerate(result.get("results", [])):
|
||||||
|
print(f"[{i+1}] 标题: {res.get('title')}")
|
||||||
|
print(f" 内容: {res.get('content')}")
|
||||||
|
print(f" 链接: {res.get('url')}\n")
|
||||||
|
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f"❌ 请求失败,HTTP 错误代码: {e.code}")
|
||||||
|
print("这通常意味着您的 API Key 错误或无效。详细信息:")
|
||||||
|
error_msg = e.read().decode('utf-8')
|
||||||
|
print(error_msg)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 发生其他错误: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
key = input("请输入您的 Tavily API Key (以 tvly- 开头): ").strip()
|
||||||
|
if not key:
|
||||||
|
print("未输入 Key,程序退出。")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("\n正在连接 Tavily 进行测试搜索...")
|
||||||
|
test_tavily(key)
|
||||||
Loading…
Reference in New Issue