The entire system has been largely completed.
This commit is contained in:
@@ -191,7 +191,8 @@ class ReportAgent:
|
||||
if self.config.default_llm_provider == "gemini":
|
||||
return GeminiLLM(
|
||||
api_key=self.config.gemini_api_key,
|
||||
model_name=self.config.gemini_model
|
||||
model_name=self.config.gemini_model,
|
||||
config=self.config # 传入配置对象以支持动态超时设置
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"不支持的LLM提供商: {self.config.default_llm_provider}")
|
||||
|
||||
@@ -40,9 +40,10 @@ def initialize_report_engine():
|
||||
class ReportTask:
|
||||
"""报告生成任务"""
|
||||
|
||||
def __init__(self, query: str, task_id: str):
|
||||
def __init__(self, query: str, task_id: str, custom_template: str = ""):
|
||||
self.task_id = task_id
|
||||
self.query = query
|
||||
self.custom_template = custom_template
|
||||
self.status = "pending" # pending, running, completed, error
|
||||
self.progress = 0
|
||||
self.result = None
|
||||
@@ -98,7 +99,7 @@ def check_engines_ready() -> Dict[str, Any]:
|
||||
)
|
||||
|
||||
|
||||
def run_report_generation(task: ReportTask, query: str):
|
||||
def run_report_generation(task: ReportTask, query: str, custom_template: str = ""):
|
||||
"""在后台线程中运行报告生成"""
|
||||
global current_task
|
||||
|
||||
@@ -123,6 +124,7 @@ def run_report_generation(task: ReportTask, query: str):
|
||||
query=query,
|
||||
reports=content['reports'],
|
||||
forum_logs=content['forum_logs'],
|
||||
custom_template=custom_template,
|
||||
save_report=True
|
||||
)
|
||||
|
||||
@@ -183,6 +185,7 @@ def generate_report():
|
||||
# 获取请求参数
|
||||
data = request.get_json() or {}
|
||||
query = data.get('query', '智能舆情分析报告')
|
||||
custom_template = data.get('custom_template', '')
|
||||
|
||||
# 清空日志文件
|
||||
clear_report_log()
|
||||
@@ -205,7 +208,7 @@ def generate_report():
|
||||
|
||||
# 创建新任务
|
||||
task_id = f"report_{int(time.time())}"
|
||||
task = ReportTask(query, task_id)
|
||||
task = ReportTask(query, task_id, custom_template)
|
||||
|
||||
with task_lock:
|
||||
current_task = task
|
||||
@@ -213,7 +216,7 @@ def generate_report():
|
||||
# 在后台线程中运行报告生成
|
||||
thread = threading.Thread(
|
||||
target=run_report_generation,
|
||||
args=(task, query),
|
||||
args=(task, query, custom_template),
|
||||
daemon=True
|
||||
)
|
||||
thread.start()
|
||||
|
||||
@@ -25,7 +25,18 @@ try:
|
||||
utils_dir = os.path.join(root_dir, 'utils')
|
||||
if utils_dir not in sys.path:
|
||||
sys.path.append(utils_dir)
|
||||
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG
|
||||
from retry_helper import with_retry, with_graceful_retry, LLM_RETRY_CONFIG, RetryConfig
|
||||
# 创建动态重试配置生成函数
|
||||
def create_report_retry_config(config=None):
|
||||
"""创建ReportEngine专用的重试配置,适应7分钟平均生成时间"""
|
||||
return RetryConfig(
|
||||
max_retries=config.max_retries if config and hasattr(config, 'max_retries') else 8,
|
||||
initial_delay=8.0, # 初始延迟增加到8秒,适应长时间生成
|
||||
backoff_factor=2.0, # 保持2倍退避
|
||||
max_delay=config.max_retry_delay if config and hasattr(config, 'max_retry_delay') else 180.0
|
||||
)
|
||||
# 创建默认配置用于模块导入时的向后兼容
|
||||
REPORT_LLM_RETRY_CONFIG = create_report_retry_config()
|
||||
except ImportError:
|
||||
# 如果无法导入重试模块,使用空装饰器避免报错
|
||||
def with_retry(config):
|
||||
@@ -33,18 +44,20 @@ except ImportError:
|
||||
return func
|
||||
return decorator
|
||||
LLM_RETRY_CONFIG = None
|
||||
REPORT_LLM_RETRY_CONFIG = None
|
||||
|
||||
|
||||
class GeminiLLM(BaseLLM):
|
||||
"""Report Engine Gemini LLM实现类"""
|
||||
|
||||
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None):
|
||||
def __init__(self, api_key: Optional[str] = None, model_name: Optional[str] = None, config=None):
|
||||
"""
|
||||
初始化Gemini客户端
|
||||
|
||||
Args:
|
||||
api_key: Gemini API密钥,如果不提供则从config或环境变量读取
|
||||
model_name: 模型名称,默认使用gemini-2.5-pro
|
||||
config: 配置对象,用于获取超时设置
|
||||
"""
|
||||
if api_key is None:
|
||||
# 优先从根目录config读取
|
||||
@@ -59,10 +72,21 @@ class GeminiLLM(BaseLLM):
|
||||
|
||||
super().__init__(api_key, model_name)
|
||||
|
||||
# 存储配置对象
|
||||
self.config = config
|
||||
|
||||
# 从配置获取超时时间,默认15分钟(适应7分钟平均生成时间)
|
||||
timeout = config.api_timeout if config and hasattr(config, 'api_timeout') else 900.0
|
||||
|
||||
# 创建针对此实例的重试配置
|
||||
self.retry_config = create_report_retry_config(config)
|
||||
|
||||
# 初始化OpenAI客户端,使用Gemini的中转endpoint
|
||||
# 专门为报告生成设置长超时(15分钟),适应7分钟平均生成时间
|
||||
self.client = OpenAI(
|
||||
api_key=self.api_key,
|
||||
base_url="https://www.chataiapi.com/v1"
|
||||
base_url="https://www.chataiapi.com/v1",
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
self.default_model = model_name or self.get_default_model()
|
||||
@@ -71,10 +95,46 @@ class GeminiLLM(BaseLLM):
|
||||
"""获取默认模型名称"""
|
||||
return "gemini-2.5-pro"
|
||||
|
||||
@with_retry(LLM_RETRY_CONFIG)
|
||||
def _make_api_call(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
|
||||
"""
|
||||
内部API调用方法
|
||||
|
||||
Args:
|
||||
system_prompt: 系统提示词
|
||||
user_prompt: 用户输入
|
||||
**kwargs: 其他参数
|
||||
|
||||
Returns:
|
||||
API响应内容
|
||||
"""
|
||||
# 构建消息
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt}
|
||||
]
|
||||
|
||||
# 设置默认参数
|
||||
params = {
|
||||
"model": self.default_model,
|
||||
"messages": messages,
|
||||
"temperature": kwargs.get("temperature", 0.7),
|
||||
"max_tokens": kwargs.get("max_tokens", 50000),
|
||||
"stream": False
|
||||
}
|
||||
|
||||
# 调用API
|
||||
response = self.client.chat.completions.create(**params)
|
||||
|
||||
# 提取回复内容
|
||||
if response.choices and response.choices[0].message:
|
||||
content = response.choices[0].message.content
|
||||
return self.validate_response(content)
|
||||
else:
|
||||
return ""
|
||||
|
||||
def invoke(self, system_prompt: str, user_prompt: str, **kwargs) -> str:
|
||||
"""
|
||||
调用Gemini API生成回复
|
||||
调用Gemini API生成回复(带动态重试配置)
|
||||
|
||||
Args:
|
||||
system_prompt: 系统提示词
|
||||
@@ -84,35 +144,39 @@ class GeminiLLM(BaseLLM):
|
||||
Returns:
|
||||
Gemini生成的回复文本
|
||||
"""
|
||||
try:
|
||||
# 构建消息
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt}
|
||||
]
|
||||
|
||||
# 设置默认参数
|
||||
params = {
|
||||
"model": self.default_model,
|
||||
"messages": messages,
|
||||
"temperature": kwargs.get("temperature", 0.7),
|
||||
"max_tokens": kwargs.get("max_tokens", 50000), # 增加到50000以支持20000字输出
|
||||
"stream": False
|
||||
}
|
||||
|
||||
# 调用API
|
||||
response = self.client.chat.completions.create(**params)
|
||||
|
||||
# 提取回复内容
|
||||
if response.choices and response.choices[0].message:
|
||||
content = response.choices[0].message.content
|
||||
return self.validate_response(content)
|
||||
else:
|
||||
return ""
|
||||
import time
|
||||
|
||||
last_exception = None
|
||||
|
||||
for attempt in range(self.retry_config.max_retries + 1):
|
||||
try:
|
||||
result = self._make_api_call(system_prompt, user_prompt, **kwargs)
|
||||
if attempt > 0:
|
||||
print(f"Report Engine Gemini API在第 {attempt + 1} 次尝试后成功")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"Report Engine Gemini API调用错误: {str(e)}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
last_exception = e
|
||||
|
||||
if attempt == self.retry_config.max_retries:
|
||||
print(f"Report Engine Gemini API在 {self.retry_config.max_retries + 1} 次尝试后仍然失败")
|
||||
print(f"最终错误: {str(e)}")
|
||||
raise e
|
||||
|
||||
# 计算延迟时间
|
||||
delay = min(
|
||||
self.retry_config.initial_delay * (self.retry_config.backoff_factor ** attempt),
|
||||
self.retry_config.max_delay
|
||||
)
|
||||
|
||||
print(f"Report Engine Gemini API第 {attempt + 1} 次尝试失败: {str(e)}")
|
||||
print(f"将在 {delay:.1f} 秒后进行第 {attempt + 2} 次尝试...")
|
||||
|
||||
time.sleep(delay)
|
||||
|
||||
# 这里不应该到达,但作为安全网
|
||||
if last_exception:
|
||||
raise last_exception
|
||||
|
||||
def get_model_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -55,13 +55,13 @@ class HTMLGenerationNode(StateMutationNode):
|
||||
"selected_template": input_data.get('selected_template', '')
|
||||
}
|
||||
|
||||
# 转换为JSON格式
|
||||
# 转换为JSON格式传递给LLM
|
||||
message = json.dumps(llm_input, ensure_ascii=False, indent=2)
|
||||
|
||||
# 调用LLM生成HTML
|
||||
response = self.llm_client.invoke(SYSTEM_PROMPT_HTML_GENERATION, message)
|
||||
|
||||
# 处理响应
|
||||
# 处理响应(简化版)
|
||||
processed_response = self.process_output(response)
|
||||
|
||||
self.log_info("HTML报告生成完成")
|
||||
@@ -101,72 +101,34 @@ class HTMLGenerationNode(StateMutationNode):
|
||||
output: LLM原始输出
|
||||
|
||||
Returns:
|
||||
清理后的HTML内容
|
||||
HTML内容
|
||||
"""
|
||||
try:
|
||||
self.log_info(f"处理LLM原始输出,长度: {len(output)} 字符")
|
||||
|
||||
html_content = ""
|
||||
html_content = output.strip()
|
||||
|
||||
# 尝试解析JSON响应
|
||||
try:
|
||||
result = json.loads(output)
|
||||
html_content = result.get('html_content', '')
|
||||
self.log_info("成功从JSON中提取html_content")
|
||||
except json.JSONDecodeError:
|
||||
self.log_info("不是JSON格式,直接使用原始输出")
|
||||
# 清理markdown代码块标记(如果存在)
|
||||
if html_content.startswith('```html'):
|
||||
html_content = html_content[7:] # 移除 '```html'
|
||||
if html_content.endswith('```'):
|
||||
html_content = html_content[:-3] # 移除结尾的 '```'
|
||||
elif html_content.startswith('```') and html_content.endswith('```'):
|
||||
html_content = html_content[3:-3] # 移除前后的 '```'
|
||||
|
||||
html_content = html_content.strip()
|
||||
|
||||
# 如果内容为空,返回原始输出
|
||||
if not html_content:
|
||||
self.log_info("处理后内容为空,返回原始输出")
|
||||
html_content = output
|
||||
|
||||
# 如果还是没有内容,尝试其他提取方法
|
||||
if not html_content.strip():
|
||||
# 查找HTML标记
|
||||
if '<!DOCTYPE html>' in output:
|
||||
start_idx = output.find('<!DOCTYPE html>')
|
||||
html_content = output[start_idx:]
|
||||
elif '<html' in output:
|
||||
start_idx = output.find('<html')
|
||||
html_content = output[start_idx:]
|
||||
else:
|
||||
html_content = output
|
||||
|
||||
# 清理markdown代码块标记
|
||||
if html_content.startswith('```html'):
|
||||
html_content = html_content.replace('```html', '').replace('```', '').strip()
|
||||
elif html_content.startswith('```'):
|
||||
html_content = html_content.replace('```', '').strip()
|
||||
|
||||
# 处理转义字符
|
||||
html_content = html_content.replace('\\n', '\n')
|
||||
html_content = html_content.replace('\\t', '\t')
|
||||
html_content = html_content.replace('\\r', '\r')
|
||||
html_content = html_content.replace('\\"', '"')
|
||||
html_content = html_content.replace("\\'", "'")
|
||||
|
||||
# 验证HTML内容
|
||||
if not html_content.strip():
|
||||
raise ValueError("生成的HTML内容为空")
|
||||
|
||||
# 确保HTML有基本结构
|
||||
if not html_content.strip().startswith('<!DOCTYPE') and not html_content.strip().startswith('<html'):
|
||||
self.log_info("HTML缺少基本结构,添加包装")
|
||||
html_content = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>智能舆情分析报告</title>
|
||||
</head>
|
||||
<body>
|
||||
{html_content}
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
self.log_info(f"HTML处理完成,最终长度: {len(html_content)} 字符")
|
||||
return html_content.strip()
|
||||
return html_content
|
||||
|
||||
except Exception as e:
|
||||
self.log_error(f"处理HTML输出失败: {str(e)}")
|
||||
return self._generate_error_html(str(e))
|
||||
self.log_error(f"处理HTML输出失败: {str(e)},返回原始输出")
|
||||
return output
|
||||
|
||||
def _generate_fallback_html(self, input_data: Dict[str, Any]) -> str:
|
||||
"""
|
||||
@@ -288,53 +250,4 @@ class HTMLGenerationNode(StateMutationNode):
|
||||
|
||||
return html_content
|
||||
|
||||
def _generate_error_html(self, error_message: str) -> str:
|
||||
"""
|
||||
生成错误HTML页面
|
||||
|
||||
Args:
|
||||
error_message: 错误信息
|
||||
|
||||
Returns:
|
||||
错误HTML内容
|
||||
"""
|
||||
return f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>报告生成失败</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
text-align: center;
|
||||
padding: 50px;
|
||||
background: #f8f9fa;
|
||||
}}
|
||||
.error-container {{
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}}
|
||||
.error-title {{
|
||||
color: #e74c3c;
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.error-message {{
|
||||
color: #666;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="error-title">报告生成失败</div>
|
||||
<div class="error-message">错误信息: {error_message}</div>
|
||||
<p>请检查输入数据或稍后重试。</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
||||
@@ -51,13 +51,7 @@ class TemplateSelectionNode(BaseNode):
|
||||
self.log_info("未找到预设模板,使用内置默认模板")
|
||||
return self._get_fallback_template()
|
||||
|
||||
# 首先尝试简单关键词匹配
|
||||
simple_match = self._simple_keyword_matching(query, available_templates)
|
||||
if simple_match:
|
||||
self.log_info(f"通过关键词匹配选择模板: {simple_match['template_name']}")
|
||||
return simple_match
|
||||
|
||||
# 如果关键词匹配失败,尝试LLM选择
|
||||
# 使用LLM进行模板选择
|
||||
try:
|
||||
llm_result = self._llm_template_selection(query, reports, forum_logs, available_templates)
|
||||
if llm_result:
|
||||
@@ -65,54 +59,10 @@ class TemplateSelectionNode(BaseNode):
|
||||
except Exception as e:
|
||||
self.log_error(f"LLM模板选择失败: {str(e)}")
|
||||
|
||||
# 所有方法都失败,使用默认的社会热点事件模板
|
||||
default_template = self._get_default_social_event_template(available_templates)
|
||||
if default_template:
|
||||
return default_template
|
||||
|
||||
# 最后备选方案
|
||||
# 如果LLM选择失败,使用备选方案
|
||||
return self._get_fallback_template()
|
||||
|
||||
def _simple_keyword_matching(self, query: str, available_templates: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""基于关键词的简单模板匹配"""
|
||||
query_lower = query.lower()
|
||||
|
||||
# 关键词映射
|
||||
keyword_mapping = {
|
||||
'企业': ['企业品牌'],
|
||||
'品牌': ['企业品牌'],
|
||||
'声誉': ['企业品牌'],
|
||||
'市场': ['市场竞争'],
|
||||
'竞争': ['市场竞争'],
|
||||
'格局': ['市场竞争'],
|
||||
'政策': ['政策', '行业'],
|
||||
'行业': ['政策', '行业'],
|
||||
'动态': ['政策', '行业'],
|
||||
'突发': ['突发事件', '危机'],
|
||||
'危机': ['突发事件', '危机'],
|
||||
'公关': ['突发事件', '危机'],
|
||||
'日常': ['日常', '定期'],
|
||||
'定期': ['日常', '定期'],
|
||||
'监测': ['日常', '定期'],
|
||||
'热点': ['社会公共热点'],
|
||||
'社会': ['社会公共热点'],
|
||||
'事件': ['社会公共热点'],
|
||||
}
|
||||
|
||||
# 检查查询中的关键词
|
||||
for keyword, template_keywords in keyword_mapping.items():
|
||||
if keyword in query_lower:
|
||||
# 查找匹配的模板
|
||||
for template in available_templates:
|
||||
for template_keyword in template_keywords:
|
||||
if template_keyword in template['name']:
|
||||
return {
|
||||
'template_name': template['name'],
|
||||
'template_content': template['content'],
|
||||
'selection_reason': f'基于关键词"{keyword}"匹配选择'
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _llm_template_selection(self, query: str, reports: List[Any], forum_logs: str,
|
||||
available_templates: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
@@ -122,15 +72,46 @@ class TemplateSelectionNode(BaseNode):
|
||||
# 构建模板列表
|
||||
template_list = "\n".join([f"- {t['name']}: {t['description']}" for t in available_templates])
|
||||
|
||||
# 构建报告内容摘要
|
||||
reports_summary = ""
|
||||
if reports:
|
||||
reports_summary = "\n\n=== 分析引擎报告内容 ===\n"
|
||||
for i, report in enumerate(reports, 1):
|
||||
# 获取报告内容,支持不同的数据格式
|
||||
if isinstance(report, dict):
|
||||
content = report.get('content', str(report))
|
||||
elif hasattr(report, 'content'):
|
||||
content = report.content
|
||||
else:
|
||||
content = str(report)
|
||||
|
||||
# 截断过长的内容,保留前1000个字符
|
||||
if len(content) > 1000:
|
||||
content = content[:1000] + "...(内容已截断)"
|
||||
|
||||
reports_summary += f"\n报告{i}内容:\n{content}\n"
|
||||
|
||||
# 构建论坛日志摘要
|
||||
forum_summary = ""
|
||||
if forum_logs and forum_logs.strip():
|
||||
forum_summary = "\n\n=== 三个引擎的讨论内容 ===\n"
|
||||
# 截断过长的日志内容,保留前800个字符
|
||||
if len(forum_logs) > 800:
|
||||
forum_content = forum_logs[:800] + "...(讨论内容已截断)"
|
||||
else:
|
||||
forum_content = forum_logs
|
||||
forum_summary += forum_content
|
||||
|
||||
user_message = f"""查询内容: {query}
|
||||
|
||||
报告数量: {len(reports)} 个分析引擎报告
|
||||
论坛日志: {'有' if forum_logs else '无'}
|
||||
{reports_summary}{forum_summary}
|
||||
|
||||
可用模板:
|
||||
{template_list}
|
||||
|
||||
请选择最合适的模板。"""
|
||||
请根据查询内容、报告内容和论坛日志的具体情况,选择最合适的模板。"""
|
||||
|
||||
# 调用LLM
|
||||
response = self.llm_client.invoke(SYSTEM_PROMPT_TEMPLATE_SELECTION, user_message)
|
||||
@@ -140,7 +121,7 @@ class TemplateSelectionNode(BaseNode):
|
||||
self.log_error("LLM返回空响应")
|
||||
return None
|
||||
|
||||
self.log_info(f"LLM原始响应: {response[:200]}...")
|
||||
self.log_info(f"LLM原始响应: {response}")
|
||||
|
||||
# 尝试解析JSON响应
|
||||
try:
|
||||
@@ -250,18 +231,7 @@ class TemplateSelectionNode(BaseNode):
|
||||
|
||||
return "通用报告模板"
|
||||
|
||||
def _get_default_social_event_template(self, available_templates: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""获取默认的社会热点事件分析模板"""
|
||||
# 查找社会热点事件分析模板
|
||||
for template in available_templates:
|
||||
if '社会公共热点事件' in template['name'] or '热点' in template['name']:
|
||||
self.log_info(f"使用默认模板: {template['name']}")
|
||||
return {
|
||||
'template_name': template['name'],
|
||||
'template_content': template['content'],
|
||||
'selection_reason': '默认使用社会热点事件分析模板'
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
def _get_fallback_template(self) -> Dict[str, Any]:
|
||||
"""获取备用默认模板(空模板,让LLM自行发挥)"""
|
||||
|
||||
@@ -7,7 +7,6 @@ from .prompts import (
|
||||
SYSTEM_PROMPT_TEMPLATE_SELECTION,
|
||||
SYSTEM_PROMPT_HTML_GENERATION,
|
||||
output_schema_template_selection,
|
||||
output_schema_html_generation,
|
||||
input_schema_html_generation
|
||||
)
|
||||
|
||||
@@ -15,6 +14,5 @@ __all__ = [
|
||||
"SYSTEM_PROMPT_TEMPLATE_SELECTION",
|
||||
"SYSTEM_PROMPT_HTML_GENERATION",
|
||||
"output_schema_template_selection",
|
||||
"output_schema_html_generation",
|
||||
"input_schema_html_generation"
|
||||
]
|
||||
|
||||
@@ -30,14 +30,14 @@ input_schema_html_generation = {
|
||||
}
|
||||
}
|
||||
|
||||
# HTML报告生成输出Schema
|
||||
output_schema_html_generation = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"html_content": {"type": "string"}
|
||||
},
|
||||
"required": ["html_content"]
|
||||
}
|
||||
# HTML报告生成输出Schema - 已简化,不再使用JSON格式
|
||||
# output_schema_html_generation = {
|
||||
# "type": "object",
|
||||
# "properties": {
|
||||
# "html_content": {"type": "string"}
|
||||
# },
|
||||
# "required": ["html_content"]
|
||||
# }
|
||||
|
||||
# ===== 系统提示词定义 =====
|
||||
|
||||
@@ -52,12 +52,12 @@ SYSTEM_PROMPT_TEMPLATE_SELECTION = f"""
|
||||
4. 目标受众和使用场景
|
||||
|
||||
可用模板类型:
|
||||
- 企业品牌声誉分析报告模板:适用于品牌形象、声誉管理分析
|
||||
- 市场竞争格局舆情分析报告模板:适用于竞争对手、市场份额分析
|
||||
- 日常或定期舆情监测报告模板:适用于常规监控、定期汇报
|
||||
- 特定政策或行业动态舆情分析报告:适用于政策影响、行业变化分析
|
||||
- 社会公共热点事件分析报告模板:适用于热点事件、社会话题分析
|
||||
- 突发事件与危机公关舆情报告模板:适用于危机管理、应急响应
|
||||
- 企业品牌声誉分析报告模板:适用于品牌形象、声誉管理分析当需要对品牌在特定周期内(如年度、半年度)的整体网络形象、资产健康度进行全面、深度的评估与复盘时,应选择此模板。核心任务是战略性、全局性分析。
|
||||
- 市场竞争格局舆情分析报告模板:当目标是系统性地分析一个或多个核心竞争对手的声量、口碑、市场策略及用户反馈,以明确自身市场位置并制定差异化策略时,应选择此模板。核心任务是对比与洞察。
|
||||
- 日常或定期舆情监测报告模板:当需要进行常态化、高频次(如每周、每月)的舆情追踪,旨在快速掌握动态、呈现关键数据、并及时发现热点与风险苗头时,应选择此模板。核心任务是数据呈现与动态追踪。
|
||||
- 特定政策或行业动态舆情分析报告:当监测到重要政策发布、法规变动或足以影响整个行业的宏观动态时,应选择此模板。核心任务是深度解读、预判趋势及对本机构的潜在影响。
|
||||
- 社会公共热点事件分析报告模板(最推荐):当社会上出现与本机构无直接关联,但已形成广泛讨论的公共热点、文化现象或网络流行趋势时,应选择此模板。核心任务是洞察社会心态,并评估事件与本机构的关联性(风险与机遇)。
|
||||
- 突发事件与危机公关舆情报告模板:当监测到与本机构直接相关的、具有潜在危害的突发负面事件时,应选择此模板。核心任务是快速响应、评估风险、控制事态。
|
||||
|
||||
请按照以下JSON模式定义格式化输出:
|
||||
|
||||
@@ -71,7 +71,7 @@ SYSTEM_PROMPT_TEMPLATE_SELECTION = f"""
|
||||
|
||||
# HTML报告生成的系统提示词
|
||||
SYSTEM_PROMPT_HTML_GENERATION = f"""
|
||||
你是一位专业的HTML报告生成专家。你将接收来自三个分析引擎的报告内容、论坛监控日志以及选定的报告模板,需要生成一份完整的HTML格式分析报告。
|
||||
你是一位专业的HTML报告生成专家。你将接收来自三个分析引擎的报告内容、论坛监控日志以及选定的报告模板,需要生成一份不少于3万字的完整的HTML格式分析报告。
|
||||
|
||||
<INPUT JSON SCHEMA>
|
||||
{json.dumps(input_schema_html_generation, indent=2, ensure_ascii=False)}
|
||||
@@ -79,9 +79,9 @@ SYSTEM_PROMPT_HTML_GENERATION = f"""
|
||||
|
||||
**你的任务:**
|
||||
1. 整合三个引擎的分析结果,避免重复内容
|
||||
2. 结合论坛日志数据,提供用户行为洞察
|
||||
2. 结合三个引擎在分析时的相互讨论数据(forum_logs),站在不同角度分析内容
|
||||
3. 按照选定模板的结构组织内容
|
||||
4. 生成包含数据可视化的完整HTML报告
|
||||
4. 生成包含数据可视化的完整HTML报告,不少于3万字
|
||||
|
||||
**HTML报告要求:**
|
||||
|
||||
@@ -89,12 +89,14 @@ SYSTEM_PROMPT_HTML_GENERATION = f"""
|
||||
- 包含DOCTYPE、html、head、body标签
|
||||
- 响应式CSS样式
|
||||
- JavaScript交互功能
|
||||
- 如果有目录,不要使用侧边栏设计,而是放在文章的开始部分
|
||||
|
||||
2. **美观的设计**:
|
||||
- 现代化的UI设计
|
||||
- 合理的色彩搭配
|
||||
- 清晰的排版布局
|
||||
- 适配移动设备
|
||||
- 不要采用需要展开内容的前端效果,一次性完整显示
|
||||
|
||||
3. **数据可视化**:
|
||||
- 使用Chart.js生成图表
|
||||
@@ -129,12 +131,5 @@ SYSTEM_PROMPT_HTML_GENERATION = f"""
|
||||
- 导出功能
|
||||
- 主题切换
|
||||
|
||||
请按照以下JSON模式定义格式化输出:
|
||||
|
||||
<OUTPUT JSON SCHEMA>
|
||||
{json.dumps(output_schema_html_generation, indent=2, ensure_ascii=False)}
|
||||
</OUTPUT JSON SCHEMA>
|
||||
|
||||
确保生成的HTML是完整可用的,包含所有必要的样式和脚本。
|
||||
只返回JSON对象,不要有解释或额外文本。
|
||||
**重要:直接返回完整的HTML代码,不要包含任何解释、说明或其他文本。只返回HTML代码本身。**
|
||||
"""
|
||||
|
||||
@@ -19,10 +19,15 @@ class Config:
|
||||
gemini_model: str = "gemini-2.5-pro"
|
||||
|
||||
# 报告配置
|
||||
max_content_length: int = 500000 # 增加到500000字符以支持30000字输入和20000字输出
|
||||
max_content_length: int = 200000
|
||||
output_dir: str = "final_reports"
|
||||
template_dir: str = "ReportEngine/report_template"
|
||||
|
||||
# 超时配置 - 专门为长报告生成优化(平均生成时间7分钟)
|
||||
api_timeout: float = 900.0 # API调用超时时间(秒),设置为15分钟,适应7分钟平均生成时间
|
||||
max_retry_delay: float = 180.0 # 最大重试延迟(秒),设置为3分钟
|
||||
max_retries: int = 8 # 最大重试次数,增加到8次
|
||||
|
||||
# 日志配置
|
||||
log_file: str = "logs/report.log"
|
||||
|
||||
@@ -53,9 +58,12 @@ class Config:
|
||||
gemini_api_key=getattr(config_module, "GEMINI_API_KEY", None),
|
||||
default_llm_provider=getattr(config_module, "DEFAULT_LLM_PROVIDER", "gemini"),
|
||||
gemini_model=getattr(config_module, "GEMINI_MODEL", "gemini-2.5-pro"),
|
||||
max_content_length=getattr(config_module, "MAX_CONTENT_LENGTH", 500000),
|
||||
max_content_length=getattr(config_module, "MAX_CONTENT_LENGTH", 200000),
|
||||
output_dir=getattr(config_module, "REPORT_OUTPUT_DIR", "final_reports"),
|
||||
template_dir=getattr(config_module, "TEMPLATE_DIR", "ReportEngine/report_template"),
|
||||
api_timeout=getattr(config_module, "REPORT_API_TIMEOUT", 900.0),
|
||||
max_retry_delay=getattr(config_module, "REPORT_MAX_RETRY_DELAY", 180.0),
|
||||
max_retries=getattr(config_module, "REPORT_MAX_RETRIES", 8),
|
||||
log_file=getattr(config_module, "REPORT_LOG_FILE", "logs/report.log"),
|
||||
enable_pdf_export=getattr(config_module, "ENABLE_PDF_EXPORT", True),
|
||||
chart_style=getattr(config_module, "CHART_STYLE", "modern")
|
||||
@@ -76,9 +84,12 @@ class Config:
|
||||
gemini_api_key=config_dict.get("GEMINI_API_KEY"),
|
||||
default_llm_provider=config_dict.get("DEFAULT_LLM_PROVIDER", "gemini"),
|
||||
gemini_model=config_dict.get("GEMINI_MODEL", "gemini-2.5-pro"),
|
||||
max_content_length=int(config_dict.get("MAX_CONTENT_LENGTH", "500000")),
|
||||
max_content_length=int(config_dict.get("MAX_CONTENT_LENGTH", "200000")),
|
||||
output_dir=config_dict.get("REPORT_OUTPUT_DIR", "final_reports"),
|
||||
template_dir=config_dict.get("TEMPLATE_DIR", "ReportEngine/report_template"),
|
||||
api_timeout=float(config_dict.get("REPORT_API_TIMEOUT", "900.0")),
|
||||
max_retry_delay=float(config_dict.get("REPORT_MAX_RETRY_DELAY", "180.0")),
|
||||
max_retries=int(config_dict.get("REPORT_MAX_RETRIES", "8")),
|
||||
log_file=config_dict.get("REPORT_LOG_FILE", "logs/report.log"),
|
||||
enable_pdf_export=config_dict.get("ENABLE_PDF_EXPORT", "true").lower() == "true",
|
||||
chart_style=config_dict.get("CHART_STYLE", "modern")
|
||||
@@ -127,6 +138,9 @@ def print_config(config: Config):
|
||||
print(f"最大内容长度: {config.max_content_length}")
|
||||
print(f"输出目录: {config.output_dir}")
|
||||
print(f"模板目录: {config.template_dir}")
|
||||
print(f"API超时时间: {config.api_timeout}秒({config.api_timeout/60:.1f}分钟)")
|
||||
print(f"最大重试延迟: {config.max_retry_delay}秒({config.max_retry_delay/60:.1f}分钟)")
|
||||
print(f"最大重试次数: {config.max_retries}次")
|
||||
print(f"日志文件: {config.log_file}")
|
||||
print(f"PDF导出: {config.enable_pdf_export}")
|
||||
print(f"图表样式: {config.chart_style}")
|
||||
|
||||
Reference in New Issue
Block a user