From 11be119ffccdea8a11129273c3694f06f6b0f20f Mon Sep 17 00:00:00 2001 From: python Date: Wed, 10 Dec 2025 10:05:45 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=8E=AF=E5=A2=83=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=EF=BC=8C=E6=94=AF=E6=8C=81=E5=A4=9A=E7=A7=8DAI?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=8F=90=E4=BE=9B=E5=95=86=EF=BC=88=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E5=92=8C=E7=A1=85=E5=9F=BA=E6=B5=81=E5=8A=A8=EF=BC=89?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=BC=BAAPI=E8=B0=83=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E7=81=B5=E6=B4=BB=E6=80=A7=E5=92=8C=E5=8F=AF=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=80=A7=EF=BC=8C=E5=90=8C=E6=97=B6=E6=9B=B4=E6=96=B0=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=BB=A5=E5=8F=8D=E6=98=A0=E6=96=B0=E7=9A=84=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=80=89=E9=A1=B9=E5=92=8C=E4=BD=BF=E7=94=A8=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 58 +++++++- .env.example | 56 +++++++- README.md | 38 +++-- services/ai_service.py | 310 +++++++++++++++++++++++++++++++++-------- template_ai_helper.py | 79 ++++++++--- 5 files changed, 447 insertions(+), 94 deletions(-) diff --git a/.env b/.env index 8695945..edf2747 100644 --- a/.env +++ b/.env @@ -1,18 +1,68 @@ -# 华为大模型API配置(必需) +# ========== AI服务提供商配置 ========== +# 选择使用的AI服务提供商 +# 可选值: 'huawei' 或 'siliconflow' +# 默认值: 'siliconflow' +AI_PROVIDER=siliconflow + +# ========== 华为大模型API配置 ========== +# 当 AI_PROVIDER=huawei 时使用以下配置 + # API端点地址 HUAWEI_API_ENDPOINT=http://10.100.31.26:3001/v1/chat/completions # API密钥 HUAWEI_API_KEY=sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186 -HUAWEI_API_TIMEOUT=900 - # 模型名称 HUAWEI_MODEL=DeepSeek-R1-Distill-Llama-70B -# 数据库配置 +# API超时配置(秒) +# 开启思考模式时,响应时间会显著增加,需要更长的超时时间 +# 默认180秒(3分钟) +HUAWEI_API_TIMEOUT=180 + +# API最大token数配置 +# 开启思考模式时,模型可能生成更长的响应,需要更多的token +# 默认12000 +HUAWEI_API_MAX_TOKENS=12000 + +# ========== 硅基流动API配置 ========== +# 当 AI_PROVIDER=siliconflow 时使用以下配置 + +# API端点地址(默认值,通常不需要修改) +SILICONFLOW_URL=https://api.siliconflow.cn/v1/chat/completions + +# API密钥(必需) +SILICONFLOW_API_KEY=sk-pgujibohpenkomkwlufexmqzyckglgogdiubfplgqxkfqgfu + +# 模型名称(默认值,通常不需要修改) +SILICONFLOW_MODEL=Qwen/Qwen2.5-72B-Instruct + +# API超时配置(秒) +# 默认120秒 +SILICONFLOW_API_TIMEOUT=120 + +# API最大token数配置 +# 默认2000 +SILICONFLOW_API_MAX_TOKENS=2000 + +# ========== 数据库配置 ========== DB_HOST=152.136.177.240 DB_PORT=5012 DB_USER=finyx DB_PASSWORD=6QsGK6MpePZDE57Z DB_NAME=finyx + +# ========== MinIO配置(可选,文档生成功能需要) ========== +MINIO_ENDPOINT=minio.datacubeworld.com:9000 +MINIO_ACCESS_KEY=JOLXFXny3avFSzB0uRA5 +MINIO_SECRET_KEY=G1BR8jStNfovkfH5ou39EmPl34E4l7dGrnd3Cz0I +MINIO_BUCKET=finyx +MINIO_SECURE=true + +# ========== 服务配置 ========== +# 服务端口 +PORT=7500 + +# 调试模式(true/false) +DEBUG=False diff --git a/.env.example b/.env.example index ac8fee7..edf2747 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,12 @@ -# 华为大模型API配置(必需) +# ========== AI服务提供商配置 ========== +# 选择使用的AI服务提供商 +# 可选值: 'huawei' 或 'siliconflow' +# 默认值: 'siliconflow' +AI_PROVIDER=siliconflow + +# ========== 华为大模型API配置 ========== +# 当 AI_PROVIDER=huawei 时使用以下配置 + # API端点地址 HUAWEI_API_ENDPOINT=http://10.100.31.26:3001/v1/chat/completions @@ -8,9 +16,53 @@ HUAWEI_API_KEY=sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186 # 模型名称 HUAWEI_MODEL=DeepSeek-R1-Distill-Llama-70B -# 数据库配置 +# API超时配置(秒) +# 开启思考模式时,响应时间会显著增加,需要更长的超时时间 +# 默认180秒(3分钟) +HUAWEI_API_TIMEOUT=180 + +# API最大token数配置 +# 开启思考模式时,模型可能生成更长的响应,需要更多的token +# 默认12000 +HUAWEI_API_MAX_TOKENS=12000 + +# ========== 硅基流动API配置 ========== +# 当 AI_PROVIDER=siliconflow 时使用以下配置 + +# API端点地址(默认值,通常不需要修改) +SILICONFLOW_URL=https://api.siliconflow.cn/v1/chat/completions + +# API密钥(必需) +SILICONFLOW_API_KEY=sk-pgujibohpenkomkwlufexmqzyckglgogdiubfplgqxkfqgfu + +# 模型名称(默认值,通常不需要修改) +SILICONFLOW_MODEL=Qwen/Qwen2.5-72B-Instruct + +# API超时配置(秒) +# 默认120秒 +SILICONFLOW_API_TIMEOUT=120 + +# API最大token数配置 +# 默认2000 +SILICONFLOW_API_MAX_TOKENS=2000 + +# ========== 数据库配置 ========== DB_HOST=152.136.177.240 DB_PORT=5012 DB_USER=finyx DB_PASSWORD=6QsGK6MpePZDE57Z DB_NAME=finyx + +# ========== MinIO配置(可选,文档生成功能需要) ========== +MINIO_ENDPOINT=minio.datacubeworld.com:9000 +MINIO_ACCESS_KEY=JOLXFXny3avFSzB0uRA5 +MINIO_SECRET_KEY=G1BR8jStNfovkfH5ou39EmPl34E4l7dGrnd3Cz0I +MINIO_BUCKET=finyx +MINIO_SECURE=true + +# ========== 服务配置 ========== +# 服务端口 +PORT=7500 + +# 调试模式(true/false) +DEBUG=False diff --git a/README.md b/README.md index a61c9ef..477a458 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,10 @@ - ✅ AI解析接口 (`/api/ai/extract`) - 从输入文本中提取结构化字段 - ✅ 字段配置管理 - 从数据库读取字段配置 -- ✅ 支持华为大模型(DeepSeek-R1-Distill-Llama-70B) +- ✅ 支持多种AI服务提供商: + - 华为大模型(DeepSeek-R1-Distill-Llama-70B) + - 硅基流动(DeepSeek-V3.2-Exp) +- ✅ 可通过配置灵活切换AI服务提供商 - ✅ Web测试界面 - 可视化测试解析功能 ## 项目结构 @@ -69,20 +72,30 @@ copy .env.example .env cp .env.example .env ``` -编辑 `.env` 文件,填入你的API密钥: +编辑 `.env` 文件,填入你的配置: ```env -# 华为大模型API配置(必需,已内置默认值,如需修改可调整) -# API端点地址 +# ========== AI服务提供商配置 ========== +# 选择使用的AI服务提供商 +# 可选值: 'huawei' 或 'siliconflow' +# 默认值: 'siliconflow' +AI_PROVIDER=siliconflow + +# ========== 华为大模型API配置(当 AI_PROVIDER=huawei 时使用) ========== HUAWEI_API_ENDPOINT=http://10.100.31.26:3001/v1/chat/completions - -# API密钥 HUAWEI_API_KEY=sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186 - -# 模型名称 HUAWEI_MODEL=DeepSeek-R1-Distill-Llama-70B +HUAWEI_API_TIMEOUT=180 +HUAWEI_API_MAX_TOKENS=12000 -# 数据库配置(已默认配置,如需修改可调整) +# ========== 硅基流动API配置(当 AI_PROVIDER=siliconflow 时使用) ========== +SILICONFLOW_URL=https://api.siliconflow.cn/v1/chat/completions +SILICONFLOW_API_KEY=your_siliconflow_api_key_here +SILICONFLOW_MODEL=deepseek-ai/DeepSeek-V3.2-Exp +SILICONFLOW_API_TIMEOUT=120 +SILICONFLOW_API_MAX_TOKENS=2000 + +# ========== 数据库配置 ========== DB_HOST=152.136.177.240 DB_PORT=5012 DB_USER=finyx @@ -90,6 +103,13 @@ DB_PASSWORD=6QsGK6MpePZDE57Z DB_NAME=finyx ``` +**AI服务提供商选择说明:** + +- **华为大模型**:设置 `AI_PROVIDER=huawei`,并配置 `HUAWEI_API_KEY` 和 `HUAWEI_API_ENDPOINT` +- **硅基流动**:设置 `AI_PROVIDER=siliconflow`(默认值),并配置 `SILICONFLOW_API_KEY` + +如果配置的AI服务不完整,系统会自动尝试使用另一个可用的服务。 + **华为大模型API调用示例:** ```bash diff --git a/services/ai_service.py b/services/ai_service.py index 20a91d7..4d39f0c 100644 --- a/services/ai_service.py +++ b/services/ai_service.py @@ -31,7 +31,12 @@ class AIService: """AI服务类""" def __init__(self): - # 华为大模型配置(必需) + # ========== AI服务提供商选择 ========== + # 通过环境变量 AI_PROVIDER 选择使用的AI服务 + # 可选值: 'huawei' 或 'siliconflow',默认为 'siliconflow' + self.ai_provider_config = os.getenv('AI_PROVIDER', 'siliconflow').lower() + + # ========== 华为大模型配置 ========== self.huawei_api_endpoint = os.getenv('HUAWEI_API_ENDPOINT', 'http://10.100.31.26:3001/v1/chat/completions') self.huawei_api_key = os.getenv('HUAWEI_API_KEY', 'sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186') self.huawei_model = os.getenv('HUAWEI_MODEL', 'DeepSeek-R1-Distill-Llama-70B') @@ -39,16 +44,39 @@ class AIService: # API超时配置(秒) # 开启思考模式时,响应时间会显著增加,需要更长的超时时间 # 可以通过环境变量 HUAWEI_API_TIMEOUT 自定义,默认180秒(3分钟) - self.api_timeout = int(os.getenv('HUAWEI_API_TIMEOUT', '180')) + self.huawei_api_timeout = int(os.getenv('HUAWEI_API_TIMEOUT', '180')) # API最大token数配置 # 开启思考模式时,模型可能生成更长的响应,需要更多的token # 可以通过环境变量 HUAWEI_API_MAX_TOKENS 自定义,默认12000 - self.api_max_tokens = int(os.getenv('HUAWEI_API_MAX_TOKENS', '12000')) + self.huawei_api_max_tokens = int(os.getenv('HUAWEI_API_MAX_TOKENS', '12000')) + + # ========== 硅基流动配置 ========== + self.siliconflow_url = os.getenv('SILICONFLOW_URL', 'https://api.siliconflow.cn/v1/chat/completions') + self.siliconflow_api_key = os.getenv('SILICONFLOW_API_KEY', '') + self.siliconflow_model = os.getenv('SILICONFLOW_MODEL', 'deepseek-ai/DeepSeek-V3.2-Exp') + + # API超时配置(秒) + self.siliconflow_api_timeout = int(os.getenv('SILICONFLOW_API_TIMEOUT', '120')) + + # API最大token数配置 + self.siliconflow_api_max_tokens = int(os.getenv('SILICONFLOW_API_MAX_TOKENS', '2000')) # 确定使用的AI服务 self.ai_provider = self._determine_ai_provider() + # 根据选择的提供商设置当前使用的超时和token配置 + if self.ai_provider == 'huawei': + self.api_timeout = self.huawei_api_timeout + self.api_max_tokens = self.huawei_api_max_tokens + elif self.ai_provider == 'siliconflow': + self.api_timeout = self.siliconflow_api_timeout + self.api_max_tokens = self.siliconflow_api_max_tokens + else: + # 默认值 + self.api_timeout = 120 + self.api_max_tokens = 2000 + # 初始化AI日志记录器 if AI_LOGGER_AVAILABLE: try: @@ -60,11 +88,48 @@ class AIService: self.ai_logger = None def _determine_ai_provider(self) -> str: - """确定使用的AI服务提供商(仅支持华为大模型)""" - if self.huawei_api_endpoint and self.huawei_api_key: - return 'huawei' + """ + 确定使用的AI服务提供商 + 根据 AI_PROVIDER 环境变量配置选择,支持 'huawei' 或 'siliconflow' + """ + # 根据配置选择服务提供商 + if self.ai_provider_config == 'huawei': + # 检查华为大模型配置是否完整 + if self.huawei_api_endpoint and self.huawei_api_key: + print(f"[AI服务] 使用华为大模型: {self.huawei_model}") + return 'huawei' + else: + print(f"[AI服务] 警告: 配置为使用华为大模型,但配置不完整,尝试使用硅基流动") + # 如果华为配置不完整,尝试使用硅基流动 + if self.siliconflow_api_key and self.siliconflow_url: + print(f"[AI服务] 使用硅基流动: {self.siliconflow_model}") + return 'siliconflow' + else: + return 'none' + elif self.ai_provider_config == 'siliconflow': + # 检查硅基流动配置是否完整 + if self.siliconflow_api_key and self.siliconflow_url: + print(f"[AI服务] 使用硅基流动: {self.siliconflow_model}") + return 'siliconflow' + else: + print(f"[AI服务] 警告: 配置为使用硅基流动,但配置不完整,尝试使用华为大模型") + # 如果硅基流动配置不完整,尝试使用华为大模型 + if self.huawei_api_endpoint and self.huawei_api_key: + print(f"[AI服务] 使用华为大模型: {self.huawei_model}") + return 'huawei' + else: + return 'none' else: - return 'none' + # 未知的配置值,尝试自动检测 + print(f"[AI服务] 警告: 未知的AI_PROVIDER配置值 '{self.ai_provider_config}',尝试自动检测") + if self.siliconflow_api_key and self.siliconflow_url: + print(f"[AI服务] 自动选择硅基流动: {self.siliconflow_model}") + return 'siliconflow' + elif self.huawei_api_endpoint and self.huawei_api_key: + print(f"[AI服务] 自动选择华为大模型: {self.huawei_model}") + return 'huawei' + else: + return 'none' def extract_fields(self, prompt: str, output_fields: List[Dict]) -> Optional[Dict]: """ @@ -78,80 +143,207 @@ class AIService: 提取的字段字典,格式: {field_code: field_value} """ if self.ai_provider == 'none': - raise Exception("未配置华为大模型服务,请设置 HUAWEI_API_KEY 和 HUAWEI_API_ENDPOINT") + raise Exception("未配置AI服务,请设置 AI_PROVIDER 环境变量('huawei' 或 'siliconflow'),并配置相应的API密钥") if self.ai_provider == 'huawei': return self._extract_with_huawei(prompt, output_fields) + elif self.ai_provider == 'siliconflow': + return self._extract_with_siliconflow(prompt, output_fields) else: raise Exception(f"未知的AI服务提供商: {self.ai_provider}") def _extract_with_siliconflow(self, prompt: str, output_fields: List[Dict]) -> Optional[Dict]: """ - 使用硅基流动API提取字段(已不再使用,仅保留用于参考) - 系统仅支持华为大模型,不再支持自动回退 + 使用硅基流动API提取字段(临时启用) """ + # 生成会话ID(用于关联同一次调用的请求和响应) + session_id = f"session_{int(time.time() * 1000)}" + + payload = { + "model": self.siliconflow_model, + "messages": [ + { + "role": "system", + "content": "你是一个专业的数据提取助手。请从输入文本中提取结构化信息,并严格按照JSON格式返回结果。\n\n⚠️ 核心要求(必须严格遵守):\n\n1. 字段提取要求:\n - 如果文本中明确提到信息(如性别、年龄、职务、职级、线索来源等),必须提取,绝对不能设为空字符串\n - 性别字段(target_gender):如果文本中出现\"男\"、\"女\"、\"男性\"、\"女性\"、\"先生\"、\"女士\"等任何表示性别的词汇,必须提取并转换为\"男\"或\"女\",不能为空\n - 职级字段(target_professional_rank):如果文本中提到\"正处级\"、\"副处级\"、\"正科级\"等,必须提取,不能为空\n - 线索来源字段(clue_source):如果文本中提到\"举报\"、\"群众举报\"、\"来信\"等,必须提取,不能为空\n\n2. 字段名格式要求(严格禁止错误):\n - 必须使用\"target_professional_rank\",禁止使用\"_professional_rank\"或任何下划线前缀\n - 必须使用\"clue_source\",禁止使用\"_source\"、\"source\"或任何下划线前缀\n - 必须使用\"target_organization\",禁止使用\"target_organisation\"(英式拼写)\n - 所有字段名必须严格按照JSON示例中的字段编码,不能随意修改\n\n3. JSON格式要求:\n - 只返回JSON对象,不要包含任何其他文字、思考过程、markdown代码块标记或```json标记\n - 所有字段名必须使用双引号\n - 必须返回所有要求的字段,即使值为空字符串也要包含在JSON中\n - JSON格式必须完整且有效,不能有语法错误\n\n4. 提取逻辑:\n - 逐字逐句仔细阅读输入文本,不要遗漏任何信息\n - 对于性别、职级、线索来源等关键字段,请特别仔细查找\n - 如果文本中明确提到某个信息,必须提取出来,不能设为空" + }, + { + "role": "user", + "content": prompt + } + ], + "temperature": 0.2, + "max_tokens": self.siliconflow_api_max_tokens + } + + headers = { + "Authorization": f"Bearer {self.siliconflow_api_key}", + "Content-Type": "application/json" + } + + # 记录请求信息(发送请求前) + api_request_info = { + "endpoint": self.siliconflow_url, + "model": self.siliconflow_model, + "messages": payload["messages"], + "temperature": payload.get("temperature"), + "max_tokens": payload.get("max_tokens"), + } + if self.ai_logger: + self.ai_logger.log_request_only(prompt, api_request_info, session_id) + + print(f"[AI服务] 正在调用硅基流动API...") + + extracted_data = None + error_message = None + try: - payload = { - "model": self.siliconflow_model, - "messages": [ - { - "role": "system", - "content": "你是一个专业的数据提取助手,能够从文本中准确提取结构化信息。请严格按照JSON格式返回结果。" - }, - { - "role": "user", - "content": prompt - } - ], - "temperature": 0.3, - "max_tokens": 2000 - } - - headers = { - "Authorization": f"Bearer {self.siliconflow_api_key}", - "Content-Type": "application/json" - } - response = requests.post( self.siliconflow_url, json=payload, headers=headers, - timeout=30 + timeout=self.siliconflow_api_timeout ) if response.status_code != 200: - raise Exception(f"API调用失败: {response.status_code} - {response.text}") + error_message = f"API调用失败: {response.status_code} - {response.text}" + # 记录错误 + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=None, + extracted_data=None, + error=error_message, + session_id=session_id + ) + raise Exception(error_message) result = response.json() # 提取AI返回的内容 if 'choices' in result and len(result['choices']) > 0: - content = result['choices'][0]['message']['content'] + raw_content = result['choices'][0]['message']['content'] - # 尝试解析JSON - try: - # 如果返回的是代码块,提取JSON部分 - if '```json' in content: - json_start = content.find('```json') + 7 - json_end = content.find('```', json_start) - content = content[json_start:json_end].strip() - elif '```' in content: - json_start = content.find('```') + 3 - json_end = content.find('```', json_start) - content = content[json_start:json_end].strip() - - extracted_data = json.loads(content) - return extracted_data - except json.JSONDecodeError: - # 如果不是JSON,尝试从文本中提取 - return self._parse_text_response(content, output_fields) + # 调试:打印原始返回内容(前500字符) + print(f"[AI服务] API返回的原始内容(前500字符): {raw_content[:500]}") + + # 清理内容 + content = raw_content + + # 尝试解析JSON(使用增强的修复机制) + extracted_data = self._extract_json_from_text(content) + if extracted_data: + print(f"[AI服务] JSON解析成功,提取到 {len(extracted_data)} 个字段") + print(f"[AI服务] 原始字段名: {list(extracted_data.keys())}") + # 规范化字段名并映射到正确的字段编码 + normalized_data = self._normalize_field_names(extracted_data, output_fields) + print(f"[AI服务] 规范化后的字段名: {list(normalized_data.keys())}") + # 打印关键字段的值用于调试 + for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth']: + if key in normalized_data: + print(f"[AI服务] 规范化后 {key} = '{normalized_data[key]}'") + # 规范化日期格式 + normalized_data = self._normalize_date_formats(normalized_data, output_fields) + # 后处理:从已有信息推断缺失字段(传入原始prompt以便从输入文本中提取) + normalized_data = self._post_process_inferred_fields(normalized_data, output_fields, prompt) + # 打印后处理后的关键字段 + for key in ['target_name', 'target_gender', 'target_age', 'target_date_of_birth', 'target_organization', 'target_position']: + if key in normalized_data: + print(f"[AI服务] 后处理后 {key} = '{normalized_data[key]}'") + # 即使提取的字段不完整,也返回结果(更宽容的处理) + if any(v for v in normalized_data.values() if v): # 至少有一个非空字段 + print(f"[AI服务] 返回提取的数据(包含 {sum(1 for v in normalized_data.values() if v)} 个非空字段)") + # 记录成功的对话 + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=result, + extracted_data=normalized_data, + error=None, + session_id=session_id + ) + return normalized_data + else: + print(f"[AI服务] 警告:提取的数据全部为空,但继续返回(允许部分字段为空)") + # 记录对话(即使数据为空) + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=result, + extracted_data=normalized_data, + error="提取的数据全部为空", + session_id=session_id + ) + return normalized_data + + # 如果无法提取JSON,记录错误但尝试更宽容的处理 + print(f"[AI服务] 警告:无法从内容中提取完整JSON,尝试备用解析方法") + print(f"[AI服务] 清理后的内容(前500字符): {content[:500]}") + + # 尝试从文本中提取 + parsed_data = self._parse_text_response(content, output_fields) + if parsed_data and any(v for v in parsed_data.values() if v): # 至少有一个非空字段 + print(f"[AI服务] 使用备用方法解析成功,提取到 {len(parsed_data)} 个字段") + # 记录对话 + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=result, + extracted_data=parsed_data, + error=None, + session_id=session_id + ) + return parsed_data + + # 如果所有方法都失败,返回空字典而不是抛出异常(更宽容) + error_msg = f"无法从API返回内容中提取JSON数据。原始内容长度: {len(raw_content)}, 清理后内容长度: {len(content)}" + print(f"[AI服务] 警告:{error_msg}") + print(f"[AI服务] 完整内容: {content}") + # 返回一个包含所有输出字段的空字典,而不是抛出异常 + empty_result = {field['field_code']: '' for field in output_fields} + print(f"[AI服务] 返回空结果(包含 {len(empty_result)} 个字段,全部为空)") + # 记录失败的对话 + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=result, + extracted_data=empty_result, + error=error_msg, + session_id=session_id + ) + return empty_result else: - raise Exception("API返回格式异常") - - except requests.exceptions.Timeout: - raise Exception("AI服务调用超时") + error_msg = "API返回格式异常:未找到choices字段或choices为空" + # 记录错误 + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=result, + extracted_data=None, + error=error_msg, + session_id=session_id + ) + raise Exception(error_msg) + except Exception as e: - raise Exception(f"AI服务调用失败: {str(e)}") + # 如果发生异常,记录错误日志 + error_msg = str(e) + if self.ai_logger: + self.ai_logger.log_conversation( + prompt=prompt, + api_request=api_request_info, + api_response=None, + extracted_data=None, + error=error_msg, + session_id=session_id + ) + # 重新抛出异常,让上层处理 + raise def _extract_with_huawei(self, prompt: str, output_fields: List[Dict]) -> Optional[Dict]: """ @@ -249,7 +441,7 @@ class AIService: "top_p": 0.9, # 降低top_p,更聚焦 "top_k": 40, # 增加top_k,允许更多选择 "seed": 1, - "max_tokens": self.api_max_tokens, + "max_tokens": self.huawei_api_max_tokens, "n": 1, "enable_thinking": False # 关闭思考模式以提高JSON生成稳定性 } @@ -276,11 +468,11 @@ class AIService: enable_thinking = payload.get('enable_thinking', False) if enable_thinking: # 思考模式:使用配置的超时时间(默认180秒) - timeout = self.api_timeout + timeout = self.huawei_api_timeout print(f"[AI服务] 思考模式已开启,使用超时时间: {timeout}秒") else: # 非思考模式:使用较短的超时时间 - timeout = min(self.api_timeout, 120) # 最多120秒 + timeout = min(self.huawei_api_timeout, 120) # 最多120秒 print(f"[AI服务] 思考模式未开启,使用超时时间: {timeout}秒") extracted_data = None diff --git a/template_ai_helper.py b/template_ai_helper.py index ceb46a2..80f6ae4 100644 --- a/template_ai_helper.py +++ b/template_ai_helper.py @@ -15,17 +15,50 @@ class TemplateAIHelper: """模板AI辅助类,用于智能分析文档内容""" def __init__(self): - # 华为大模型配置(必需) + # ========== AI服务提供商选择 ========== + # 通过环境变量 AI_PROVIDER 选择使用的AI服务 + # 可选值: 'huawei' 或 'siliconflow',默认为 'siliconflow' + ai_provider = os.getenv('AI_PROVIDER', 'siliconflow').lower() + + # ========== 华为大模型配置 ========== huawei_key = os.getenv('HUAWEI_API_KEY', 'sk-PoeiV3qwyTIRqcVc84E8E24cD2904872859a87922e0d9186') huawei_endpoint = os.getenv('HUAWEI_API_ENDPOINT', 'http://10.100.31.26:3001/v1/chat/completions') + huawei_model = os.getenv('HUAWEI_MODEL', 'DeepSeek-R1-Distill-Llama-70B') - if not huawei_key or not huawei_endpoint: - raise Exception("未配置华为大模型服务,请设置 HUAWEI_API_KEY 和 HUAWEI_API_ENDPOINT") + # ========== 硅基流动配置 ========== + siliconflow_key = os.getenv('SILICONFLOW_API_KEY', '') + siliconflow_url = os.getenv('SILICONFLOW_URL', 'https://api.siliconflow.cn/v1/chat/completions') + siliconflow_model = os.getenv('SILICONFLOW_MODEL', 'deepseek-ai/DeepSeek-V3.2-Exp') - # 使用华为大模型 - self.api_key = huawei_key - self.model = os.getenv('HUAWEI_MODEL', 'DeepSeek-R1-Distill-Llama-70B') - self.api_url = huawei_endpoint + # 根据配置选择服务提供商 + if ai_provider == 'huawei': + if not huawei_key or not huawei_endpoint: + raise Exception("未配置华为大模型服务,请设置 HUAWEI_API_KEY 和 HUAWEI_API_ENDPOINT,或设置 AI_PROVIDER=siliconflow 使用硅基流动") + self.api_key = huawei_key + self.model = huawei_model + self.api_url = huawei_endpoint + print(f"[模板AI助手] 使用华为大模型: {huawei_model}") + elif ai_provider == 'siliconflow': + if not siliconflow_key: + raise Exception("未配置硅基流动服务,请设置 SILICONFLOW_API_KEY,或设置 AI_PROVIDER=huawei 使用华为大模型") + self.api_key = siliconflow_key + self.model = siliconflow_model + self.api_url = siliconflow_url + print(f"[模板AI助手] 使用硅基流动: {siliconflow_model}") + else: + # 自动检测:优先使用硅基流动,如果未配置则使用华为大模型 + if siliconflow_key and siliconflow_url: + self.api_key = siliconflow_key + self.model = siliconflow_model + self.api_url = siliconflow_url + print(f"[模板AI助手] 自动选择硅基流动: {siliconflow_model}") + elif huawei_key and huawei_endpoint: + self.api_key = huawei_key + self.model = huawei_model + self.api_url = huawei_endpoint + print(f"[模板AI助手] 自动选择华为大模型: {huawei_model}") + else: + raise Exception("未配置AI服务,请设置 AI_PROVIDER 环境变量('huawei' 或 'siliconflow'),并配置相应的API密钥") def test_api_connection(self) -> bool: """ @@ -35,9 +68,9 @@ class TemplateAIHelper: 是否连接成功 """ try: - print(f" [测试] 正在测试华为大模型API连接...") + print(f" [测试] 正在测试API连接...") - # 华为大模型payload + # 测试payload test_payload = { "model": self.model, "messages": [ @@ -46,11 +79,14 @@ class TemplateAIHelper: "content": "测试" } ], - "stream": False, "temperature": 0.5, "max_tokens": 10 } + # 如果是华为大模型,添加额外的参数 + if 'huawei' in self.api_url.lower() or '10.100.31.26' in self.api_url: + test_payload["stream"] = False + headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" @@ -146,7 +182,7 @@ class TemplateAIHelper: 只返回JSON,不要其他说明文字。""" - # 调用AI API(华为大模型) + # 调用AI API payload = { "model": self.model, "messages": [ @@ -159,18 +195,21 @@ class TemplateAIHelper: "content": prompt } ], - "stream": False, - "presence_penalty": 1.03, - "frequency_penalty": 1.0, - "repetition_penalty": 1.0, "temperature": 0.5, - "top_p": 0.95, - "top_k": 1, - "seed": 1, - "max_tokens": 8192, - "n": 1 + "max_tokens": 8192 } + # 如果是华为大模型,添加额外的参数 + if 'huawei' in self.api_url.lower() or '10.100.31.26' in self.api_url: + payload["stream"] = False + payload["presence_penalty"] = 1.03 + payload["frequency_penalty"] = 1.0 + payload["repetition_penalty"] = 1.0 + payload["top_p"] = 0.95 + payload["top_k"] = 1 + payload["seed"] = 1 + payload["n"] = 1 + headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"