Merge pull request #428 from Ghostubborn/feat/i18n

feat(i18n): 添加多语言切换功能,支持中英文
This commit is contained in:
BaiFu
2026-04-02 14:27:04 +08:00
committed by GitHub
39 changed files with 2612 additions and 766 deletions

View File

@@ -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:
"""获取图谱信息"""

View File

@@ -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必须是整数30gender必须是字符串"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"",

View File

@@ -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}
]

View File

@@ -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 返回是否为 NoneAPI 异常或内容为空)
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
# 兼容旧格式:删除单独的文件

View File

@@ -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", [])}

View File

@@ -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
)

View File

@@ -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)
# 新的日志结构:分平台的动作日志

View File

@@ -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秒

View File

@@ -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:
# 不指定platformAPI会在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])