修改了测试服务,添加了搜索直连DeepSeek大语言模型的测试
This commit is contained in:
parent
acf1f7c568
commit
1465e1564e
@ -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()
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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())
|
||||
@ -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())
|
||||
@ -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"\n✗ RAGFlow初始化失败: {e}")
|
||||
print("\n⚠️ 需要配置RAGFlow服务才能测试LLM处理")
|
||||
print(f"\n✗ DeepSeek初始化失败: {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:
|
||||
|
||||
@ -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())
|
||||
137
ruoyi-fastapi-backend/utils/deepseek_client.py
Normal file
137
ruoyi-fastapi-backend/utils/deepseek_client.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user