Merge pull request #428 from Ghostubborn/feat/i18n
feat(i18n): 添加多语言切换功能,支持中英文
This commit is contained in:
@@ -17,6 +17,7 @@ from ..config import Config
|
||||
from ..models.task import TaskManager, TaskStatus
|
||||
from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges
|
||||
from .text_processor import TextProcessor
|
||||
from ..utils.locale import t, get_locale, set_locale
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -83,10 +84,13 @@ class GraphBuilderService:
|
||||
}
|
||||
)
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 在后台线程中执行构建
|
||||
thread = threading.Thread(
|
||||
target=self._build_graph_worker,
|
||||
args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size)
|
||||
args=(task_id, text, ontology, graph_name, chunk_size, chunk_overlap, batch_size, current_locale)
|
||||
)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
@@ -101,15 +105,17 @@ class GraphBuilderService:
|
||||
graph_name: str,
|
||||
chunk_size: int,
|
||||
chunk_overlap: int,
|
||||
batch_size: int
|
||||
batch_size: int,
|
||||
locale: str = 'zh'
|
||||
):
|
||||
"""图谱构建工作线程"""
|
||||
set_locale(locale)
|
||||
try:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
status=TaskStatus.PROCESSING,
|
||||
progress=5,
|
||||
message="开始构建图谱..."
|
||||
message=t('progress.startBuildingGraph')
|
||||
)
|
||||
|
||||
# 1. 创建图谱
|
||||
@@ -117,7 +123,7 @@ class GraphBuilderService:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=10,
|
||||
message=f"图谱已创建: {graph_id}"
|
||||
message=t('progress.graphCreated', graphId=graph_id)
|
||||
)
|
||||
|
||||
# 2. 设置本体
|
||||
@@ -125,7 +131,7 @@ class GraphBuilderService:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=15,
|
||||
message="本体已设置"
|
||||
message=t('progress.ontologySet')
|
||||
)
|
||||
|
||||
# 3. 文本分块
|
||||
@@ -134,7 +140,7 @@ class GraphBuilderService:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=20,
|
||||
message=f"文本已分割为 {total_chunks} 个块"
|
||||
message=t('progress.textSplit', count=total_chunks)
|
||||
)
|
||||
|
||||
# 4. 分批发送数据
|
||||
@@ -151,7 +157,7 @@ class GraphBuilderService:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=60,
|
||||
message="等待Zep处理数据..."
|
||||
message=t('progress.waitingZepProcess')
|
||||
)
|
||||
|
||||
self._wait_for_episodes(
|
||||
@@ -167,7 +173,7 @@ class GraphBuilderService:
|
||||
self.task_manager.update_task(
|
||||
task_id,
|
||||
progress=90,
|
||||
message="获取图谱信息..."
|
||||
message=t('progress.fetchingGraphInfo')
|
||||
)
|
||||
|
||||
graph_info = self._get_graph_info(graph_id)
|
||||
@@ -304,7 +310,7 @@ class GraphBuilderService:
|
||||
if progress_callback:
|
||||
progress = (i + len(batch_chunks)) / total_chunks
|
||||
progress_callback(
|
||||
f"发送第 {batch_num}/{total_batches} 批数据 ({len(batch_chunks)} 块)...",
|
||||
t('progress.sendingBatch', current=batch_num, total=total_batches, chunks=len(batch_chunks)),
|
||||
progress
|
||||
)
|
||||
|
||||
@@ -333,7 +339,7 @@ class GraphBuilderService:
|
||||
|
||||
except Exception as e:
|
||||
if progress_callback:
|
||||
progress_callback(f"批次 {batch_num} 发送失败: {str(e)}", 0)
|
||||
progress_callback(t('progress.batchFailed', batch=batch_num, error=str(e)), 0)
|
||||
raise
|
||||
|
||||
return episode_uuids
|
||||
@@ -347,7 +353,7 @@ class GraphBuilderService:
|
||||
"""等待所有 episode 处理完成(通过查询每个 episode 的 processed 状态)"""
|
||||
if not episode_uuids:
|
||||
if progress_callback:
|
||||
progress_callback("无需等待(没有 episode)", 1.0)
|
||||
progress_callback(t('progress.noEpisodesWait'), 1.0)
|
||||
return
|
||||
|
||||
start_time = time.time()
|
||||
@@ -356,13 +362,13 @@ class GraphBuilderService:
|
||||
total_episodes = len(episode_uuids)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"开始等待 {total_episodes} 个文本块处理...", 0)
|
||||
progress_callback(t('progress.waitingEpisodes', count=total_episodes), 0)
|
||||
|
||||
while pending_episodes:
|
||||
if time.time() - start_time > timeout:
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
f"部分文本块超时,已完成 {completed_count}/{total_episodes}",
|
||||
t('progress.episodesTimeout', completed=completed_count, total=total_episodes),
|
||||
completed_count / total_episodes
|
||||
)
|
||||
break
|
||||
@@ -384,7 +390,7 @@ class GraphBuilderService:
|
||||
elapsed = int(time.time() - start_time)
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
f"Zep处理中... {completed_count}/{total_episodes} 完成, {len(pending_episodes)} 待处理 ({elapsed}秒)",
|
||||
t('progress.zepProcessing', completed=completed_count, total=total_episodes, pending=len(pending_episodes), elapsed=elapsed),
|
||||
completed_count / total_episodes if total_episodes > 0 else 0
|
||||
)
|
||||
|
||||
@@ -392,7 +398,7 @@ class GraphBuilderService:
|
||||
time.sleep(3) # 每3秒检查一次
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(f"处理完成: {completed_count}/{total_episodes}", 1.0)
|
||||
progress_callback(t('progress.processingComplete', completed=completed_count, total=total_episodes), 1.0)
|
||||
|
||||
def _get_graph_info(self, graph_id: str) -> GraphInfo:
|
||||
"""获取图谱信息"""
|
||||
|
||||
@@ -20,6 +20,7 @@ from zep_cloud.client import Zep
|
||||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, get_locale, set_locale, t
|
||||
from .zep_entity_reader import EntityNode, ZepEntityReader
|
||||
|
||||
logger = get_logger('mirofish.oasis_profile')
|
||||
@@ -313,7 +314,7 @@ class OasisProfileGenerator:
|
||||
logger.debug(f"跳过Zep检索:未设置graph_id")
|
||||
return results
|
||||
|
||||
comprehensive_query = f"关于{entity_name}的所有信息、活动、事件、关系和背景"
|
||||
comprehensive_query = t('progress.zepSearchQuery', name=entity_name)
|
||||
|
||||
def search_edges():
|
||||
"""搜索边(事实/关系)- 带重试机制"""
|
||||
@@ -670,8 +671,8 @@ class OasisProfileGenerator:
|
||||
|
||||
def _get_system_prompt(self, is_individual: bool) -> str:
|
||||
"""获取系统提示词"""
|
||||
base_prompt = "你是社交媒体用户画像生成专家。生成详细、真实的人设用于舆论模拟,最大程度还原已有现实情况。必须返回有效的JSON格式,所有字符串值不能包含未转义的换行符。使用中文。"
|
||||
return base_prompt
|
||||
base_prompt = "你是社交媒体用户画像生成专家。生成详细、真实的人设用于舆论模拟,最大程度还原已有现实情况。必须返回有效的JSON格式,所有字符串值不能包含未转义的换行符。"
|
||||
return f"{base_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
def _build_individual_persona_prompt(
|
||||
self,
|
||||
@@ -717,7 +718,7 @@ class OasisProfileGenerator:
|
||||
重要:
|
||||
- 所有字段值必须是字符串或数字,不要使用换行符
|
||||
- persona必须是一段连贯的文字描述
|
||||
- 使用中文(除了gender字段必须用英文male/female)
|
||||
- {get_language_instruction()} (gender字段必须用英文male/female)
|
||||
- 内容要与实体信息保持一致
|
||||
- age必须是有效的整数,gender必须是"male"或"female"
|
||||
"""
|
||||
@@ -766,7 +767,7 @@ class OasisProfileGenerator:
|
||||
重要:
|
||||
- 所有字段值必须是字符串或数字,不允许null值
|
||||
- persona必须是一段连贯的文字描述,不要使用换行符
|
||||
- 使用中文(除了gender字段必须用英文"other")
|
||||
- {get_language_instruction()} (gender字段必须用英文"other")
|
||||
- age必须是整数30,gender必须是字符串"other"
|
||||
- 机构账号发言要符合其身份定位"""
|
||||
|
||||
@@ -915,8 +916,12 @@ class OasisProfileGenerator:
|
||||
except Exception as e:
|
||||
logger.warning(f"实时保存 profiles 失败: {e}")
|
||||
|
||||
# Capture locale before spawning thread pool workers
|
||||
current_locale = get_locale()
|
||||
|
||||
def generate_single_profile(idx: int, entity: EntityNode) -> tuple:
|
||||
"""生成单个profile的工作函数"""
|
||||
set_locale(current_locale)
|
||||
entity_type = entity.get_entity_type() or "Entity"
|
||||
|
||||
try:
|
||||
@@ -1017,7 +1022,7 @@ class OasisProfileGenerator:
|
||||
|
||||
output_lines = [
|
||||
f"\n{separator}",
|
||||
f"[已生成] {entity_name} ({entity_type})",
|
||||
t('progress.profileGenerated', name=entity_name, type=entity_type),
|
||||
f"{separator}",
|
||||
f"用户名: {profile.user_name}",
|
||||
f"",
|
||||
|
||||
@@ -8,6 +8,7 @@ import logging
|
||||
import re
|
||||
from typing import Dict, Any, List, Optional
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.locale import get_language_instruction
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -83,7 +84,7 @@ ONTOLOGY_SYSTEM_PROMPT = """你是一个专业的知识图谱本体设计专家
|
||||
"attributes": []
|
||||
}
|
||||
],
|
||||
"analysis_summary": "对文本内容的简要分析说明(中文)"
|
||||
"analysis_summary": "对文本内容的简要分析说明"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -205,8 +206,10 @@ class OntologyGenerator:
|
||||
additional_context
|
||||
)
|
||||
|
||||
lang_instruction = get_language_instruction()
|
||||
system_prompt = f"{ONTOLOGY_SYSTEM_PROMPT}\n\n{lang_instruction}\nIMPORTANT: Entity type names MUST be in English PascalCase (e.g., 'PersonEntity', 'MediaOrganization'). Relationship type names MUST be in English UPPER_SNAKE_CASE (e.g., 'WORKS_FOR'). Attribute names MUST be in English snake_case. Only description fields and analysis_summary should use the specified language above."
|
||||
messages = [
|
||||
{"role": "system", "content": ONTOLOGY_SYSTEM_PROMPT},
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_message}
|
||||
]
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from enum import Enum
|
||||
from ..config import Config
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, t
|
||||
from .zep_tools import (
|
||||
ZepToolsService,
|
||||
SearchResult,
|
||||
@@ -105,7 +106,7 @@ class ReportLogger:
|
||||
"simulation_id": simulation_id,
|
||||
"graph_id": graph_id,
|
||||
"simulation_requirement": simulation_requirement,
|
||||
"message": "报告生成任务开始"
|
||||
"message": t('report.taskStarted')
|
||||
}
|
||||
)
|
||||
|
||||
@@ -114,7 +115,7 @@ class ReportLogger:
|
||||
self.log(
|
||||
action="planning_start",
|
||||
stage="planning",
|
||||
details={"message": "开始规划报告大纲"}
|
||||
details={"message": t('report.planningStart')}
|
||||
)
|
||||
|
||||
def log_planning_context(self, context: Dict[str, Any]):
|
||||
@@ -123,7 +124,7 @@ class ReportLogger:
|
||||
action="planning_context",
|
||||
stage="planning",
|
||||
details={
|
||||
"message": "获取模拟上下文信息",
|
||||
"message": t('report.fetchSimContext'),
|
||||
"context": context
|
||||
}
|
||||
)
|
||||
@@ -134,7 +135,7 @@ class ReportLogger:
|
||||
action="planning_complete",
|
||||
stage="planning",
|
||||
details={
|
||||
"message": "大纲规划完成",
|
||||
"message": t('report.planningComplete'),
|
||||
"outline": outline_dict
|
||||
}
|
||||
)
|
||||
@@ -146,7 +147,7 @@ class ReportLogger:
|
||||
stage="generating",
|
||||
section_title=section_title,
|
||||
section_index=section_index,
|
||||
details={"message": f"开始生成章节: {section_title}"}
|
||||
details={"message": t('report.sectionStart', title=section_title)}
|
||||
)
|
||||
|
||||
def log_react_thought(self, section_title: str, section_index: int, iteration: int, thought: str):
|
||||
@@ -159,7 +160,7 @@ class ReportLogger:
|
||||
details={
|
||||
"iteration": iteration,
|
||||
"thought": thought,
|
||||
"message": f"ReACT 第{iteration}轮思考"
|
||||
"message": t('report.reactThought', iteration=iteration)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -181,7 +182,7 @@ class ReportLogger:
|
||||
"iteration": iteration,
|
||||
"tool_name": tool_name,
|
||||
"parameters": parameters,
|
||||
"message": f"调用工具: {tool_name}"
|
||||
"message": t('report.toolCall', toolName=tool_name)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -204,7 +205,7 @@ class ReportLogger:
|
||||
"tool_name": tool_name,
|
||||
"result": result, # 完整结果,不截断
|
||||
"result_length": len(result),
|
||||
"message": f"工具 {tool_name} 返回结果"
|
||||
"message": t('report.toolResult', toolName=tool_name)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -229,7 +230,7 @@ class ReportLogger:
|
||||
"response_length": len(response),
|
||||
"has_tool_calls": has_tool_calls,
|
||||
"has_final_answer": has_final_answer,
|
||||
"message": f"LLM 响应 (工具调用: {has_tool_calls}, 最终答案: {has_final_answer})"
|
||||
"message": t('report.llmResponse', hasToolCalls=has_tool_calls, hasFinalAnswer=has_final_answer)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -250,7 +251,7 @@ class ReportLogger:
|
||||
"content": content, # 完整内容,不截断
|
||||
"content_length": len(content),
|
||||
"tool_calls_count": tool_calls_count,
|
||||
"message": f"章节 {section_title} 内容生成完成"
|
||||
"message": t('report.sectionContentDone', title=section_title)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -273,7 +274,7 @@ class ReportLogger:
|
||||
details={
|
||||
"content": full_content,
|
||||
"content_length": len(full_content),
|
||||
"message": f"章节 {section_title} 生成完成"
|
||||
"message": t('report.sectionComplete', title=section_title)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -285,7 +286,7 @@ class ReportLogger:
|
||||
details={
|
||||
"total_sections": total_sections,
|
||||
"total_time_seconds": round(total_time_seconds, 2),
|
||||
"message": "报告生成完成"
|
||||
"message": t('report.reportComplete')
|
||||
}
|
||||
)
|
||||
|
||||
@@ -298,7 +299,7 @@ class ReportLogger:
|
||||
section_index=None,
|
||||
details={
|
||||
"error": error_message,
|
||||
"message": f"发生错误: {error_message}"
|
||||
"message": t('report.errorOccurred', error=error_message)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -652,9 +653,9 @@ SECTION_SYSTEM_PROMPT_TEMPLATE = """\
|
||||
- 这些引用是模拟预测的核心证据
|
||||
|
||||
3. 【语言一致性 - 引用内容必须翻译为报告语言】
|
||||
- 工具返回的内容可能包含英文或中英文混杂的表述
|
||||
- 如果模拟需求和材料原文是中文的,报告必须全部使用中文撰写
|
||||
- 当你引用工具返回的英文或中英混杂内容时,必须将其翻译为流畅的中文后再写入报告
|
||||
- 工具返回的内容可能包含与报告语言不同的表述
|
||||
- 报告必须全部使用与用户指定语言一致的语言撰写
|
||||
- 当你引用工具返回的其他语言内容时,必须将其翻译为报告语言后再写入
|
||||
- 翻译时保持原意不变,确保表述自然通顺
|
||||
- 这一规则同时适用于正文和引用块(> 格式)中的内容
|
||||
|
||||
@@ -913,7 +914,7 @@ class ReportAgent:
|
||||
# 控制台日志记录器(在 generate_report 中初始化)
|
||||
self.console_logger: Optional[ReportConsoleLogger] = None
|
||||
|
||||
logger.info(f"ReportAgent 初始化完成: graph_id={graph_id}, simulation_id={simulation_id}")
|
||||
logger.info(t('report.agentInitDone', graphId=graph_id, simulationId=simulation_id))
|
||||
|
||||
def _define_tools(self) -> Dict[str, Dict[str, Any]]:
|
||||
"""定义可用工具"""
|
||||
@@ -964,7 +965,7 @@ class ReportAgent:
|
||||
Returns:
|
||||
工具执行结果(文本格式)
|
||||
"""
|
||||
logger.info(f"执行工具: {tool_name}, 参数: {parameters}")
|
||||
logger.info(t('report.executingTool', toolName=tool_name, params=parameters))
|
||||
|
||||
try:
|
||||
if tool_name == "insight_forge":
|
||||
@@ -1023,7 +1024,7 @@ class ReportAgent:
|
||||
|
||||
elif tool_name == "search_graph":
|
||||
# 重定向到 quick_search
|
||||
logger.info("search_graph 已重定向到 quick_search")
|
||||
logger.info(t('report.redirectToQuickSearch'))
|
||||
return self._execute_tool("quick_search", parameters, report_context)
|
||||
|
||||
elif tool_name == "get_graph_statistics":
|
||||
@@ -1040,7 +1041,7 @@ class ReportAgent:
|
||||
|
||||
elif tool_name == "get_simulation_context":
|
||||
# 重定向到 insight_forge,因为它更强大
|
||||
logger.info("get_simulation_context 已重定向到 insight_forge")
|
||||
logger.info(t('report.redirectToInsightForge'))
|
||||
query = parameters.get("query", self.simulation_requirement)
|
||||
return self._execute_tool("insight_forge", {"query": query}, report_context)
|
||||
|
||||
@@ -1057,7 +1058,7 @@ class ReportAgent:
|
||||
return f"未知工具: {tool_name}。请使用以下工具之一: insight_forge, panorama_search, quick_search"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"工具执行失败: {tool_name}, 错误: {str(e)}")
|
||||
logger.error(t('report.toolExecFailed', toolName=tool_name, error=str(e)))
|
||||
return f"工具执行失败: {str(e)}"
|
||||
|
||||
# 合法的工具名称集合,用于裸 JSON 兜底解析时校验
|
||||
@@ -1148,10 +1149,10 @@ class ReportAgent:
|
||||
Returns:
|
||||
ReportOutline: 报告大纲
|
||||
"""
|
||||
logger.info("开始规划报告大纲...")
|
||||
logger.info(t('report.startPlanningOutline'))
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 0, "正在分析模拟需求...")
|
||||
progress_callback("planning", 0, t('progress.analyzingRequirements'))
|
||||
|
||||
# 首先获取模拟上下文
|
||||
context = self.zep_tools.get_simulation_context(
|
||||
@@ -1160,9 +1161,9 @@ class ReportAgent:
|
||||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 30, "正在生成报告大纲...")
|
||||
progress_callback("planning", 30, t('progress.generatingOutline'))
|
||||
|
||||
system_prompt = PLAN_SYSTEM_PROMPT
|
||||
system_prompt = f"{PLAN_SYSTEM_PROMPT}\n\n{get_language_instruction()}"
|
||||
user_prompt = PLAN_USER_PROMPT_TEMPLATE.format(
|
||||
simulation_requirement=self.simulation_requirement,
|
||||
total_nodes=context.get('graph_statistics', {}).get('total_nodes', 0),
|
||||
@@ -1182,7 +1183,7 @@ class ReportAgent:
|
||||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 80, "正在解析大纲结构...")
|
||||
progress_callback("planning", 80, t('progress.parsingOutline'))
|
||||
|
||||
# 解析大纲
|
||||
sections = []
|
||||
@@ -1199,13 +1200,13 @@ class ReportAgent:
|
||||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 100, "大纲规划完成")
|
||||
progress_callback("planning", 100, t('progress.outlinePlanComplete'))
|
||||
|
||||
logger.info(f"大纲规划完成: {len(sections)} 个章节")
|
||||
logger.info(t('report.outlinePlanDone', count=len(sections)))
|
||||
return outline
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"大纲规划失败: {str(e)}")
|
||||
logger.error(t('report.outlinePlanFailed', error=str(e)))
|
||||
# 返回默认大纲(3个章节,作为fallback)
|
||||
return ReportOutline(
|
||||
title="未来预测报告",
|
||||
@@ -1245,7 +1246,7 @@ class ReportAgent:
|
||||
Returns:
|
||||
章节内容(Markdown格式)
|
||||
"""
|
||||
logger.info(f"ReACT生成章节: {section.title}")
|
||||
logger.info(t('report.reactGenerateSection', title=section.title))
|
||||
|
||||
# 记录章节开始日志
|
||||
if self.report_logger:
|
||||
@@ -1258,6 +1259,7 @@ class ReportAgent:
|
||||
section_title=section.title,
|
||||
tools_description=self._get_tools_description(),
|
||||
)
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
# 构建用户prompt - 每个已完成章节各传入最大4000字
|
||||
if previous_sections:
|
||||
@@ -1296,7 +1298,7 @@ class ReportAgent:
|
||||
progress_callback(
|
||||
"generating",
|
||||
int((iteration / max_iterations) * 100),
|
||||
f"深度检索与撰写中 ({tool_calls_count}/{self.MAX_TOOL_CALLS_PER_SECTION})"
|
||||
t('progress.deepSearchAndWrite', current=tool_calls_count, max=self.MAX_TOOL_CALLS_PER_SECTION)
|
||||
)
|
||||
|
||||
# 调用LLM
|
||||
@@ -1308,7 +1310,7 @@ class ReportAgent:
|
||||
|
||||
# 检查 LLM 返回是否为 None(API 异常或内容为空)
|
||||
if response is None:
|
||||
logger.warning(f"章节 {section.title} 第 {iteration + 1} 次迭代: LLM 返回 None")
|
||||
logger.warning(t('report.sectionIterNone', title=section.title, iteration=iteration + 1))
|
||||
# 如果还有迭代次数,添加消息并重试
|
||||
if iteration < max_iterations - 1:
|
||||
messages.append({"role": "assistant", "content": "(响应为空)"})
|
||||
@@ -1328,8 +1330,7 @@ class ReportAgent:
|
||||
if has_tool_calls and has_final_answer:
|
||||
conflict_retries += 1
|
||||
logger.warning(
|
||||
f"章节 {section.title} 第 {iteration+1} 轮: "
|
||||
f"LLM 同时输出工具调用和 Final Answer(第 {conflict_retries} 次冲突)"
|
||||
t('report.sectionConflict', title=section.title, iteration=iteration+1, conflictCount=conflict_retries)
|
||||
)
|
||||
|
||||
if conflict_retries <= 2:
|
||||
@@ -1349,8 +1350,7 @@ class ReportAgent:
|
||||
else:
|
||||
# 第三次:降级处理,截断到第一个工具调用,强制执行
|
||||
logger.warning(
|
||||
f"章节 {section.title}: 连续 {conflict_retries} 次冲突,"
|
||||
"降级为截断执行第一个工具调用"
|
||||
t('report.sectionConflictDowngrade', title=section.title, conflictCount=conflict_retries)
|
||||
)
|
||||
first_tool_end = response.find('</tool_call>')
|
||||
if first_tool_end != -1:
|
||||
@@ -1390,7 +1390,7 @@ class ReportAgent:
|
||||
|
||||
# 正常结束
|
||||
final_answer = response.split("Final Answer:")[-1].strip()
|
||||
logger.info(f"章节 {section.title} 生成完成(工具调用: {tool_calls_count}次)")
|
||||
logger.info(t('report.sectionGenDone', title=section.title, count=tool_calls_count))
|
||||
|
||||
if self.report_logger:
|
||||
self.report_logger.log_section_content(
|
||||
@@ -1418,7 +1418,7 @@ class ReportAgent:
|
||||
# 只执行第一个工具调用
|
||||
call = tool_calls[0]
|
||||
if len(tool_calls) > 1:
|
||||
logger.info(f"LLM 尝试调用 {len(tool_calls)} 个工具,只执行第一个: {call['name']}")
|
||||
logger.info(t('report.multiToolOnlyFirst', total=len(tool_calls), toolName=call['name']))
|
||||
|
||||
if self.report_logger:
|
||||
self.report_logger.log_tool_call(
|
||||
@@ -1487,7 +1487,7 @@ class ReportAgent:
|
||||
|
||||
# 工具调用已足够,LLM 输出了内容但没带 "Final Answer:" 前缀
|
||||
# 直接将这段内容作为最终答案,不再空转
|
||||
logger.info(f"章节 {section.title} 未检测到 'Final Answer:' 前缀,直接采纳LLM输出作为最终内容(工具调用: {tool_calls_count}次)")
|
||||
logger.info(t('report.sectionNoPrefix', title=section.title, count=tool_calls_count))
|
||||
final_answer = response.strip()
|
||||
|
||||
if self.report_logger:
|
||||
@@ -1500,7 +1500,7 @@ class ReportAgent:
|
||||
return final_answer
|
||||
|
||||
# 达到最大迭代次数,强制生成内容
|
||||
logger.warning(f"章节 {section.title} 达到最大迭代次数,强制生成")
|
||||
logger.warning(t('report.sectionMaxIter', title=section.title))
|
||||
messages.append({"role": "user", "content": REACT_FORCE_FINAL_MSG})
|
||||
|
||||
response = self.llm.chat(
|
||||
@@ -1511,8 +1511,8 @@ class ReportAgent:
|
||||
|
||||
# 检查强制收尾时 LLM 返回是否为 None
|
||||
if response is None:
|
||||
logger.error(f"章节 {section.title} 强制收尾时 LLM 返回 None,使用默认错误提示")
|
||||
final_answer = f"(本章节生成失败:LLM 返回空响应,请稍后重试)"
|
||||
logger.error(t('report.sectionForceFailed', title=section.title))
|
||||
final_answer = t('report.sectionGenFailedContent')
|
||||
elif "Final Answer:" in response:
|
||||
final_answer = response.split("Final Answer:")[-1].strip()
|
||||
else:
|
||||
@@ -1590,7 +1590,7 @@ class ReportAgent:
|
||||
self.console_logger = ReportConsoleLogger(report_id)
|
||||
|
||||
ReportManager.update_progress(
|
||||
report_id, "pending", 0, "初始化报告...",
|
||||
report_id, "pending", 0, t('progress.initReport'),
|
||||
completed_sections=[]
|
||||
)
|
||||
ReportManager.save_report(report)
|
||||
@@ -1598,7 +1598,7 @@ class ReportAgent:
|
||||
# 阶段1: 规划大纲
|
||||
report.status = ReportStatus.PLANNING
|
||||
ReportManager.update_progress(
|
||||
report_id, "planning", 5, "开始规划报告大纲...",
|
||||
report_id, "planning", 5, t('progress.startPlanningOutline'),
|
||||
completed_sections=[]
|
||||
)
|
||||
|
||||
@@ -1606,7 +1606,7 @@ class ReportAgent:
|
||||
self.report_logger.log_planning_start()
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("planning", 0, "开始规划报告大纲...")
|
||||
progress_callback("planning", 0, t('progress.startPlanningOutline'))
|
||||
|
||||
outline = self.plan_outline(
|
||||
progress_callback=lambda stage, prog, msg:
|
||||
@@ -1620,12 +1620,12 @@ class ReportAgent:
|
||||
# 保存大纲到文件
|
||||
ReportManager.save_outline(report_id, outline)
|
||||
ReportManager.update_progress(
|
||||
report_id, "planning", 15, f"大纲规划完成,共{len(outline.sections)}个章节",
|
||||
report_id, "planning", 15, t('progress.outlineDone', count=len(outline.sections)),
|
||||
completed_sections=[]
|
||||
)
|
||||
ReportManager.save_report(report)
|
||||
|
||||
logger.info(f"大纲已保存到文件: {report_id}/outline.json")
|
||||
logger.info(t('report.outlineSavedToFile', reportId=report_id))
|
||||
|
||||
# 阶段2: 逐章节生成(分章节保存)
|
||||
report.status = ReportStatus.GENERATING
|
||||
@@ -1640,16 +1640,16 @@ class ReportAgent:
|
||||
# 更新进度
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating", base_progress,
|
||||
f"正在生成章节: {section.title} ({section_num}/{total_sections})",
|
||||
t('progress.generatingSection', title=section.title, current=section_num, total=total_sections),
|
||||
current_section=section.title,
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating",
|
||||
base_progress,
|
||||
f"正在生成章节: {section.title} ({section_num}/{total_sections})"
|
||||
"generating",
|
||||
base_progress,
|
||||
t('progress.generatingSection', title=section.title, current=section_num, total=total_sections)
|
||||
)
|
||||
|
||||
# 生成主章节内容
|
||||
@@ -1683,23 +1683,23 @@ class ReportAgent:
|
||||
full_content=full_section_content.strip()
|
||||
)
|
||||
|
||||
logger.info(f"章节已保存: {report_id}/section_{section_num:02d}.md")
|
||||
logger.info(t('report.sectionSaved', reportId=report_id, sectionNum=f"{section_num:02d}"))
|
||||
|
||||
# 更新进度
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating",
|
||||
base_progress + int(70 / total_sections),
|
||||
f"章节 {section.title} 已完成",
|
||||
t('progress.sectionDone', title=section.title),
|
||||
current_section=None,
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
# 阶段3: 组装完整报告
|
||||
if progress_callback:
|
||||
progress_callback("generating", 95, "正在组装完整报告...")
|
||||
progress_callback("generating", 95, t('progress.assemblingReport'))
|
||||
|
||||
ReportManager.update_progress(
|
||||
report_id, "generating", 95, "正在组装完整报告...",
|
||||
report_id, "generating", 95, t('progress.assemblingReport'),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
@@ -1721,14 +1721,14 @@ class ReportAgent:
|
||||
# 保存最终报告
|
||||
ReportManager.save_report(report)
|
||||
ReportManager.update_progress(
|
||||
report_id, "completed", 100, "报告生成完成",
|
||||
report_id, "completed", 100, t('progress.reportComplete'),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("completed", 100, "报告生成完成")
|
||||
progress_callback("completed", 100, t('progress.reportComplete'))
|
||||
|
||||
logger.info(f"报告生成完成: {report_id}")
|
||||
logger.info(t('report.reportGenDone', reportId=report_id))
|
||||
|
||||
# 关闭控制台日志记录器
|
||||
if self.console_logger:
|
||||
@@ -1738,7 +1738,7 @@ class ReportAgent:
|
||||
return report
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"报告生成失败: {str(e)}")
|
||||
logger.error(t('report.reportGenFailed', error=str(e)))
|
||||
report.status = ReportStatus.FAILED
|
||||
report.error = str(e)
|
||||
|
||||
@@ -1750,7 +1750,7 @@ class ReportAgent:
|
||||
try:
|
||||
ReportManager.save_report(report)
|
||||
ReportManager.update_progress(
|
||||
report_id, "failed", -1, f"报告生成失败: {str(e)}",
|
||||
report_id, "failed", -1, t('progress.reportFailed', error=str(e)),
|
||||
completed_sections=completed_section_titles
|
||||
)
|
||||
except Exception:
|
||||
@@ -1784,7 +1784,7 @@ class ReportAgent:
|
||||
"sources": [信息来源]
|
||||
}
|
||||
"""
|
||||
logger.info(f"Report Agent对话: {message[:50]}...")
|
||||
logger.info(t('report.agentChat', message=message[:50]))
|
||||
|
||||
chat_history = chat_history or []
|
||||
|
||||
@@ -1798,13 +1798,14 @@ class ReportAgent:
|
||||
if len(report.markdown_content) > 15000:
|
||||
report_content += "\n\n... [报告内容已截断] ..."
|
||||
except Exception as e:
|
||||
logger.warning(f"获取报告内容失败: {e}")
|
||||
logger.warning(t('report.fetchReportFailed', error=e))
|
||||
|
||||
system_prompt = CHAT_SYSTEM_PROMPT_TEMPLATE.format(
|
||||
simulation_requirement=self.simulation_requirement,
|
||||
report_content=report_content if report_content else "(暂无报告)",
|
||||
tools_description=self._get_tools_description(),
|
||||
)
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
# 构建消息
|
||||
messages = [{"role": "system", "content": system_prompt}]
|
||||
@@ -2088,7 +2089,7 @@ class ReportManager:
|
||||
with open(cls._get_outline_path(report_id), 'w', encoding='utf-8') as f:
|
||||
json.dump(outline.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
|
||||
logger.info(f"大纲已保存: {report_id}")
|
||||
logger.info(t('report.outlineSaved', reportId=report_id))
|
||||
|
||||
@classmethod
|
||||
def save_section(
|
||||
@@ -2124,7 +2125,7 @@ class ReportManager:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
|
||||
logger.info(f"章节已保存: {report_id}/{file_suffix}")
|
||||
logger.info(t('report.sectionFileSaved', reportId=report_id, fileSuffix=file_suffix))
|
||||
return file_path
|
||||
|
||||
@classmethod
|
||||
@@ -2293,7 +2294,7 @@ class ReportManager:
|
||||
with open(full_path, 'w', encoding='utf-8') as f:
|
||||
f.write(md_content)
|
||||
|
||||
logger.info(f"完整报告已组装: {report_id}")
|
||||
logger.info(t('report.fullReportAssembled', reportId=report_id))
|
||||
return md_content
|
||||
|
||||
@classmethod
|
||||
@@ -2440,7 +2441,7 @@ class ReportManager:
|
||||
with open(cls._get_report_markdown_path(report.report_id), 'w', encoding='utf-8') as f:
|
||||
f.write(report.markdown_content)
|
||||
|
||||
logger.info(f"报告已保存: {report.report_id}")
|
||||
logger.info(t('report.reportSaved', reportId=report.report_id))
|
||||
|
||||
@classmethod
|
||||
def get_report(cls, report_id: str) -> Optional[Report]:
|
||||
@@ -2553,7 +2554,7 @@ class ReportManager:
|
||||
# 新格式:删除整个文件夹
|
||||
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
||||
shutil.rmtree(folder_path)
|
||||
logger.info(f"报告文件夹已删除: {report_id}")
|
||||
logger.info(t('report.reportFolderDeleted', reportId=report_id))
|
||||
return True
|
||||
|
||||
# 兼容旧格式:删除单独的文件
|
||||
|
||||
@@ -20,6 +20,7 @@ from openai import OpenAI
|
||||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_language_instruction, t
|
||||
from .zep_entity_reader import EntityNode, ZepEntityReader
|
||||
|
||||
logger = get_logger('mirofish.simulation_config')
|
||||
@@ -292,17 +293,17 @@ class SimulationConfigGenerator:
|
||||
reasoning_parts = []
|
||||
|
||||
# ========== 步骤1: 生成时间配置 ==========
|
||||
report_progress(1, "生成时间配置...")
|
||||
report_progress(1, t('progress.generatingTimeConfig'))
|
||||
num_entities = len(entities)
|
||||
time_config_result = self._generate_time_config(context, num_entities)
|
||||
time_config = self._parse_time_config(time_config_result, num_entities)
|
||||
reasoning_parts.append(f"时间配置: {time_config_result.get('reasoning', '成功')}")
|
||||
reasoning_parts.append(f"{t('progress.timeConfigLabel')}: {time_config_result.get('reasoning', t('common.success'))}")
|
||||
|
||||
# ========== 步骤2: 生成事件配置 ==========
|
||||
report_progress(2, "生成事件配置和热点话题...")
|
||||
report_progress(2, t('progress.generatingEventConfig'))
|
||||
event_config_result = self._generate_event_config(context, simulation_requirement, entities)
|
||||
event_config = self._parse_event_config(event_config_result)
|
||||
reasoning_parts.append(f"事件配置: {event_config_result.get('reasoning', '成功')}")
|
||||
reasoning_parts.append(f"{t('progress.eventConfigLabel')}: {event_config_result.get('reasoning', t('common.success'))}")
|
||||
|
||||
# ========== 步骤3-N: 分批生成Agent配置 ==========
|
||||
all_agent_configs = []
|
||||
@@ -313,7 +314,7 @@ class SimulationConfigGenerator:
|
||||
|
||||
report_progress(
|
||||
3 + batch_idx,
|
||||
f"生成Agent配置 ({start_idx + 1}-{end_idx}/{len(entities)})..."
|
||||
t('progress.generatingAgentConfig', start=start_idx + 1, end=end_idx, total=len(entities))
|
||||
)
|
||||
|
||||
batch_configs = self._generate_agent_configs_batch(
|
||||
@@ -324,16 +325,16 @@ class SimulationConfigGenerator:
|
||||
)
|
||||
all_agent_configs.extend(batch_configs)
|
||||
|
||||
reasoning_parts.append(f"Agent配置: 成功生成 {len(all_agent_configs)} 个")
|
||||
reasoning_parts.append(t('progress.agentConfigResult', count=len(all_agent_configs)))
|
||||
|
||||
# ========== 为初始帖子分配发布者 Agent ==========
|
||||
logger.info("为初始帖子分配合适的发布者 Agent...")
|
||||
event_config = self._assign_initial_post_agents(event_config, all_agent_configs)
|
||||
assigned_count = len([p for p in event_config.initial_posts if p.get("poster_agent_id") is not None])
|
||||
reasoning_parts.append(f"初始帖子分配: {assigned_count} 个帖子已分配发布者")
|
||||
reasoning_parts.append(t('progress.postAssignResult', count=assigned_count))
|
||||
|
||||
# ========== 最后一步: 生成平台配置 ==========
|
||||
report_progress(total_steps, "生成平台配置...")
|
||||
report_progress(total_steps, t('progress.generatingPlatformConfig'))
|
||||
twitter_config = None
|
||||
reddit_config = None
|
||||
|
||||
@@ -547,7 +548,7 @@ class SimulationConfigGenerator:
|
||||
请生成时间配置JSON。
|
||||
|
||||
### 基本原则(仅供参考,需根据具体事件和参与群体灵活调整):
|
||||
- 用户群体为中国人,需符合北京时间作息习惯
|
||||
- 请根据模拟场景推断目标用户群体所在时区和作息习惯,以下为东八区(UTC+8)的参考示例
|
||||
- 凌晨0-5点几乎无人活动(活跃度系数0.05)
|
||||
- 早上6-8点逐渐活跃(活跃度系数0.4)
|
||||
- 工作时间9-18点中等活跃(活跃度系数0.7)
|
||||
@@ -584,8 +585,9 @@ class SimulationConfigGenerator:
|
||||
- work_hours (int数组): 工作时段
|
||||
- reasoning (string): 简要说明为什么这样配置"""
|
||||
|
||||
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合中国人作息习惯。"
|
||||
|
||||
system_prompt = "你是社交媒体模拟专家。返回纯JSON格式,时间配置需符合模拟场景中目标用户群体的作息习惯。"
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}"
|
||||
|
||||
try:
|
||||
return self._call_llm_with_retry(prompt, system_prompt)
|
||||
except Exception as e:
|
||||
@@ -701,7 +703,8 @@ class SimulationConfigGenerator:
|
||||
}}"""
|
||||
|
||||
system_prompt = "你是舆论分析专家。返回纯JSON格式。注意 poster_type 必须精确匹配可用实体类型。"
|
||||
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}\nIMPORTANT: The 'poster_type' field value MUST be in English PascalCase exactly matching the available entity types. Only 'content', 'narrative_direction', 'hot_topics' and 'reasoning' fields should use the specified language."
|
||||
|
||||
try:
|
||||
return self._call_llm_with_retry(prompt, system_prompt)
|
||||
except Exception as e:
|
||||
@@ -838,7 +841,7 @@ class SimulationConfigGenerator:
|
||||
|
||||
## 任务
|
||||
为每个实体生成活动配置,注意:
|
||||
- **时间符合中国人作息**:凌晨0-5点几乎不活动,晚间19-22点最活跃
|
||||
- **时间符合目标用户群体作息**:以下为参考(东八区),请根据模拟场景调整
|
||||
- **官方机构**(University/GovernmentAgency):活跃度低(0.1-0.3),工作时间(9-17)活动,响应慢(60-240分钟),影响力高(2.5-3.0)
|
||||
- **媒体**(MediaOutlet):活跃度中(0.4-0.6),全天活动(8-23),响应快(5-30分钟),影响力高(2.0-2.5)
|
||||
- **个人**(Student/Person/Alumni):活跃度高(0.6-0.9),主要晚间活动(18-23),响应快(1-15分钟),影响力低(0.8-1.2)
|
||||
@@ -863,8 +866,9 @@ class SimulationConfigGenerator:
|
||||
]
|
||||
}}"""
|
||||
|
||||
system_prompt = "你是社交媒体行为分析专家。返回纯JSON,配置需符合中国人作息习惯。"
|
||||
|
||||
system_prompt = "你是社交媒体行为分析专家。返回纯JSON,配置需符合模拟场景中目标用户群体的作息习惯。"
|
||||
system_prompt = f"{system_prompt}\n\n{get_language_instruction()}\nIMPORTANT: The 'stance' field value MUST be one of the English strings: 'supportive', 'opposing', 'neutral', 'observer'. All JSON field names and numeric values must remain unchanged. Only natural language text fields should use the specified language."
|
||||
|
||||
try:
|
||||
result = self._call_llm_with_retry(prompt, system_prompt)
|
||||
llm_configs = {cfg["agent_id"]: cfg for cfg in result.get("agent_configs", [])}
|
||||
|
||||
@@ -17,6 +17,7 @@ from ..utils.logger import get_logger
|
||||
from .zep_entity_reader import ZepEntityReader, FilteredEntities
|
||||
from .oasis_profile_generator import OasisProfileGenerator, OasisAgentProfile
|
||||
from .simulation_config_generator import SimulationConfigGenerator, SimulationParameters
|
||||
from ..utils.locale import t
|
||||
|
||||
logger = get_logger('mirofish.simulation')
|
||||
|
||||
@@ -270,12 +271,12 @@ class SimulationManager:
|
||||
|
||||
# ========== 阶段1: 读取并过滤实体 ==========
|
||||
if progress_callback:
|
||||
progress_callback("reading", 0, "正在连接Zep图谱...")
|
||||
progress_callback("reading", 0, t('progress.connectingZepGraph'))
|
||||
|
||||
reader = ZepEntityReader()
|
||||
|
||||
if progress_callback:
|
||||
progress_callback("reading", 30, "正在读取节点数据...")
|
||||
progress_callback("reading", 30, t('progress.readingNodeData'))
|
||||
|
||||
filtered = reader.filter_defined_entities(
|
||||
graph_id=state.graph_id,
|
||||
@@ -288,8 +289,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"reading", 100,
|
||||
f"完成,共 {filtered.filtered_count} 个实体",
|
||||
"reading", 100,
|
||||
t('progress.readingComplete', count=filtered.filtered_count),
|
||||
current=filtered.filtered_count,
|
||||
total=filtered.filtered_count
|
||||
)
|
||||
@@ -305,8 +306,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 0,
|
||||
"开始生成...",
|
||||
"generating_profiles", 0,
|
||||
t('progress.startGenerating'),
|
||||
current=0,
|
||||
total=total_entities
|
||||
)
|
||||
@@ -351,8 +352,8 @@ class SimulationManager:
|
||||
# Reddit 已经在生成过程中实时保存了,这里再保存一次确保完整性
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 95,
|
||||
"保存Profile文件...",
|
||||
"generating_profiles", 95,
|
||||
t('progress.savingProfiles'),
|
||||
current=total_entities,
|
||||
total=total_entities
|
||||
)
|
||||
@@ -374,8 +375,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_profiles", 100,
|
||||
f"完成,共 {len(profiles)} 个Profile",
|
||||
"generating_profiles", 100,
|
||||
t('progress.profilesComplete', count=len(profiles)),
|
||||
current=len(profiles),
|
||||
total=len(profiles)
|
||||
)
|
||||
@@ -383,8 +384,8 @@ class SimulationManager:
|
||||
# ========== 阶段3: LLM智能生成模拟配置 ==========
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 0,
|
||||
"正在分析模拟需求...",
|
||||
"generating_config", 0,
|
||||
t('progress.analyzingRequirements'),
|
||||
current=0,
|
||||
total=3
|
||||
)
|
||||
@@ -393,8 +394,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 30,
|
||||
"正在调用LLM生成配置...",
|
||||
"generating_config", 30,
|
||||
t('progress.callingLLMConfig'),
|
||||
current=1,
|
||||
total=3
|
||||
)
|
||||
@@ -412,8 +413,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 70,
|
||||
"正在保存配置文件...",
|
||||
"generating_config", 70,
|
||||
t('progress.savingConfigFiles'),
|
||||
current=2,
|
||||
total=3
|
||||
)
|
||||
@@ -428,8 +429,8 @@ class SimulationManager:
|
||||
|
||||
if progress_callback:
|
||||
progress_callback(
|
||||
"generating_config", 100,
|
||||
"配置生成完成",
|
||||
"generating_config", 100,
|
||||
t('progress.configComplete'),
|
||||
current=3,
|
||||
total=3
|
||||
)
|
||||
|
||||
@@ -20,6 +20,7 @@ from queue import Queue
|
||||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
from .zep_graph_memory_updater import ZepGraphMemoryManager
|
||||
from .simulation_ipc import SimulationIPCClient, CommandType, IPCResponse
|
||||
|
||||
@@ -455,10 +456,13 @@ class SimulationRunner:
|
||||
cls._processes[simulation_id] = process
|
||||
cls._save_run_state(state)
|
||||
|
||||
# Capture locale before spawning monitor thread
|
||||
current_locale = get_locale()
|
||||
|
||||
# 启动监控线程
|
||||
monitor_thread = threading.Thread(
|
||||
target=cls._monitor_simulation,
|
||||
args=(simulation_id,),
|
||||
args=(simulation_id, current_locale),
|
||||
daemon=True
|
||||
)
|
||||
monitor_thread.start()
|
||||
@@ -475,8 +479,9 @@ class SimulationRunner:
|
||||
return state
|
||||
|
||||
@classmethod
|
||||
def _monitor_simulation(cls, simulation_id: str):
|
||||
def _monitor_simulation(cls, simulation_id: str, locale: str = 'zh'):
|
||||
"""监控模拟进程,解析动作日志"""
|
||||
set_locale(locale)
|
||||
sim_dir = os.path.join(cls.RUN_STATE_DIR, simulation_id)
|
||||
|
||||
# 新的日志结构:分平台的动作日志
|
||||
|
||||
@@ -16,6 +16,7 @@ from zep_cloud.client import Zep
|
||||
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.locale import get_locale, set_locale
|
||||
|
||||
logger = get_logger('mirofish.zep_graph_memory_updater')
|
||||
|
||||
@@ -275,10 +276,14 @@ class ZepGraphMemoryUpdater:
|
||||
"""启动后台工作线程"""
|
||||
if self._running:
|
||||
return
|
||||
|
||||
|
||||
# Capture locale before spawning background thread
|
||||
current_locale = get_locale()
|
||||
|
||||
self._running = True
|
||||
self._worker_thread = threading.Thread(
|
||||
target=self._worker_loop,
|
||||
args=(current_locale,),
|
||||
daemon=True,
|
||||
name=f"ZepMemoryUpdater-{self.graph_id[:8]}"
|
||||
)
|
||||
@@ -356,8 +361,9 @@ class ZepGraphMemoryUpdater:
|
||||
|
||||
self.add_activity(activity)
|
||||
|
||||
def _worker_loop(self):
|
||||
def _worker_loop(self, locale: str = 'zh'):
|
||||
"""后台工作循环 - 按平台批量发送活动到Zep"""
|
||||
set_locale(locale)
|
||||
while self._running or not self._activity_queue.empty():
|
||||
try:
|
||||
# 尝试从队列获取活动(超时1秒)
|
||||
|
||||
@@ -18,6 +18,7 @@ from zep_cloud.client import Zep
|
||||
from ..config import Config
|
||||
from ..utils.logger import get_logger
|
||||
from ..utils.llm_client import LLMClient
|
||||
from ..utils.locale import get_locale, t
|
||||
from ..utils.zep_paging import fetch_all_nodes, fetch_all_edges
|
||||
|
||||
logger = get_logger('mirofish.zep_tools')
|
||||
@@ -429,7 +430,7 @@ class ZepToolsService:
|
||||
self.client = Zep(api_key=self.api_key)
|
||||
# LLM客户端用于InsightForge生成子问题
|
||||
self._llm_client = llm_client
|
||||
logger.info("ZepToolsService 初始化完成")
|
||||
logger.info(t("console.zepToolsInitialized"))
|
||||
|
||||
@property
|
||||
def llm(self) -> LLMClient:
|
||||
@@ -451,13 +452,12 @@ class ZepToolsService:
|
||||
last_exception = e
|
||||
if attempt < max_retries - 1:
|
||||
logger.warning(
|
||||
f"Zep {operation_name} 第 {attempt + 1} 次尝试失败: {str(e)[:100]}, "
|
||||
f"{delay:.1f}秒后重试..."
|
||||
t("console.zepRetryAttempt", operation=operation_name, attempt=attempt + 1, error=str(e)[:100], delay=f"{delay:.1f}")
|
||||
)
|
||||
time.sleep(delay)
|
||||
delay *= 2
|
||||
else:
|
||||
logger.error(f"Zep {operation_name} 在 {max_retries} 次尝试后仍失败: {str(e)}")
|
||||
logger.error(t("console.zepAllRetriesFailed", operation=operation_name, retries=max_retries, error=str(e)))
|
||||
|
||||
raise last_exception
|
||||
|
||||
@@ -483,7 +483,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"图谱搜索: graph_id={graph_id}, query={query[:50]}...")
|
||||
logger.info(t("console.graphSearch", graphId=graph_id, query=query[:50]))
|
||||
|
||||
# 尝试使用Zep Cloud Search API
|
||||
try:
|
||||
@@ -495,7 +495,7 @@ class ZepToolsService:
|
||||
scope=scope,
|
||||
reranker="cross_encoder"
|
||||
),
|
||||
operation_name=f"图谱搜索(graph={graph_id})"
|
||||
operation_name=t("console.graphSearchOp", graphId=graph_id)
|
||||
)
|
||||
|
||||
facts = []
|
||||
@@ -528,7 +528,7 @@ class ZepToolsService:
|
||||
if hasattr(node, 'summary') and node.summary:
|
||||
facts.append(f"[{node.name}]: {node.summary}")
|
||||
|
||||
logger.info(f"搜索完成: 找到 {len(facts)} 条相关事实")
|
||||
logger.info(t("console.searchComplete", count=len(facts)))
|
||||
|
||||
return SearchResult(
|
||||
facts=facts,
|
||||
@@ -539,7 +539,7 @@ class ZepToolsService:
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Zep Search API失败,降级为本地搜索: {str(e)}")
|
||||
logger.warning(t("console.zepSearchApiFallback", error=str(e)))
|
||||
# 降级:使用本地关键词匹配搜索
|
||||
return self._local_search(graph_id, query, limit, scope)
|
||||
|
||||
@@ -564,7 +564,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"使用本地搜索: query={query[:30]}...")
|
||||
logger.info(t("console.usingLocalSearch", query=query[:30]))
|
||||
|
||||
facts = []
|
||||
edges_result = []
|
||||
@@ -634,10 +634,10 @@ class ZepToolsService:
|
||||
if node.summary:
|
||||
facts.append(f"[{node.name}]: {node.summary}")
|
||||
|
||||
logger.info(f"本地搜索完成: 找到 {len(facts)} 条相关事实")
|
||||
logger.info(t("console.localSearchComplete", count=len(facts)))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"本地搜索失败: {str(e)}")
|
||||
logger.error(t("console.localSearchFailed", error=str(e)))
|
||||
|
||||
return SearchResult(
|
||||
facts=facts,
|
||||
@@ -657,7 +657,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
节点列表
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的所有节点...")
|
||||
logger.info(t("console.fetchingAllNodes", graphId=graph_id))
|
||||
|
||||
nodes = fetch_all_nodes(self.client, graph_id)
|
||||
|
||||
@@ -672,7 +672,7 @@ class ZepToolsService:
|
||||
attributes=node.attributes or {}
|
||||
))
|
||||
|
||||
logger.info(f"获取到 {len(result)} 个节点")
|
||||
logger.info(t("console.fetchedNodes", count=len(result)))
|
||||
return result
|
||||
|
||||
def get_all_edges(self, graph_id: str, include_temporal: bool = True) -> List[EdgeInfo]:
|
||||
@@ -686,7 +686,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
边列表(包含created_at, valid_at, invalid_at, expired_at)
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的所有边...")
|
||||
logger.info(t("console.fetchingAllEdges", graphId=graph_id))
|
||||
|
||||
edges = fetch_all_edges(self.client, graph_id)
|
||||
|
||||
@@ -710,7 +710,7 @@ class ZepToolsService:
|
||||
|
||||
result.append(edge_info)
|
||||
|
||||
logger.info(f"获取到 {len(result)} 条边")
|
||||
logger.info(t("console.fetchedEdges", count=len(result)))
|
||||
return result
|
||||
|
||||
def get_node_detail(self, node_uuid: str) -> Optional[NodeInfo]:
|
||||
@@ -723,12 +723,12 @@ class ZepToolsService:
|
||||
Returns:
|
||||
节点信息或None
|
||||
"""
|
||||
logger.info(f"获取节点详情: {node_uuid[:8]}...")
|
||||
logger.info(t("console.fetchingNodeDetail", uuid=node_uuid[:8]))
|
||||
|
||||
try:
|
||||
node = self._call_with_retry(
|
||||
func=lambda: self.client.graph.node.get(uuid_=node_uuid),
|
||||
operation_name=f"获取节点详情(uuid={node_uuid[:8]}...)"
|
||||
operation_name=t("console.fetchNodeDetailOp", uuid=node_uuid[:8])
|
||||
)
|
||||
|
||||
if not node:
|
||||
@@ -742,7 +742,7 @@ class ZepToolsService:
|
||||
attributes=node.attributes or {}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"获取节点详情失败: {str(e)}")
|
||||
logger.error(t("console.fetchNodeDetailFailed", error=str(e)))
|
||||
return None
|
||||
|
||||
def get_node_edges(self, graph_id: str, node_uuid: str) -> List[EdgeInfo]:
|
||||
@@ -758,7 +758,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
边列表
|
||||
"""
|
||||
logger.info(f"获取节点 {node_uuid[:8]}... 的相关边")
|
||||
logger.info(t("console.fetchingNodeEdges", uuid=node_uuid[:8]))
|
||||
|
||||
try:
|
||||
# 获取图谱所有边,然后过滤
|
||||
@@ -770,11 +770,11 @@ class ZepToolsService:
|
||||
if edge.source_node_uuid == node_uuid or edge.target_node_uuid == node_uuid:
|
||||
result.append(edge)
|
||||
|
||||
logger.info(f"找到 {len(result)} 条与节点相关的边")
|
||||
logger.info(t("console.foundNodeEdges", count=len(result)))
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"获取节点边失败: {str(e)}")
|
||||
logger.warning(t("console.fetchNodeEdgesFailed", error=str(e)))
|
||||
return []
|
||||
|
||||
def get_entities_by_type(
|
||||
@@ -792,7 +792,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
符合类型的实体列表
|
||||
"""
|
||||
logger.info(f"获取类型为 {entity_type} 的实体...")
|
||||
logger.info(t("console.fetchingEntitiesByType", type=entity_type))
|
||||
|
||||
all_nodes = self.get_all_nodes(graph_id)
|
||||
|
||||
@@ -802,7 +802,7 @@ class ZepToolsService:
|
||||
if entity_type in node.labels:
|
||||
filtered.append(node)
|
||||
|
||||
logger.info(f"找到 {len(filtered)} 个 {entity_type} 类型的实体")
|
||||
logger.info(t("console.foundEntitiesByType", count=len(filtered), type=entity_type))
|
||||
return filtered
|
||||
|
||||
def get_entity_summary(
|
||||
@@ -822,7 +822,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
实体摘要信息
|
||||
"""
|
||||
logger.info(f"获取实体 {entity_name} 的关系摘要...")
|
||||
logger.info(t("console.fetchingEntitySummary", name=entity_name))
|
||||
|
||||
# 先搜索该实体相关的信息
|
||||
search_result = self.search_graph(
|
||||
@@ -862,7 +862,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
统计信息
|
||||
"""
|
||||
logger.info(f"获取图谱 {graph_id} 的统计信息...")
|
||||
logger.info(t("console.fetchingGraphStats", graphId=graph_id))
|
||||
|
||||
nodes = self.get_all_nodes(graph_id)
|
||||
edges = self.get_all_edges(graph_id)
|
||||
@@ -906,7 +906,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
模拟上下文信息
|
||||
"""
|
||||
logger.info(f"获取模拟上下文: {simulation_requirement[:50]}...")
|
||||
logger.info(t("console.fetchingSimContext", requirement=simulation_requirement[:50]))
|
||||
|
||||
# 搜索与模拟需求相关的信息
|
||||
search_result = self.search_graph(
|
||||
@@ -970,7 +970,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
InsightForgeResult: 深度洞察检索结果
|
||||
"""
|
||||
logger.info(f"InsightForge 深度洞察检索: {query[:50]}...")
|
||||
logger.info(t("console.insightForgeStart", query=query[:50]))
|
||||
|
||||
result = InsightForgeResult(
|
||||
query=query,
|
||||
@@ -986,7 +986,7 @@ class ZepToolsService:
|
||||
max_queries=max_sub_queries
|
||||
)
|
||||
result.sub_queries = sub_queries
|
||||
logger.info(f"生成 {len(sub_queries)} 个子问题")
|
||||
logger.info(t("console.generatedSubQueries", count=len(sub_queries)))
|
||||
|
||||
# Step 2: 对每个子问题进行语义搜索
|
||||
all_facts = []
|
||||
@@ -1086,7 +1086,7 @@ class ZepToolsService:
|
||||
result.relationship_chains = relationship_chains
|
||||
result.total_relationships = len(relationship_chains)
|
||||
|
||||
logger.info(f"InsightForge完成: {result.total_facts}条事实, {result.total_entities}个实体, {result.total_relationships}条关系")
|
||||
logger.info(t("console.insightForgeComplete", facts=result.total_facts, entities=result.total_entities, relationships=result.total_relationships))
|
||||
return result
|
||||
|
||||
def _generate_sub_queries(
|
||||
@@ -1133,7 +1133,7 @@ class ZepToolsService:
|
||||
return [str(sq) for sq in sub_queries[:max_queries]]
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成子问题失败: {str(e)},使用默认子问题")
|
||||
logger.warning(t("console.generateSubQueriesFailed", error=str(e)))
|
||||
# 降级:返回基于原问题的变体
|
||||
return [
|
||||
query,
|
||||
@@ -1168,7 +1168,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
PanoramaResult: 广度搜索结果
|
||||
"""
|
||||
logger.info(f"PanoramaSearch 广度搜索: {query[:50]}...")
|
||||
logger.info(t("console.panoramaSearchStart", query=query[:50]))
|
||||
|
||||
result = PanoramaResult(query=query)
|
||||
|
||||
@@ -1231,7 +1231,7 @@ class ZepToolsService:
|
||||
result.active_count = len(active_facts)
|
||||
result.historical_count = len(historical_facts)
|
||||
|
||||
logger.info(f"PanoramaSearch完成: {result.active_count}条有效, {result.historical_count}条历史")
|
||||
logger.info(t("console.panoramaSearchComplete", active=result.active_count, historical=result.historical_count))
|
||||
return result
|
||||
|
||||
def quick_search(
|
||||
@@ -1256,7 +1256,7 @@ class ZepToolsService:
|
||||
Returns:
|
||||
SearchResult: 搜索结果
|
||||
"""
|
||||
logger.info(f"QuickSearch 简单搜索: {query[:50]}...")
|
||||
logger.info(t("console.quickSearchStart", query=query[:50]))
|
||||
|
||||
# 直接调用现有的search_graph方法
|
||||
result = self.search_graph(
|
||||
@@ -1266,7 +1266,7 @@ class ZepToolsService:
|
||||
scope="edges"
|
||||
)
|
||||
|
||||
logger.info(f"QuickSearch完成: {result.total_count}条结果")
|
||||
logger.info(t("console.quickSearchComplete", count=result.total_count))
|
||||
return result
|
||||
|
||||
def interview_agents(
|
||||
@@ -1306,7 +1306,7 @@ class ZepToolsService:
|
||||
"""
|
||||
from .simulation_runner import SimulationRunner
|
||||
|
||||
logger.info(f"InterviewAgents 深度采访(真实API): {interview_requirement[:50]}...")
|
||||
logger.info(t("console.interviewAgentsStart", requirement=interview_requirement[:50]))
|
||||
|
||||
result = InterviewResult(
|
||||
interview_topic=interview_requirement,
|
||||
@@ -1317,12 +1317,12 @@ class ZepToolsService:
|
||||
profiles = self._load_agent_profiles(simulation_id)
|
||||
|
||||
if not profiles:
|
||||
logger.warning(f"未找到模拟 {simulation_id} 的人设文件")
|
||||
logger.warning(t("console.profilesNotFound", simId=simulation_id))
|
||||
result.summary = "未找到可采访的Agent人设文件"
|
||||
return result
|
||||
|
||||
result.total_agents = len(profiles)
|
||||
logger.info(f"加载到 {len(profiles)} 个Agent人设")
|
||||
logger.info(t("console.loadedProfiles", count=len(profiles)))
|
||||
|
||||
# Step 2: 使用LLM选择要采访的Agent(返回agent_id列表)
|
||||
selected_agents, selected_indices, selection_reasoning = self._select_agents_for_interview(
|
||||
@@ -1334,7 +1334,7 @@ class ZepToolsService:
|
||||
|
||||
result.selected_agents = selected_agents
|
||||
result.selection_reasoning = selection_reasoning
|
||||
logger.info(f"选择了 {len(selected_agents)} 个Agent进行采访: {selected_indices}")
|
||||
logger.info(t("console.selectedAgentsForInterview", count=len(selected_agents), indices=selected_indices))
|
||||
|
||||
# Step 3: 生成采访问题(如果没有提供)
|
||||
if not result.interview_questions:
|
||||
@@ -1343,7 +1343,7 @@ class ZepToolsService:
|
||||
simulation_requirement=simulation_requirement,
|
||||
selected_agents=selected_agents
|
||||
)
|
||||
logger.info(f"生成了 {len(result.interview_questions)} 个采访问题")
|
||||
logger.info(t("console.generatedInterviewQuestions", count=len(result.interview_questions)))
|
||||
|
||||
# 将问题合并为一个采访prompt
|
||||
combined_prompt = "\n".join([f"{i+1}. {q}" for i, q in enumerate(result.interview_questions)])
|
||||
@@ -1373,7 +1373,7 @@ class ZepToolsService:
|
||||
# 不指定platform,API会在twitter和reddit两个平台都采访
|
||||
})
|
||||
|
||||
logger.info(f"调用批量采访API(双平台): {len(interviews_request)} 个Agent")
|
||||
logger.info(t("console.callingBatchInterviewApi", count=len(interviews_request)))
|
||||
|
||||
# 调用 SimulationRunner 的批量采访方法(不传platform,双平台采访)
|
||||
api_result = SimulationRunner.interview_agents_batch(
|
||||
@@ -1383,12 +1383,12 @@ class ZepToolsService:
|
||||
timeout=180.0 # 双平台需要更长超时
|
||||
)
|
||||
|
||||
logger.info(f"采访API返回: {api_result.get('interviews_count', 0)} 个结果, success={api_result.get('success')}")
|
||||
logger.info(t("console.interviewApiReturned", count=api_result.get('interviews_count', 0), success=api_result.get('success')))
|
||||
|
||||
# 检查API调用是否成功
|
||||
if not api_result.get("success", False):
|
||||
error_msg = api_result.get("error", "未知错误")
|
||||
logger.warning(f"采访API返回失败: {error_msg}")
|
||||
logger.warning(t("console.interviewApiReturnedFailure", error=error_msg))
|
||||
result.summary = f"采访API调用失败:{error_msg}。请检查OASIS模拟环境状态。"
|
||||
return result
|
||||
|
||||
@@ -1461,11 +1461,11 @@ class ZepToolsService:
|
||||
|
||||
except ValueError as e:
|
||||
# 模拟环境未运行
|
||||
logger.warning(f"采访API调用失败(环境未运行?): {e}")
|
||||
logger.warning(t("console.interviewApiCallFailed", error=e))
|
||||
result.summary = f"采访失败:{str(e)}。模拟环境可能已关闭,请确保OASIS环境正在运行。"
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"采访API调用异常: {e}")
|
||||
logger.error(t("console.interviewApiCallException", error=e))
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
result.summary = f"采访过程发生错误:{str(e)}"
|
||||
@@ -1478,7 +1478,7 @@ class ZepToolsService:
|
||||
interview_requirement=interview_requirement
|
||||
)
|
||||
|
||||
logger.info(f"InterviewAgents完成: 采访了 {result.interviewed_count} 个Agent(双平台)")
|
||||
logger.info(t("console.interviewAgentsComplete", count=result.interviewed_count))
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
@@ -1521,10 +1521,10 @@ class ZepToolsService:
|
||||
try:
|
||||
with open(reddit_profile_path, 'r', encoding='utf-8') as f:
|
||||
profiles = json.load(f)
|
||||
logger.info(f"从 reddit_profiles.json 加载了 {len(profiles)} 个人设")
|
||||
logger.info(t("console.loadedRedditProfiles", count=len(profiles)))
|
||||
return profiles
|
||||
except Exception as e:
|
||||
logger.warning(f"读取 reddit_profiles.json 失败: {e}")
|
||||
logger.warning(t("console.readRedditProfilesFailed", error=e))
|
||||
|
||||
# 尝试读取Twitter CSV格式
|
||||
twitter_profile_path = os.path.join(sim_dir, "twitter_profiles.csv")
|
||||
@@ -1541,10 +1541,10 @@ class ZepToolsService:
|
||||
"persona": row.get("user_char", ""),
|
||||
"profession": "未知"
|
||||
})
|
||||
logger.info(f"从 twitter_profiles.csv 加载了 {len(profiles)} 个人设")
|
||||
logger.info(t("console.loadedTwitterProfiles", count=len(profiles)))
|
||||
return profiles
|
||||
except Exception as e:
|
||||
logger.warning(f"读取 twitter_profiles.csv 失败: {e}")
|
||||
logger.warning(t("console.readTwitterProfilesFailed", error=e))
|
||||
|
||||
return profiles
|
||||
|
||||
@@ -1625,7 +1625,7 @@ class ZepToolsService:
|
||||
return selected_agents, valid_indices, reasoning
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"LLM选择Agent失败,使用默认选择: {e}")
|
||||
logger.warning(t("console.llmSelectAgentFailed", error=e))
|
||||
# 降级:选择前N个
|
||||
selected = profiles[:max_agents]
|
||||
indices = list(range(min(max_agents, len(profiles))))
|
||||
@@ -1673,7 +1673,7 @@ class ZepToolsService:
|
||||
return response.get("questions", [f"关于{interview_requirement},您有什么看法?"])
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成采访问题失败: {e}")
|
||||
logger.warning(t("console.generateInterviewQuestionsFailed", error=e))
|
||||
return [
|
||||
f"关于{interview_requirement},您的观点是什么?",
|
||||
"这件事对您或您所代表的群体有什么影响?",
|
||||
@@ -1695,7 +1695,8 @@ class ZepToolsService:
|
||||
for interview in interviews:
|
||||
interview_texts.append(f"【{interview.agent_name}({interview.agent_role})】\n{interview.response[:500]}")
|
||||
|
||||
system_prompt = """你是一个专业的新闻编辑。请根据多位受访者的回答,生成一份采访摘要。
|
||||
quote_instruction = "引用受访者原话时使用中文引号「」" if get_locale() == 'zh' else 'Use quotation marks "" when quoting interviewees'
|
||||
system_prompt = f"""你是一个专业的新闻编辑。请根据多位受访者的回答,生成一份采访摘要。
|
||||
|
||||
摘要要求:
|
||||
1. 提炼各方主要观点
|
||||
@@ -1708,7 +1709,7 @@ class ZepToolsService:
|
||||
- 使用纯文本段落,用空行分隔不同部分
|
||||
- 不要使用Markdown标题(如#、##、###)
|
||||
- 不要使用分割线(如---、***)
|
||||
- 引用受访者原话时使用中文引号「」
|
||||
- {quote_instruction}
|
||||
- 可以使用**加粗**标记关键词,但不要使用其他Markdown语法"""
|
||||
|
||||
user_prompt = f"""采访主题:{interview_requirement}
|
||||
@@ -1730,6 +1731,6 @@ class ZepToolsService:
|
||||
return summary
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"生成采访摘要失败: {e}")
|
||||
logger.warning(t("console.generateInterviewSummaryFailed", error=e))
|
||||
# 降级:简单拼接
|
||||
return f"共采访了{len(interviews)}位受访者,包括:" + "、".join([i.agent_name for i in interviews])
|
||||
|
||||
Reference in New Issue
Block a user