MindMap/backend/mindmap/ai_service.py

205 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import re
import traceback
import json
def call_ai_api(system_prompt, user_prompt, model="glm-4.5", base_url="https://open.bigmodel.cn/api/paas/v4/", api_key="ce39bdd4fcf34ec0aec75072bc9ff988.hAp7HZTVUwy7vImn", stream=False):
"""
调用AI API生成Markdown
"""
try:
# 尝试导入OpenAI客户端
try:
from openai import OpenAI
except ImportError:
print("OpenAI库未安装返回模拟数据")
if stream:
# 返回模拟流式数据
def mock_stream():
mock_content = f"""# {user_prompt}
## 概述
{user_prompt}是一个重要的概念和领域。
## 核心要素
- 要素1
- 要素2
- 要素3
## 应用场景
- 场景1
- 场景2
- 场景3
## 发展趋势
- 趋势1
- 趋势2
- 趋势3"""
for char in mock_content:
yield char
return mock_stream()
else:
return f"""# {user_prompt}
## 概述
{user_prompt}是一个重要的概念和领域。
## 核心要素
- 要素1
- 要素2
- 要素3
## 应用场景
- 场景1
- 场景2
- 场景3
## 发展趋势
- 趋势1
- 趋势2
- 趋势3"""
# 构建消息
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
print(f"发送AI API请求到: {base_url}")
print(f"模型: {model}")
print(f"流式模式: {stream}")
# 创建OpenAI客户端
client = OpenAI(api_key=api_key, base_url=base_url)
# 根据stream参数决定是否使用流式调用
try:
response = client.chat.completions.create(
model=model,
messages=messages,
temperature=0.7,
max_tokens=4000, # 减少token限制提高响应速度
stream=stream
)
except Exception as e:
print(f"API调用失败: {e}")
# 如果API调用失败抛出异常而不是返回模拟数据
raise Exception(f"AI API调用失败: {e}")
if stream:
# 流式响应处理
def generate_stream():
full_content = ""
try:
for chunk in response:
if chunk.choices[0].delta.content is not None:
content_chunk = chunk.choices[0].delta.content
full_content += content_chunk
yield content_chunk
print(f"流式响应完成,总长度: {len(full_content)}")
except Exception as e:
print(f"流式响应处理失败: {e}")
raise e
return full_content
return generate_stream()
else:
# 非流式响应处理
content = response.choices[0].message.content
print(f"AI原始响应: {content}")
# 处理可能的JSON格式响应
try:
# 尝试解析为JSON
json_data = json.loads(content)
if 'answer' in json_data:
content = json_data['answer']
print(f"从JSON中提取answer: {content[:100]}...")
elif 'content' in json_data:
content = json_data['content']
print(f"从JSON中提取content: {content[:100]}...")
elif 'markdown' in json_data:
content = json_data['markdown']
print(f"从JSON中提取markdown: {content[:100]}...")
except json.JSONDecodeError:
# 不是JSON格式直接使用内容
print("响应不是JSON格式直接使用内容")
# 清理内容
content = content.strip()
# 如果返回的内容包含代码块标记,提取其中的内容
markdown_match = re.search(r"```(?:markdown)?\n(.*?)```", content, re.DOTALL)
if markdown_match:
content = markdown_match.group(1).strip()
# 如果内容为空,返回模拟数据
if not content:
print("AI返回内容为空使用模拟数据")
return f"""# {user_prompt}
## 概述
{user_prompt}是一个重要的概念和领域。
## 核心要素
- 要素1
- 要素2
- 要素3
## 应用场景
- 场景1
- 场景2
- 场景3
## 发展趋势
- 趋势1
- 趋势2
- 趋势3"""
return content
except Exception as e:
print(f"AI API调用异常: {e}")
traceback.print_exc()
return None
def generate_markdown_from_text(text):
"""
从文本生成Markdown的便捷函数
"""
system_prompt = """你是一位Markdown格式转换专家。你的任务是将用户提供的文章内容精确转换为结构化的Markdown格式。请遵循以下步骤
提取主标题: 识别文章最顶层的主标题通常为文章题目或书名并使用Markdown的 # 级别表示。
识别层级标题: 从文章内容中提取所有层级的内容标题从主标题后的第一个标题开始Level 1 至 Level 4。判断层级依据
视觉与结构特征: 如独立成行/段、位置(行首)、格式(加粗、编号如 1., 1.1, (1), - 等)。
语义逻辑: 标题之间的包含和并列关系。
在Markdown中使用相应标题级别
Level 1 标题用 ##
Level 2 标题用 ###
Level 3 标题用 ####
Level 4 标题用 #####
精确保留原文标题文字,不得修改、概括或润色。
处理正文内容: 对于每个标题下的正文内容区块(从该标题后开始,直到下一个同级或更高级别标题前):
直接保留原文文本但根据内容结构适当格式化为Markdown。
如果内容是列表如项目符号或编号列表使用Markdown列表语法例如 - 用于无序列表1. 用于有序列表)。
保持段落和换行不变。
输出格式: 输出必须是纯Markdown格式的文本不得包含任何额外说明、JSON或非Markdown元素。确保输出与示例风格一致。"""
user_prompt = f"请将以下内容转换为结构化的Markdown格式\n\n{text}"
return call_ai_api(system_prompt, user_prompt)