step3_generate_inspirations.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. """
  2. Step3: 基于匹配节点生成灵感点
  3. 基于 Step1 的 Top1 匹配结果,以匹配到的人设要素作为锚点,
  4. 让 Agent 分析可以产生哪些灵感点
  5. """
  6. import os
  7. import sys
  8. import json
  9. import asyncio
  10. from pathlib import Path
  11. from agents import Agent, Runner, trace
  12. from agents.tracing.create import custom_span
  13. from lib.my_trace import set_trace_smith as set_trace
  14. from lib.client import get_model
  15. from lib.data_loader import load_persona_data, load_inspiration_list, select_inspiration
  16. # 模型配置
  17. MODEL_NAME = "google/gemini-2.5-pro"
  18. # ========== System Prompt ==========
  19. GENERATE_INSPIRATIONS_PROMPT = """
  20. # 任务
  21. 基于给定的人设要素(锚点),分析和生成可能的灵感点。
  22. ## 输入说明
  23. - **<人设要素></人设要素>**: 作为锚点的人设要素(一级或二级分类)
  24. - **<要素上下文></要素上下文>**: 该要素的上下文信息(所属视角、一级分类等)
  25. - **<参考灵感></参考灵感>**: 一个已匹配到该要素的灵感点示例(可选)
  26. ## 分析方法
  27. ### 核心原则:基于要素特征发散灵感
  28. 从人设要素的核心特征出发,思考可能触发该要素的各种灵感来源。
  29. ### 分析步骤
  30. 1. **理解要素核心**
  31. - 分析人设要素的核心特征
  32. - 理解该要素代表的内容类型或表达方式
  33. - 结合上下文理解要素的定位
  34. 2. **参考已有灵感**(如果提供)
  35. - 分析参考灵感如何触发该要素
  36. - 识别灵感的关键特征
  37. 3. **发散思考**
  38. - 从不同角度思考可能的灵感来源
  39. - 考虑不同的场景、话题、情感、事件等
  40. - 保持与要素核心特征的相关性
  41. 4. **生成灵感点列表**
  42. - 每个灵感点应该简洁明确
  43. - 灵感点之间应有一定的多样性
  44. - 灵感点应该能够触发该人设要素
  45. ---
  46. ## 输出格式(严格JSON)
  47. ```json
  48. {
  49. "要素分析": {
  50. "核心特征": "简要描述该要素的核心特征(1-2句话)",
  51. "适用场景": "该要素适用的内容场景或表达方式"
  52. },
  53. "灵感点列表": [
  54. {
  55. "灵感点": "具体的灵感点描述",
  56. "说明": "为什么这个灵感可能触发该要素"
  57. },
  58. {
  59. "灵感点": "具体的灵感点描述",
  60. "说明": "为什么这个灵感可能触发该要素"
  61. }
  62. ]
  63. }
  64. ```
  65. **输出要求**:
  66. 1. 必须严格按照上述JSON格式输出
  67. 2. 所有字段都必须填写
  68. 3. **要素分析**:包含核心特征和适用场景
  69. 4. **灵感点列表**:生成 5-10 个灵感点
  70. 5. 每个灵感点包含:
  71. - **灵感点**:简洁的灵感描述(一句话)
  72. - **说明**:解释为什么这个灵感可能触发该要素(1-2句话)
  73. 6. 灵感点应该多样化,覆盖不同角度和场景
  74. """.strip()
  75. def create_generate_agent(model_name: str) -> Agent:
  76. """创建灵感生成的 Agent
  77. Args:
  78. model_name: 模型名称
  79. Returns:
  80. Agent 实例
  81. """
  82. agent = Agent(
  83. name="Inspiration Generator Expert",
  84. instructions=GENERATE_INSPIRATIONS_PROMPT,
  85. model=get_model(model_name),
  86. tools=[],
  87. )
  88. return agent
  89. def parse_generate_response(response_content: str) -> dict:
  90. """解析生成响应
  91. Args:
  92. response_content: Agent 返回的响应内容
  93. Returns:
  94. 解析后的字典
  95. """
  96. try:
  97. # 如果响应包含在 markdown 代码块中,提取 JSON 部分
  98. if "```json" in response_content:
  99. json_start = response_content.index("```json") + 7
  100. json_end = response_content.index("```", json_start)
  101. json_text = response_content[json_start:json_end].strip()
  102. elif "```" in response_content:
  103. json_start = response_content.index("```") + 3
  104. json_end = response_content.index("```", json_start)
  105. json_text = response_content[json_start:json_end].strip()
  106. else:
  107. json_text = response_content.strip()
  108. return json.loads(json_text)
  109. except Exception as e:
  110. print(f"解析响应失败: {e}")
  111. return {
  112. "要素分析": {
  113. "核心特征": "解析失败",
  114. "适用场景": "解析失败"
  115. },
  116. "灵感点列表": []
  117. }
  118. def find_step1_file(persona_dir: str, inspiration: str, model_name: str) -> str:
  119. """查找 step1 输出文件
  120. Args:
  121. persona_dir: 人设目录
  122. inspiration: 灵感点名称
  123. model_name: 模型名称
  124. Returns:
  125. step1 文件路径
  126. Raises:
  127. SystemExit: 找不到文件时退出
  128. """
  129. step1_dir = os.path.join(persona_dir, "how", "灵感点", inspiration)
  130. model_name_short = model_name.replace("google/", "").replace("/", "_")
  131. step1_file_pattern = f"*_step1_*_{model_name_short}.json"
  132. step1_files = list(Path(step1_dir).glob(step1_file_pattern))
  133. if not step1_files:
  134. print(f"❌ 找不到 step1 输出文件")
  135. print(f"查找路径: {step1_dir}/{step1_file_pattern}")
  136. sys.exit(1)
  137. return str(step1_files[0])
  138. async def process_step3_generate_inspirations(
  139. step1_top1: dict,
  140. reference_inspiration: str,
  141. current_time: str = None,
  142. log_url: str = None
  143. ) -> dict:
  144. """执行灵感生成分析(核心业务逻辑)
  145. Args:
  146. step1_top1: step1 的 top1 匹配结果
  147. reference_inspiration: 参考灵感(step1 输入的灵感)
  148. current_time: 当前时间戳
  149. log_url: trace URL
  150. Returns:
  151. 生成结果字典
  152. """
  153. # 从 step1 结果中提取信息
  154. business_info = step1_top1.get("业务信息", {})
  155. input_info = step1_top1.get("输入信息", {})
  156. matched_element = business_info.get("匹配要素", "")
  157. element_context = input_info.get("A_Context", "")
  158. print(f"\n开始灵感生成分析")
  159. print(f"锚点要素: {matched_element}")
  160. print(f"参考灵感: {reference_inspiration}")
  161. print(f"模型: {MODEL_NAME}\n")
  162. # 构建任务描述
  163. task_description = f"""## 本次分析任务
  164. <人设要素>
  165. {matched_element}
  166. </人设要素>
  167. <要素上下文>
  168. {element_context}
  169. </要素上下文>
  170. <参考灵感>
  171. {reference_inspiration}
  172. </参考灵感>
  173. 请基于上述人设要素作为锚点,分析并生成可能的灵感点列表,严格按照系统提示中的 JSON 格式输出结果。"""
  174. # 构造消息
  175. messages = [{
  176. "role": "user",
  177. "content": [
  178. {
  179. "type": "input_text",
  180. "text": task_description
  181. }
  182. ]
  183. }]
  184. # 使用 custom_span 追踪生成过程
  185. with custom_span(
  186. name=f"Step3: 灵感生成 - {matched_element}",
  187. data={
  188. "锚点要素": matched_element,
  189. "参考灵感": reference_inspiration,
  190. "模型": MODEL_NAME,
  191. "步骤": "基于要素生成灵感点"
  192. }
  193. ):
  194. # 创建 Agent
  195. agent = create_generate_agent(MODEL_NAME)
  196. # 运行 Agent
  197. result = await Runner.run(agent, input=messages)
  198. # 解析响应
  199. parsed_result = parse_generate_response(result.final_output)
  200. # 构建输出
  201. return {
  202. "元数据": {
  203. "current_time": current_time,
  204. "log_url": log_url,
  205. "model": MODEL_NAME,
  206. "步骤": "Step3: 基于匹配节点生成灵感点"
  207. },
  208. "锚点信息": {
  209. "人设要素": matched_element,
  210. "要素上下文": element_context,
  211. "参考灵感": reference_inspiration
  212. },
  213. "step1_结果": step1_top1,
  214. "生成结果": parsed_result
  215. }
  216. async def main(current_time: str, log_url: str):
  217. """主函数"""
  218. # 解析命令行参数
  219. persona_dir = sys.argv[1] if len(sys.argv) > 1 else "data/阿里多多酱/out/人设_1110"
  220. inspiration_arg = sys.argv[2] if len(sys.argv) > 2 else "0"
  221. print(f"{'=' * 80}")
  222. print(f"Step3: 基于匹配节点生成灵感点")
  223. print(f"{'=' * 80}")
  224. print(f"人设目录: {persona_dir}")
  225. print(f"灵感参数: {inspiration_arg}")
  226. # 加载数据
  227. persona_data = load_persona_data(persona_dir)
  228. inspiration_list = load_inspiration_list(persona_dir)
  229. # 选择灵感
  230. try:
  231. inspiration_index = int(inspiration_arg)
  232. if 0 <= inspiration_index < len(inspiration_list):
  233. test_inspiration = inspiration_list[inspiration_index]
  234. print(f"使用灵感[{inspiration_index}]: {test_inspiration}")
  235. else:
  236. print(f"❌ 灵感索引超出范围: {inspiration_index}")
  237. sys.exit(1)
  238. except ValueError:
  239. if inspiration_arg in inspiration_list:
  240. test_inspiration = inspiration_arg
  241. print(f"使用灵感: {test_inspiration}")
  242. else:
  243. print(f"❌ 找不到灵感: {inspiration_arg}")
  244. sys.exit(1)
  245. # 查找并加载 step1 结果
  246. step1_file = find_step1_file(persona_dir, test_inspiration, MODEL_NAME)
  247. step1_filename = os.path.basename(step1_file)
  248. step1_basename = os.path.splitext(step1_filename)[0]
  249. print(f"Step1 输入文件: {step1_file}")
  250. with open(step1_file, 'r', encoding='utf-8') as f:
  251. step1_data = json.load(f)
  252. actual_inspiration = step1_data.get("灵感", "")
  253. step1_results = step1_data.get("匹配结果列表", [])
  254. if not step1_results:
  255. print("❌ step1 结果为空")
  256. sys.exit(1)
  257. print(f"灵感: {actual_inspiration}")
  258. # 默认处理 top1
  259. result_index = 0
  260. selected_result = step1_results[result_index]
  261. print(f"处理第 {result_index + 1} 个匹配结果(Top{result_index + 1})\n")
  262. # 执行核心业务逻辑
  263. output = await process_step3_generate_inspirations(
  264. step1_top1=selected_result,
  265. reference_inspiration=actual_inspiration,
  266. current_time=current_time,
  267. log_url=log_url
  268. )
  269. # 在元数据中添加 step1 匹配索引
  270. output["元数据"]["step1_匹配索引"] = result_index + 1
  271. # 保存结果
  272. output_dir = os.path.join(persona_dir, "how", "灵感点", test_inspiration)
  273. model_name_short = MODEL_NAME.replace("google/", "").replace("/", "_")
  274. # 提取 step1 的范围标识(all 或 top10 等)
  275. scope_prefix = step1_basename.split("_")[0]
  276. output_filename = f"{scope_prefix}_step3_top{result_index + 1}_生成灵感_{model_name_short}.json"
  277. os.makedirs(output_dir, exist_ok=True)
  278. output_file = os.path.join(output_dir, output_filename)
  279. with open(output_file, 'w', encoding='utf-8') as f:
  280. json.dump(output, f, ensure_ascii=False, indent=2)
  281. # 输出生成的灵感点预览
  282. generated = output.get("生成结果", {})
  283. inspirations = generated.get("灵感点列表", [])
  284. print(f"\n{'=' * 80}")
  285. print(f"生成了 {len(inspirations)} 个灵感点:")
  286. print(f"{'=' * 80}")
  287. for i, item in enumerate(inspirations[:5], 1):
  288. print(f"{i}. {item.get('灵感点', '')}")
  289. if len(inspirations) > 5:
  290. print(f"... 还有 {len(inspirations) - 5} 个")
  291. print(f"\n完成!结果已保存到: {output_file}")
  292. if log_url:
  293. print(f"Trace: {log_url}\n")
  294. if __name__ == "__main__":
  295. # 设置 trace
  296. current_time, log_url = set_trace()
  297. # 使用 trace 上下文包裹整个执行流程
  298. with trace("Step3: 生成灵感点"):
  299. asyncio.run(main(current_time, log_url))