修改了测试服务,添加了搜索直连DeepSeek大语言模型的测试

This commit is contained in:
Tian jianyong 2025-12-12 14:31:31 +08:00
parent acf1f7c568
commit 1465e1564e
8 changed files with 349 additions and 440 deletions

View File

@ -221,6 +221,13 @@ class SearchSettings:
SEARCH_CACHE_TTL = int(os.getenv("SEARCH_CACHE_TTL", "1800"))
class DeepSeekSettings:
"""DeepSeek大语言模型配置"""
DEEPSEEK_API_BASE = os.getenv("DEEPSEEK_API_BASE", "https://api.deepseek.com")
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "sk-56b608b26a6949e4b09b5bf5f11c8f5b")
DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
class GetConfig:
"""
获取配置
@ -302,6 +309,11 @@ class GetConfig:
def get_search_config(self):
"""获取搜索配置"""
return SearchSettings()
@lru_cache()
def get_deepseek_config(self):
"""获取DeepSeek配置"""
return DeepSeekSettings()
@staticmethod
def parse_cli_args():
@ -351,4 +363,6 @@ RAGFlowConfig = get_config.get_ragflow_config()
# compreface配置
ComprefaceConfig = get_config.get_compreface_config()
# 搜索配置
SearchConfig = get_config.get_search_config()
SearchConfig = get_config.get_search_config()
# DeepSeek配置
DeepSeekConfig = get_config.get_deepseek_config()

View File

@ -1,7 +1,8 @@
import hashlib
import json
import re
from datetime import datetime
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional
from config.env import SearchConfig
from utils.log_util import logger
@ -15,13 +16,35 @@ class SearchServiceError(Exception):
class SearchService:
"""搜索服务 - 处理联网搜索请求"""
# 搜索关键词 - 用于判断是否应该使用搜索服务
# 基础搜索关键词 - 用于判断是否应该使用搜索服务
SEARCH_KEYWORDS = (
'搜索', '查找', '新闻', '时事', '最新', '今天', '现在', '当前',
'天气', '温度', '气温', '下雨', '', '多云',
'search', 'find', 'news', 'latest', 'today', 'current', 'now',
'weather', 'temperature', 'rain', 'sunny', 'cloudy'
# 操作类
'搜索', '查找', '查询', '搜一下', '帮我查', '帮我搜', '检索',
# 时间类
'最新', '最近', '今日', '今天', '现在', '当前', '昨日', '昨天',
# 天气类
'天气', '温度', '气温', '降水', '下雨', '下雪', '', '多云', '阴天', '雾霾',
# 新闻资讯类
'新闻', '时事', '资讯', '动态', '报道', '消息',
# 事件类
'发生了什么', '怎么回事', '事件', '事故', '活动',
# 英文关键词
'search', 'find', 'query', 'latest', 'recent', 'today', 'now', 'current',
'weather', 'temperature', 'news', 'events', 'what happened'
)
# 领域特定关键词(扩展)
DOMAIN_KEYWORDS = {
'科技': ('AI', '人工智能', '区块链', '元宇宙', '量子计算', '5G', '芯片'),
'体育': ('足球', '篮球', '奥运会', '世界杯', 'NBA', '英超', '比分'),
'娱乐': ('电影', '电视剧', '明星', '演唱会', '综艺', '票房'),
'财经': ('股票', '基金', '汇率', '利率', '财经', '股市', '行情'),
'健康': ('疫情', '疫苗', '健康', '医疗', '疾病', '症状'),
'教育': ('考试', '考研', '高考', '录取', '学校', '教育'),
}
# 否定关键词(这些词出现时不应使用搜索)
NEGATIVE_KEYWORDS = ('不要搜索', '不搜索', '无需搜索', '不用搜索', 'don\'t search', 'no search')
_client = SearchAPIClient(
base_url=SearchConfig.SEARCH_API_BASE,
@ -40,8 +63,42 @@ class SearchService:
"""
if not question:
return False
# 转换为小写用于匹配
normalized = question.strip().lower()
return any(k in question for k in cls.SEARCH_KEYWORDS) or any(k in normalized for k in cls.SEARCH_KEYWORDS)
# 检查是否包含否定关键词
if any(neg_k in normalized for neg_k in cls.NEGATIVE_KEYWORDS):
return False
# 1. 基础关键词匹配
if any(k in question for k in cls.SEARCH_KEYWORDS) or any(k in normalized for k in cls.SEARCH_KEYWORDS):
return True
# 2. 领域特定关键词匹配(需要同时满足领域词+时间/操作词)
for domain, domain_terms in cls.DOMAIN_KEYWORDS.items():
# 检查是否包含领域关键词
has_domain_term = any(term in question for term in domain_terms) or any(term.lower() in normalized for term in domain_terms)
# 检查是否包含时间或操作关键词
has_time_or_action = any(
k in question for k in ('最新', '最近', '今日', '今天', '搜索', '查找', '查询')
) or any(
k in normalized for k in ('latest', 'recent', 'today', 'search', 'find', 'query')
)
if has_domain_term and has_time_or_action:
return True
# 3. 模式匹配(如包含时间+事件的模式)
# 示例:"2023年发生了什么"、"昨天的新闻"等
time_patterns = r'(\d{4}年|\d{1,2}月\d{1,2}日|昨天|昨日|今天|今日|最近\d+天|最近\d+周|最近\d+月)'
if re.search(time_patterns, question) and any(
k in question for k in ('发生', '新闻', '事件', '动态')
):
return True
return False
@classmethod
async def get_search_answer(cls, question: str, redis=None) -> Dict[str, Any]:
@ -133,6 +190,9 @@ class SearchService:
# 提取答案框(如果有)
answer_box = payload.get('answer_box', {})
# 提取知识图谱(如果有)
knowledge_graph = payload.get('knowledge_graph', {})
# 构建结果列表
results = []
for item in organic_results[:SearchConfig.SEARCH_NUM_RESULTS]:
@ -140,27 +200,39 @@ class SearchService:
'title': item.get('title', ''),
'link': item.get('link', ''),
'snippet': item.get('snippet', ''),
'source': item.get('source', ''),
}
results.append(result_item)
# 构建用户友好的消息
message_parts = [f'关于"{query}"的搜索结果:\n']
# 如果有答案框,优先显示
# 如果有知识图谱,优先显示
if knowledge_graph:
kg_title = knowledge_graph.get('title', '')
kg_description = knowledge_graph.get('description', '')
if kg_title and kg_description:
simplified_desc = cls._simplify_text(kg_description)
message_parts.append(f'【知识卡片】{kg_title}: {simplified_desc}\n')
# 如果有答案框,显示答案
if answer_box:
answer = answer_box.get('answer') or answer_box.get('snippet', '')
if answer:
message_parts.append(f'【快速答案】{answer}\n')
simplified_answer = cls._simplify_text(answer)
message_parts.append(f'【快速答案】{simplified_answer}\n')
# 添加搜索结果
if results:
message_parts.append('\n【相关结果】')
for idx, item in enumerate(results, 1):
message_parts.append('\n【相关信息')
for idx, item in enumerate(results[:3], 1): # 只显示前3条
title = item['title']
snippet = item['snippet']
message_parts.append(f'\n{idx}. {title}')
if snippet:
message_parts.append(f' {snippet}')
if title:
message_parts.append(f'\n{idx}. {title}')
if snippet:
simplified_snippet = cls._simplify_text(snippet)
message_parts.append(f' {simplified_snippet}')
else:
message_parts.append('\n未找到相关结果。')
@ -168,39 +240,107 @@ class SearchService:
'type': 'search',
'query': query,
'answer_box': answer_box,
'knowledge_graph': knowledge_graph,
'results': results,
'message': '\n'.join(message_parts),
'context_for_llm': cls._prepare_llm_context(query, answer_box, results),
'context_for_llm': cls._prepare_llm_context(query, answer_box, knowledge_graph, results),
'raw': payload,
}
@staticmethod
def _simplify_text(text: str) -> str:
"""简化文本内容,去除冗余信息,保留核心内容
Args:
text: 原始文本
Returns:
简化后的文本
"""
if not text:
return ""
# 去除多余空格和换行
text = re.sub(r'\s+', ' ', text.strip())
# 去除冗余的标点符号
text = re.sub(r'[,、;;]+', '', text)
text = re.sub(r'[。.!]+', '', text)
# 去除括号内的辅助信息(可选)
# text = re.sub(r'\([^)]*\)|\[[^\]]*\]|\{[^}]*\}', '', text)
# 限制长度(根据需要调整)
max_length = 150
if len(text) > max_length:
# 尝试在句子边界截断
sentence_endings = re.finditer(r'[。.!?]', text[:max_length])
if sentence_endings:
last_end = list(sentence_endings)[-1].end()
text = text[:last_end] + "..."
else:
text = text[:max_length] + "..."
return text
@classmethod
def _prepare_llm_context(cls, query: str, answer_box: Dict[str, Any], results: list) -> str:
def _prepare_llm_context(cls, query: str, answer_box: Dict[str, Any], knowledge_graph: Dict[str, Any], results: List[Dict[str, Any]]) -> str:
"""准备用于LLM处理的简洁上下文
Args:
query: 用户查询
answer_box: 答案框数据
knowledge_graph: 知识图谱数据
results: 搜索结果列表
Returns:
简洁的上下文文本供LLM生成答案
"""
context_parts = []
context_tokens = 0
max_tokens = 500 # 限制上下文长度避免超过模型token限制
# 添加知识图谱信息(如果有)
if knowledge_graph:
kg_title = knowledge_graph.get('title', '')
kg_description = knowledge_graph.get('description', '')
if kg_title and kg_description:
simplified_desc = cls._simplify_text(kg_description)
kg_context = f"知识卡片:{kg_title} - {simplified_desc}"
context_parts.append(kg_context)
context_tokens += len(kg_context)
# 添加答案框信息(如果有)
if answer_box:
if answer_box and context_tokens < max_tokens:
answer = answer_box.get('answer') or answer_box.get('snippet', '')
if answer:
context_parts.append(f"快速答案:{answer}")
simplified_answer = cls._simplify_text(answer)
answer_context = f"快速答案:{simplified_answer}"
context_parts.append(answer_context)
context_tokens += len(answer_context)
# 添加前3条最相关的搜索结果
if results:
# 添加搜索结果
if results and context_tokens < max_tokens:
context_parts.append("\n相关信息:")
for idx, item in enumerate(results[:3], 1):
for idx, item in enumerate(results, 1):
if context_tokens >= max_tokens:
break
snippet = item.get('snippet', '')
if snippet:
# 只保留摘要去掉链接和标题以节省token
context_parts.append(f"{idx}. {snippet}")
simplified_snippet = cls._simplify_text(snippet)
result_context = f"{idx}. {simplified_snippet}"
context_parts.append(result_context)
context_tokens += len(result_context)
# 最多添加3条结果
if idx >= 3:
break
return "\n".join(context_parts) if context_parts else "未找到相关信息"
final_context = "\n".join(context_parts) if context_parts else "未找到相关信息"
# 最终检查长度
if len(final_context) > max_tokens * 2: # 粗略估算token数
final_context = final_context[:max_tokens * 2] + "..."
return final_context

View File

@ -17,3 +17,4 @@ requests==2.32.3
SQLAlchemy[asyncio]==2.0.38
sqlglot[rs]==26.6.0
user-agents==2.2.0
openai==1.37.1

View File

@ -1,149 +0,0 @@
"""
测试搜索+LLM完整流程的脚本
使用方法
1. 确保服务器正在运行
2. 运行此脚本python test_full_flow.py
"""
import asyncio
import json
import sys
try:
import httpx
except ImportError:
print("错误: 缺少 httpx 模块")
print("请运行: pip install httpx 或使用系统已安装的httpx")
sys.exit(1)
async def test_search_llm_flow():
"""测试完整的搜索+LLM流程"""
print("=" * 60)
print("搜索+LLM完整流程测试")
print("=" * 60)
# 服务器配置
base_url = "http://localhost:9099/dev-api"
# 测试问题
test_question = "今天有什么大事发生"
print(f"\n测试问题: {test_question}")
print("-" * 60)
# 首先需要登录获取token
print("\n步骤1: 登录获取token...")
# 这里需要您提供登录凭证
print("\n⚠️ 需要登录凭证才能测试")
print("请提供以下信息:")
username = input("用户名: ").strip() or "admin"
password = input("密码: ").strip() or "admin123"
login_url = f"{base_url}/login"
login_data = {
"username": username,
"password": password
}
try:
async with httpx.AsyncClient(timeout=30.0) as client:
# 登录
print(f"\n正在登录...")
response = await client.post(login_url, json=login_data)
if response.status_code != 200:
print(f"✗ 登录失败: HTTP {response.status_code}")
print(response.text)
return
result = response.json()
if result.get('code') != 200:
print(f"✗ 登录失败: {result.get('msg', '未知错误')}")
return
token = result.get('data', {}).get('access_token')
if not token:
print("✗ 未获取到token")
return
print(f"✓ 登录成功token: {token[:20]}...")
# 测试对话接口
print(f"\n步骤2: 调用对话接口...")
# 需要chat_id这里使用一个测试ID
chat_id = input("\n请输入chat_id (直接回车使用默认): ").strip()
if not chat_id:
print("⚠️ 需要有效的chat_id才能调用RAGFlow")
print("请在RAGFlow中创建一个聊天助手并提供其chat_id")
return
converse_url = f"{base_url}/system/ragflow/converse_with_chat_assistant"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
converse_data = {
"chat_id": chat_id,
"question": test_question,
"stream": False
}
print(f"\n发送请求到: {converse_url}")
print(f"问题: {test_question}")
print(f"Chat ID: {chat_id}")
response = await client.post(converse_url, json=converse_data, headers=headers)
print(f"\n响应状态码: {response.status_code}")
if response.status_code == 200:
result = response.json()
print("\n✓ 请求成功!")
print("\n" + "=" * 60)
print("响应内容:")
print("=" * 60)
print(json.dumps(result, ensure_ascii=False, indent=2))
# 提取答案
if result.get('code') == 200:
data = result.get('data', {})
answer = data.get('answer') or data.get('content') or data.get('message', '')
if answer:
print("\n" + "=" * 60)
print("【机器人回答】")
print("=" * 60)
print(answer)
print("=" * 60)
else:
print(f"\n⚠️ 业务错误: {result.get('msg', '未知错误')}")
else:
print(f"\n✗ 请求失败")
print(response.text)
except httpx.RequestError as e:
print(f"\n✗ 网络错误: {e}")
except Exception as e:
print(f"\n✗ 未知错误: {type(e).__name__}: {e}")
import traceback
traceback.print_exc()
print("\n" + "=" * 60)
print("测试完成")
print("=" * 60)
if __name__ == "__main__":
print("\n提示:此测试需要以下条件:")
print("1. ✓ 搜索服务API密钥已配置")
print("2. ? FastAPI服务器正在运行 (python server.py)")
print("3. ? RAGFlow服务正在运行")
print("4. ? 有效的用户账号和chat_id")
print("\n如果服务器未运行,请先在另一个终端运行: python server.py")
input("\n按回车继续测试...")
asyncio.run(test_search_llm_flow())

View File

@ -1,108 +0,0 @@
"""
简单的搜索API测试脚本 - 直接测试HTTP请求
"""
import asyncio
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv('.env.dev')
try:
import httpx
except ImportError:
print("错误: 缺少 httpx 模块")
print("请运行: pip install httpx")
exit(1)
async def test_search_api():
"""直接测试SerpAPI"""
api_key = os.getenv('SEARCH_API_KEY', '')
print("=" * 60)
print("搜索API测试")
print("=" * 60)
print(f"API Key: {'已配置' if api_key else '未配置'}")
if not api_key:
print("\n错误: SEARCH_API_KEY 未配置")
print("请在 .env.dev 中设置 SEARCH_API_KEY")
return
# 测试查询
query = "北京天气"
print(f"\n测试查询: {query}")
print("-" * 60)
# 构建请求
url = "https://serpapi.com/search"
params = {
'q': query,
'api_key': api_key,
'engine': 'google',
'hl': 'zh-cn',
'gl': 'cn',
'num': 5,
}
try:
async with httpx.AsyncClient(timeout=15.0) as client:
print("发送请求...")
response = await client.get(url, params=params)
print(f"状态码: {response.status_code}")
if response.status_code == 200:
data = response.json()
print("\n✓ 搜索成功!")
# 显示答案框
answer_box = data.get('answer_box', {})
if answer_box:
answer = answer_box.get('answer') or answer_box.get('snippet', '')
if answer:
print(f"\n【快速答案】")
print(answer)
# 显示搜索结果
organic_results = data.get('organic_results', [])
if organic_results:
print(f"\n【搜索结果】({len(organic_results)}条)")
for idx, item in enumerate(organic_results[:3], 1):
print(f"\n{idx}. {item.get('title', 'N/A')}")
print(f" {item.get('link', 'N/A')}")
snippet = item.get('snippet', '')
if snippet:
print(f" {snippet[:100]}...")
# 显示API使用情况
search_metadata = data.get('search_metadata', {})
if search_metadata:
print(f"\n【API信息】")
print(f"总耗时: {search_metadata.get('total_time_taken', 'N/A')}")
print(f"请求ID: {search_metadata.get('id', 'N/A')}")
else:
print(f"\n✗ 请求失败")
try:
error_data = response.json()
print(f"错误: {error_data.get('error', response.text)}")
except:
print(f"错误: {response.text}")
except httpx.RequestError as e:
print(f"\n✗ 网络错误: {e}")
except Exception as e:
print(f"\n✗ 未知错误: {type(e).__name__}: {e}")
print("\n" + "=" * 60)
print("测试完成")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(test_search_api())

View File

@ -24,7 +24,6 @@ async def test_search_with_llm():
# 导入服务
from module_admin.service.search_service import SearchService, SearchServiceError
from utils.ragflow_client_manager import init_ragflow_client, get_ragflow_client
# 测试问题
test_question = "今天有什么大事发生"
@ -61,90 +60,49 @@ async def test_search_with_llm():
print(f"\n✗ 搜索失败: {e}")
return
# 步骤3: 初始化RAGFlow客户端
print("\n步骤3: 初始化RAGFlow客户端...")
# 步骤3: 初始化DeepSeek客户端
print("\n步骤3: 初始化DeepSeek客户端...")
try:
await init_ragflow_client()
client = await get_ragflow_client()
print("RAGFlow客户端初始化成功")
from utils.deepseek_client import DeepSeekAPIClient
client = DeepSeekAPIClient()
print("DeepSeek客户端初始化成功")
except Exception as e:
print(f"\nRAGFlow初始化失败: {e}")
print("\n⚠️ 需要配置RAGFlow服务才能测试LLM处理")
print(f"\nDeepSeek初始化失败: {e}")
print("\n⚠️ 需要配置DeepSeek服务才能测试LLM处理")
print("请在 .env.dev 中配置:")
print(" RAGFLOW_BASE_URL=http://your_ragflow_server")
print(" RAGFLOW_API_KEY=your_api_key")
print(" DEEPSEEK_API_KEY=your_deepseek_api_key")
return
# 步骤4: 构建增强提示词
print("\n步骤4: 构建LLM提示词...")
enhanced_question = f"""请基于以下搜索结果,简洁准确地回答用户的问题。
用户问题{test_question}
搜索结果
{context}
要求
1. 直接回答问题不要说"根据搜索结果"等前缀
2. 答案要简洁准确突出重点
3. 如果是天气查询只返回当前天气状况和温度
4. 如果是新闻查询只返回最重要的新闻摘要
5. 用自然的语气回答像人类助手一样"""
# 步骤4: 准备传递给DeepSeek的内容
print("\n步骤4: 准备DeepSeek输入...")
print("✓ 提示词构建完成")
print(f"\n【提示词预览】")
print(enhanced_question[:300] + '...')
# 步骤5: 调用RAGFlow LLM
print("\n步骤5: 调用RAGFlow LLM生成答案...")
print("⚠️ 需要提供chat_id才能调用LLM")
chat_id = input("\n请输入chat_id (直接回车跳过LLM测试): ").strip()
if not chat_id:
print("\n✓ 跳过LLM测试")
print("\n" + "=" * 70)
print("测试总结")
print("=" * 70)
print("✓ 搜索服务工作正常")
print("✓ 上下文提取成功")
print("⚠️ 未测试LLM处理需要chat_id")
print("\n要完整测试,请:")
print("1. 在RAGFlow中创建聊天助手")
print("2. 获取chat_id")
print("3. 重新运行此脚本并输入chat_id")
return
# 步骤5: 调用DeepSeek LLM
print("\n步骤5: 调用DeepSeek LLM生成答案...")
try:
# 调用RAGFlow
result = await client.converse_with_chat_assistant(
chat_id=chat_id,
question=enhanced_question,
stream=False
# 调用DeepSeek API
answer = await client.chat(
question=test_question,
context=context,
temperature=0.7,
max_tokens=1024
)
print("✓ LLM调用成功!")
# 提取答案
if result.get('code') == 0:
data = result.get('data', {})
answer = data.get('answer', '')
print("\n" + "=" * 70)
print("【机器人最终回答】")
print("=" * 70)
print(answer)
print("=" * 70)
print("\n✓ 完整流程测试成功!")
print("\n流程总结:")
print(" 1. 检测到搜索关键词 → 触发搜索服务")
print(" 2. 调用SerpAPI → 获取搜索结果")
print(" 3. 提取关键信息 → 构建LLM上下文")
print(" 4. 调用RAGFlow LLM → 生成自然语言答案")
print(" 5. 返回简洁答案 → 用户体验优化")
else:
print(f"\n⚠️ LLM返回错误: {result.get('msg', '未知错误')}")
print("\n" + "=" * 70)
print("【机器人最终回答】")
print("=" * 70)
print(answer)
print("=" * 70)
print("\n✓ 完整流程测试成功!")
print("\n流程总结:")
print(" 1. 检测到搜索关键词 → 触发搜索服务")
print(" 2. 调用SerpAPI → 获取搜索结果")
print(" 3. 提取关键信息 → 构建LLM上下文")
print(" 4. 调用DeepSeek LLM → 生成自然语言答案")
print(" 5. 返回简洁答案 → 用户体验优化")
except Exception as e:
print(f"\n✗ LLM调用失败: {e}")
@ -159,8 +117,7 @@ async def test_search_with_llm():
if __name__ == "__main__":
print("\n提示:此测试需要以下配置:")
print("1. ✓ SEARCH_API_KEY (搜索服务)")
print("2. ✓ RAGFLOW_BASE_URL 和 RAGFLOW_API_KEY (LLM服务)")
print("3. ? chat_id (可选用于LLM测试)")
print("2. ✓ DEEPSEEK_API_KEY (LLM服务)")
print()
try:

View File

@ -1,83 +0,0 @@
"""
搜索服务测试脚本
"""
import asyncio
import sys
import os
# 添加项目路径到Python路径
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from module_admin.service.search_service import SearchService, SearchServiceError
async def test_search_service():
"""测试搜索服务"""
print("=" * 60)
print("搜索服务测试")
print("=" * 60)
# 测试用例
test_queries = [
"北京今天天气怎么样",
"最新的人工智能新闻",
"搜索Python编程",
]
for query in test_queries:
print(f"\n{'='*60}")
print(f"测试查询: {query}")
print(f"{'='*60}")
# 检查是否应该处理
should_handle = SearchService.should_handle(query)
print(f"✓ 关键词检测: {'通过' if should_handle else '未通过'}")
if should_handle:
try:
# 获取搜索结果不使用Redis缓存
result = await SearchService.get_search_answer(query, redis=None)
print(f"\n✓ 搜索成功!")
print(f"\n类型: {result.get('type')}")
print(f"查询: {result.get('query')}")
# 显示答案框(如果有)
answer_box = result.get('answer_box', {})
if answer_box:
answer = answer_box.get('answer') or answer_box.get('snippet', '')
if answer:
print(f"\n【快速答案】\n{answer}")
# 显示搜索结果
results = result.get('results', [])
if results:
print(f"\n【搜索结果】({len(results)}条)")
for idx, item in enumerate(results[:3], 1): # 只显示前3条
print(f"\n{idx}. {item.get('title', 'N/A')}")
print(f" 链接: {item.get('link', 'N/A')}")
snippet = item.get('snippet', '')
if snippet:
print(f" 摘要: {snippet[:100]}...")
# 显示格式化消息
print(f"\n【格式化消息】")
message = result.get('message', '')
# 只显示前500个字符
print(message[:500] + ('...' if len(message) > 500 else ''))
except SearchServiceError as e:
print(f"\n✗ 搜索失败: {e}")
except Exception as e:
print(f"\n✗ 未知错误: {type(e).__name__}: {e}")
print()
print("=" * 60)
print("测试完成")
print("=" * 60)
if __name__ == "__main__":
asyncio.run(test_search_service())

View File

@ -0,0 +1,137 @@
from openai import AsyncOpenAI
from typing import Any, Dict, List, Optional
from config.env import DeepSeekConfig
from utils.log_util import logger
class DeepSeekAPIError(Exception):
"""DeepSeek API调用异常"""
def __init__(self, status_code: int, message: str):
self.status_code = status_code
self.message = message
super().__init__(f"DeepSeek API error {status_code}: {message}")
class DeepSeekAPIClient:
"""DeepSeek API客户端 - 用于调用DeepSeek大语言模型
使用官方推荐的OpenAI SDK进行调用
"""
def __init__(self, base_url: str = None, api_key: str = None, timeout: float = 30.0):
self.base_url = base_url or DeepSeekConfig.DEEPSEEK_API_BASE
self.base_url = self.base_url.rstrip('/') if self.base_url else 'https://api.deepseek.com'
self.api_key = api_key or DeepSeekConfig.DEEPSEEK_API_KEY
self.timeout = timeout
self.model = DeepSeekConfig.DEEPSEEK_MODEL
# 初始化OpenAI客户端
if not self.api_key:
raise DeepSeekAPIError(401, 'DeepSeek API密钥未配置')
self.client = AsyncOpenAI(
api_key=self.api_key,
base_url=self.base_url,
timeout=self.timeout
)
async def chat_completion(
self,
messages: List[Dict[str, str]],
model: str = None,
temperature: float = 0.7,
max_tokens: int = 1024,
stream: bool = False,
) -> Dict[str, Any]:
"""调用DeepSeek聊天补全API
Args:
messages: 聊天消息列表格式为 [{"role": "user", "content": "你的问题"}]
model: 模型名称默认使用配置中的模型
temperature: 温度参数控制生成内容的随机性
max_tokens: 最大生成token数
stream: 是否流式返回
Returns:
API返回的JSON响应
Raises:
DeepSeekAPIError: API调用失败时抛出
"""
model = model or self.model
try:
response = await self.client.chat.completions.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
stream=stream
)
# 转换为字典格式返回,保持与原有接口兼容
if not stream:
return response.model_dump()
else:
return response # 流式响应直接返回
except Exception as exc:
logger.error(f"DeepSeek API调用失败: {str(exc)}")
raise DeepSeekAPIError(500, str(exc)) from exc
async def chat(
self,
question: str,
context: str = None,
model: str = None,
temperature: float = 0.7,
max_tokens: int = 1024,
) -> str:
"""简化的聊天接口
Args:
question: 用户问题
context: 上下文信息可选
model: 模型名称
temperature: 温度参数
max_tokens: 最大生成token数
Returns:
生成的回答
Raises:
DeepSeekAPIError: API调用失败时抛出
"""
# 构建消息列表
messages = []
# 如果有上下文,添加到系统消息中
if context:
messages.append({
"role": "system",
"content": f"基于以下上下文信息回答用户问题:\n{context}"
})
# 添加用户问题
messages.append({
"role": "user",
"content": question
})
# 调用API
response = await self.chat_completion(
messages=messages,
model=model,
temperature=temperature,
max_tokens=max_tokens,
stream=False
)
# 提取回答
answer = response.get('choices', [{}])[0].get('message', {}).get('content', '')
if not answer:
logger.error(f"DeepSeek API返回空回答: {response}")
raise DeepSeekAPIError(200, 'DeepSeek API返回空回答')
return answer.strip()