how_decode_v9_point_dependency.py 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946
  1. """
  2. HOW 解构 V9 - 点依赖关系处理
  3. V9 新特性:
  4. - 正确处理点的依赖关系:
  5. - 灵感点 ↔ 目的点(双向互推)
  6. - 灵感点,目的点 → 关键点(单向推导)
  7. - 同类型点不能互推
  8. - 提取所有三类点(即使只分析灵感点)
  9. - 根据点类型动态提供可推导来源
  10. - 支持机器可模拟的推测路径
  11. 输入输出结构:
  12. - 帖子信息:examples_new/{账号}/作者历史帖子/{帖子ID}.json
  13. - what解构结果:examples_new/{账号}/output/{帖子ID}_{运行日期}_{运行时间}.json
  14. - 输出结果:examples_new/{账号}/how_output/{帖子ID}_{运行日期}_{运行时间}.json
  15. """
  16. import asyncio
  17. import json
  18. import os
  19. from typing import Dict, List
  20. from datetime import datetime
  21. from agents import Agent, Runner, trace
  22. from agents.tracing.create import custom_span
  23. from lib.my_trace import set_trace
  24. from lib.utils import read_json
  25. from lib.client import get_model
  26. MODEL_NAME = "google/gemini-2.5-flash"
  27. # ============================================================================
  28. # 多模态消息构建
  29. # ============================================================================
  30. def build_post_multimodal_content(post_data: Dict) -> List[Dict]:
  31. """构建单个帖子的多模态内容"""
  32. images = post_data.get('images', [])
  33. image_count = len(images)
  34. content = []
  35. if images:
  36. content.append({
  37. "type": "input_text",
  38. "text": f"[帖子图集:{image_count}张图片,第一张是封面]"
  39. })
  40. for img_url in images:
  41. content.append({
  42. "type": "input_image",
  43. "detail": "auto",
  44. "image_url": img_url
  45. })
  46. post_info = f"""
  47. <标题>
  48. {post_data.get('title', '')}
  49. </标题>
  50. <正文>
  51. {post_data.get('body_text', '')}
  52. </正文>
  53. <发布时间>
  54. {post_data.get('publish_time', '')}
  55. </发布时间>
  56. <互动数据>
  57. 点赞: {post_data.get('like_count', 0)}, 收藏: {post_data.get('collect_count', 0)}
  58. </互动数据>
  59. """
  60. content.append({
  61. "type": "input_text",
  62. "text": post_info.strip()
  63. })
  64. return content
  65. # ============================================================================
  66. # Step 1: 来源类型初筛 Agent
  67. # ============================================================================
  68. STEP1_PROMPT = """
  69. 你是一个创作溯源分析专家。
  70. 你的任务:对给定的灵感点,**广召回**所有可能的来源,并给出初步推测路径。
  71. ## 关键要求
  72. 1. **广召回**:对每个来源类型(A/B/C/D)都要分析,即使可能性很低也要列出
  73. 2. **可能性评估**:给出高/中/低的评级
  74. 3. **初步推测路径**:用3-5步描述从原始点到灵感点的推导过程
  75. ## 推测路径的要求
  76. ### 原始点
  77. - **A类来源**:原始点是上下文中"可推导来源(A类来源)"提供的其他类型的点
  78. - **B类来源**:原始点是博主历史帖子数据
  79. - **C类来源**:原始点是外部平台信息(小红书/微博/知乎等)
  80. - **D类来源**:原始点是混合的(多种来源组合)
  81. ### 可用操作类型
  82. 只能使用这三种操作:
  83. 1. **从内搜**:搜索/浏览博主历史帖子、回忆过往经验
  84. 2. **从外搜**:搜索外部平台、浏览热点话题、查询知识
  85. 3. **信息处理**:观察、对比、提取、归纳、联想、组合、类比
  86. ### 步骤格式
  87. 每步必须明确:操作类型 + 具体做什么 + 输出什么
  88. 格式:`步骤X [操作类型]: 具体操作 → 输出结果`
  89. ### 注意事项
  90. - **不能跳步骤**:关键词、概念的来源必须说清楚
  91. - **不能有黑盒**:不能用"突然想到"、"产生灵感"等说法
  92. - **数字世界操作**:只能操作数字化的数据,不能有物理世界交互
  93. - **合适的粒度**:3-5步说清楚,不要太细(不说底层实现),不要太粗(不能黑盒)
  94. ## 来源类型分类
  95. **A. 从其他点推导**
  96. - 从上下文中"可推导来源(A类来源)"部分提供的点推导
  97. - 根据点依赖关系:
  98. - 灵感点可从目的点推导
  99. - 目的点可从灵感点推导
  100. - 关键点可从灵感点和目的点推导
  101. - 如果上下文说明"没有可用于推导的其他类型的点",则A类可能性为"无"
  102. **B. 从博主账号历史**
  103. - 从历史帖子中的内容、风格、经验推导
  104. **C. 从外部信息**
  105. - 从平台热点、流行梗、社会现象推导
  106. **D. 混合输入**
  107. - 由多个来源融合创新
  108. ## 输出格式
  109. **注意**:
  110. - 如果上下文说明没有可推导来源,则A类来源可能性为"无",理由说明原因
  111. - 对所有A/B/C/D来源都要分析,即使可能性很低
  112. ```json
  113. {
  114. "可能的来源": {
  115. "A_其他点推导": {
  116. "可能性": "高/中/低/无",
  117. "理由": "为什么这个来源是可能的(1-2句话),如果可能性为'无',说明为什么没有可推导来源",
  118. "初步推测路径": [
  119. "步骤1 [操作类型]: 具体操作 → 输出结果",
  120. "步骤2 [操作类型]: 具体操作 → 输出结果",
  121. "步骤3 [操作类型]: 具体操作 → 输出结果"
  122. ]
  123. },
  124. "B_博主历史": {
  125. "可能性": "高/中/低",
  126. "理由": "为什么这个来源是可能的(1-2句话)",
  127. "初步推测路径": [
  128. "原始点: 博主历史帖子数据",
  129. "步骤1 [从内搜]: 具体操作 → 输出结果",
  130. "步骤2 [信息处理]: 具体操作 → 输出结果",
  131. "步骤3 [信息处理]: 具体操作 → 输出结果"
  132. ]
  133. },
  134. "C_外部信息": {
  135. "可能性": "高/中/低",
  136. "理由": "为什么这个来源是可能的(1-2句话)",
  137. "初步推测路径": [
  138. "原始点: 外部平台信息",
  139. "步骤1 [从外搜]: 具体操作 → 输出结果",
  140. "步骤2 [信息处理]: 具体操作 → 输出结果",
  141. "步骤3 [信息处理]: 具体操作 → 输出结果"
  142. ]
  143. },
  144. "D_混合输入": {
  145. "可能性": "高/中/低",
  146. "理由": "可能混合了哪些来源",
  147. "初步推测路径": [
  148. "原始点: 混合(历史数据+外部信息)",
  149. "步骤1 [操作类型]: 具体操作 → 输出结果",
  150. "步骤2 [操作类型]: 具体操作 → 输出结果",
  151. "步骤3 [操作类型]: 具体操作 → 输出结果"
  152. ]
  153. }
  154. }
  155. }
  156. ```
  157. """
  158. step1_agent = Agent(
  159. name="Source Type Filter",
  160. instructions=STEP1_PROMPT,
  161. model=get_model(MODEL_NAME),
  162. tools=[],
  163. )
  164. # ============================================================================
  165. # Step 2: 深入分析 Agent
  166. # ============================================================================
  167. STEP2_B_PROMPT = """
  168. 你是一个创作溯源分析专家。
  169. 你的任务:解构从博主历史如何一步步得到这个灵感点。
  170. ## 核心要求:明确标识 输入 → 处理 → 输出
  171. ### 输入
  172. - 具体是博主历史中的哪个/哪些帖子?
  173. - 这些帖子里有什么内容?(图片/文字/主题)
  174. ### 处理过程(一步步推导)
  175. - 步骤1:创作者观察/接收到什么信息?
  176. - 步骤2:产生了什么联想/思考?
  177. - 步骤3:如何转化为具体的灵感?
  178. - (可以有更多步骤)
  179. ### 输出
  180. - 最终得到的灵感点
  181. ## 输出要求
  182. 输出JSON格式:
  183. ```json
  184. {
  185. "输入_博主历史帖子": {
  186. "相关帖子": [
  187. {
  188. "帖子序号": "历史帖子X/总数",
  189. "标题": "...",
  190. "关键内容": "具体是图片中什么/文字里什么"
  191. }
  192. ]
  193. },
  194. "处理_从输入到灵感的推导": {
  195. "步骤1": {
  196. "动作": "观察/接收",
  197. "内容": "创作者看到/注意到了什么"
  198. },
  199. "步骤2": {
  200. "动作": "联想/思考",
  201. "内容": "产生了什么想法/联系"
  202. },
  203. "步骤3": {
  204. "动作": "转化/形成",
  205. "内容": "如何变成具体的灵感"
  206. }
  207. },
  208. "输出_最终灵感": "灵感点名称"
  209. }
  210. ```
  211. """
  212. STEP2_C_PROMPT = """
  213. 你是一个创作溯源分析专家。
  214. 你的任务:解构从外部信息如何一步步得到这个灵感点。
  215. ## 核心要求:明确标识 输入 → 处理 → 输出
  216. ### 输入
  217. - 具体是什么外部信息?(热点话题/流行梗/社会现象)
  218. - 这些信息的具体内容是什么?
  219. ### 处理过程(一步步推导)
  220. - 步骤1:创作者接触到什么外部信息?
  221. - 步骤2:如何理解/解读这个信息?
  222. - 步骤3:如何与自己的内容结合?
  223. - 步骤4:如何转化为具体的灵感?
  224. - (可以有更多步骤)
  225. ### 输出
  226. - 最终得到的灵感点
  227. ## 输出要求
  228. 输出JSON格式:
  229. ```json
  230. {
  231. "输入_外部信息": {
  232. "信息类型": "平台热点/流行梗/社会现象",
  233. "具体内容": "是什么话题/梗/现象",
  234. "信息来源": "在哪里看到/了解到"
  235. },
  236. "处理_从输入到灵感的推导": {
  237. "步骤1": {
  238. "动作": "接触/了解",
  239. "内容": "创作者看到/听到了什么"
  240. },
  241. "步骤2": {
  242. "动作": "理解/解读",
  243. "内容": "如何理解这个信息"
  244. },
  245. "步骤3": {
  246. "动作": "结合/融合",
  247. "内容": "如何与自己的内容结合"
  248. },
  249. "步骤4": {
  250. "动作": "转化/形成",
  251. "内容": "如何变成具体的灵感"
  252. }
  253. },
  254. "输出_最终灵感": "灵感点名称"
  255. }
  256. ```
  257. """
  258. step2_b_agent = Agent(
  259. name="Blogger History Analyzer",
  260. instructions=STEP2_B_PROMPT,
  261. model=get_model(MODEL_NAME),
  262. tools=[],
  263. )
  264. step2_c_agent = Agent(
  265. name="External Info Analyzer",
  266. instructions=STEP2_C_PROMPT,
  267. model=get_model(MODEL_NAME),
  268. tools=[],
  269. )
  270. # ============================================================================
  271. # Step 3: 路径验证 Agent
  272. # ============================================================================
  273. STEP3_PROMPT = """
  274. 你是一个创作溯源分析专家。
  275. 你的任务:对每个来源路径进行验证和评分。
  276. ## 验证维度
  277. 1. **支持证据**(3-5条具体证据)
  278. 2. **反驳点**(如果有不支持的因素)
  279. 3. **可能性评分**(1-10分,基于证据强度)
  280. ## 输出要求
  281. 输出JSON格式:
  282. ```json
  283. {
  284. "来源类型": "B",
  285. "支持证据": [
  286. "证据1: ...",
  287. "证据2: ...",
  288. "证据3: ..."
  289. ],
  290. "反驳点": [
  291. "反驳1: ..."
  292. ],
  293. "可能性评分": 8,
  294. "评分说明": "为什么给这个分数"
  295. }
  296. ```
  297. """
  298. step3_agent = Agent(
  299. name="Path Validator",
  300. instructions=STEP3_PROMPT,
  301. model=get_model(MODEL_NAME),
  302. tools=[],
  303. )
  304. # ============================================================================
  305. # Step 4: 综合结论 Agent
  306. # ============================================================================
  307. STEP4_PROMPT = """
  308. 你是一个创作溯源分析专家。
  309. 你的任务:基于前面的分析,给出综合结论。
  310. ## 输出要求
  311. 输出JSON格式:
  312. ```json
  313. {
  314. "最可能的来源路径": "...",
  315. "各来源的占比": {
  316. "B_博主历史": "60%",
  317. "C_外部信息": "40%"
  318. },
  319. "完整推导路径": "从...到...最终形成...",
  320. "关键转折点": "...",
  321. "整体置信度": 85
  322. }
  323. ```
  324. """
  325. step4_agent = Agent(
  326. name="Conclusion Synthesizer",
  327. instructions=STEP4_PROMPT,
  328. model=get_model(MODEL_NAME),
  329. tools=[],
  330. )
  331. # ============================================================================
  332. # 从新格式的what结果中提取所有点
  333. # ============================================================================
  334. def extract_all_points_v9(what_result: Dict) -> Dict[str, List[Dict]]:
  335. """
  336. 从新格式的 what 解构结果中提取所有三类点
  337. V9版本:从选题理解.explicit_elements提取所有三类点,只保留名称
  338. - 描述、在帖子中的体现等都是what的中间过程,会干扰how的解构
  339. 返回格式:
  340. {
  341. '灵感点': [{'id': '灵感点1', 'name': 'xxx'}, ...],
  342. '目的点': [{'id': '目的点1', 'name': 'xxx'}, ...],
  343. '关键点': [{'id': '关键点1', 'name': 'xxx'}, ...]
  344. }
  345. """
  346. all_points = {
  347. '灵感点': [],
  348. '目的点': [],
  349. '关键点': []
  350. }
  351. # 从选题理解.explicit_elements提取
  352. explicit_elements = what_result.get('选题理解', {}).get('explicit_elements', {})
  353. # 提取灵感点列表(数组)
  354. inspiration_list = explicit_elements.get('灵感点列表', [])
  355. for idx, name in enumerate(inspiration_list, 1):
  356. all_points['灵感点'].append({
  357. 'type': '灵感点',
  358. 'id': f'灵感点{idx}',
  359. 'name': name
  360. })
  361. # 提取目的点(单个字符串)
  362. purpose_name = explicit_elements.get('目的点', '')
  363. if purpose_name:
  364. all_points['目的点'].append({
  365. 'type': '目的点',
  366. 'id': '目的点1',
  367. 'name': purpose_name
  368. })
  369. # 提取关键点列表(数组)
  370. key_list = explicit_elements.get('关键点列表', [])
  371. for idx, name in enumerate(key_list, 1):
  372. all_points['关键点'].append({
  373. 'type': '关键点',
  374. 'id': f'关键点{idx}',
  375. 'name': name
  376. })
  377. return all_points
  378. # ============================================================================
  379. # 加载博主历史数据
  380. # ============================================================================
  381. def load_blogger_history_v8(history_dir: str, target_post_id: str) -> Dict:
  382. """加载博主历史数据 - V8版本"""
  383. history_posts = []
  384. for filename in os.listdir(history_dir):
  385. if filename.endswith('.json'):
  386. post_id = filename.replace('.json', '')
  387. # 只过滤掉当前帖子本身(按ID)
  388. if post_id != target_post_id:
  389. filepath = os.path.join(history_dir, filename)
  390. with open(filepath, 'r', encoding='utf-8') as f:
  391. data = json.load(f)
  392. history_posts.append(data)
  393. # 按时间排序
  394. history_posts.sort(key=lambda x: x.get('publish_timestamp', 0))
  395. return {
  396. "历史帖子数": len(history_posts),
  397. "历史帖子列表": history_posts
  398. }
  399. # ============================================================================
  400. # 拆步骤分析(复用之前的逻辑)
  401. # ============================================================================
  402. async def analyze_point_step_by_step(
  403. point: Dict,
  404. all_points: Dict[str, List[Dict]],
  405. blogger_history: Dict,
  406. account_name: str
  407. ):
  408. """
  409. 拆步骤分析单个点
  410. V9版本:根据点的依赖关系动态提供可推导来源
  411. - 灵感点:可从目的点推导
  412. - 目的点:可从灵感点推导
  413. - 关键点:可从灵感点和目的点推导
  414. """
  415. print(f"\n{'='*80}")
  416. print(f"拆步骤溯源分析: {point['id']} - {point['name']}")
  417. print(f"{'='*80}")
  418. # ========== 根据点类型确定可推导来源 ==========
  419. point_type = point['type']
  420. derivable_sources = {}
  421. if point_type == '灵感点':
  422. # 灵感点可从目的点推导
  423. derivable_sources['目的点'] = all_points.get('目的点', [])
  424. elif point_type == '目的点':
  425. # 目的点可从灵感点推导
  426. derivable_sources['灵感点'] = all_points.get('灵感点', [])
  427. elif point_type == '关键点':
  428. # 关键点可从灵感点和目的点推导
  429. derivable_sources['灵感点'] = all_points.get('灵感点', [])
  430. derivable_sources['目的点'] = all_points.get('目的点', [])
  431. # ========== 准备基础上下文 ==========
  432. content = []
  433. # 待溯源的点信息(只有名称,避免what中间过程的干扰)
  434. content.append({
  435. "type": "input_text",
  436. "text": f"""
  437. # 待溯源的{point_type}
  438. **名称**: {point['name']}
  439. **说明**: 这是从what解构中提取的{point_type}名称,请分析这个{point_type}是如何产生的。
  440. """
  441. })
  442. # 可推导来源的点信息(A类来源)
  443. if derivable_sources:
  444. sources_info = []
  445. for source_type, source_points in derivable_sources.items():
  446. if source_points:
  447. sources_info.append(f"\n## {source_type}")
  448. for p in source_points:
  449. sources_info.append(f"- {p['id']}: {p['name']}")
  450. if sources_info:
  451. content.append({
  452. "type": "input_text",
  453. "text": f"""
  454. ---
  455. # 可推导来源(A类来源)
  456. 根据点依赖关系,当前{point_type}可以从以下类型的点推导:
  457. {''.join(sources_info)}
  458. **注意**: 同类型的点不能互相推导。
  459. """
  460. })
  461. else:
  462. # 如果没有可推导来源(例如灵感点且没有目的点)
  463. content.append({
  464. "type": "input_text",
  465. "text": f"""
  466. ---
  467. # 可推导来源(A类来源)
  468. 当前帖子中没有可用于推导{point_type}的其他类型的点。
  469. 因此A类来源可能性为:无
  470. """
  471. })
  472. # 博主历史信息(多模态)
  473. history_posts = blogger_history.get('历史帖子列表', [])
  474. content.append({
  475. "type": "input_text",
  476. "text": f"""
  477. ---
  478. # 博主历史信息(可能的输入来源B)
  479. **账号名称**: {account_name}
  480. **历史帖子数量**: {len(history_posts)} 个
  481. 以下是博主的所有历史帖子(按发布时间排序):
  482. """
  483. })
  484. # 为每个历史帖子构建多模态内容
  485. for idx, hist_post in enumerate(history_posts, 1):
  486. content.append({
  487. "type": "input_text",
  488. "text": f"\n## 历史帖子 {idx}/{len(history_posts)}\n"
  489. })
  490. hist_post_content = build_post_multimodal_content(hist_post)
  491. content.extend(hist_post_content)
  492. # ========== Step 1: 来源类型初筛 ==========
  493. print(f"\n{'='*60}")
  494. print("Step 1: 来源类型初筛")
  495. print(f"{'='*60}")
  496. step1_messages = [{
  497. "role": "user",
  498. "content": content + [{
  499. "type": "input_text",
  500. "text": "\n---\n\n请根据以上信息,判断这个点最可能来自哪些来源类型(只选1-3个最可能的)。"
  501. }]
  502. }]
  503. # 使用custom_span添加更多元数据
  504. with custom_span(
  505. name=f"Step1: {point['id']}",
  506. data={
  507. "point_id": point['id'],
  508. "point_name": point['name'],
  509. "point_type": point_type,
  510. "step": "来源类型初筛",
  511. "可推导来源数": sum(len(v) for v in derivable_sources.values())
  512. }
  513. ):
  514. result1 = await Runner.run(step1_agent, input=step1_messages)
  515. print(f"\n✅ Step 1 结果:\n{result1.final_output[:300]}...\n")
  516. step1_result = extract_json(result1.final_output)
  517. # 暂时只返回 Step 1 结果,不继续后面的步骤
  518. return {
  519. "灵感点": point['name'],
  520. "step1_来源可能性分析": step1_result
  521. }
  522. # TODO: 后续步骤暂时注释掉,先把 Step 1 做扎实
  523. # selected_types = step1_result.get('选择的来源类型', [])
  524. #
  525. # # ========== Step 2: 解构每个来源(输入→处理→输出) ==========
  526. # print(f"\n{'='*60}")
  527. # print(f"Step 2: 解构每个来源 - 输入→处理→输出 (针对类型: {', '.join(selected_types)})")
  528. # print(f"{'='*60}")
  529. #
  530. # step2_results = {}
  531. #
  532. # for source_type in selected_types:
  533. # if source_type == "B":
  534. # print(f"\n▶ Step 2.B: 解构从博主历史如何得到灵感")
  535. # step2_messages = [{
  536. # "role": "user",
  537. # "content": content + [{
  538. # "type": "input_text",
  539. # "text": f"""
  540. # ---
  541. #
  542. # Step 1 已确定:这个灵感点可能来自"博主账号历史"
  543. #
  544. # 请解构:从博主历史如何一步步得到这个灵感点?
  545. #
  546. # 要求明确标识:
  547. # - 输入:具体是哪个历史帖子?里面有什么内容?
  548. # - 处理:如何从输入一步步推导到灵感?(步骤1、2、3...)
  549. # - 输出:最终得到的灵感点
  550. # """
  551. # }]
  552. # }]
  553. # result2b = await Runner.run(step2_b_agent, input=step2_messages)
  554. # print(f"\n✅ Step 2.B 结果:\n{result2b.final_output[:300]}...\n")
  555. # step2_results['B'] = extract_json(result2b.final_output)
  556. #
  557. # elif source_type == "C":
  558. # print(f"\n▶ Step 2.C: 解构从外部信息如何得到灵感")
  559. # step2_messages = [{
  560. # "role": "user",
  561. # "content": content + [{
  562. # "type": "input_text",
  563. # "text": f"""
  564. # ---
  565. #
  566. # Step 1 已确定:这个灵感点可能来自"外部信息"
  567. #
  568. # 请解构:从外部信息如何一步步得到这个灵感点?
  569. #
  570. # 要求明确标识:
  571. # - 输入:具体是什么外部信息?(热点/梗/现象)
  572. # - 处理:如何从输入一步步推导到灵感?(步骤1、2、3、4...)
  573. # - 输出:最终得到的灵感点
  574. # """
  575. # }]
  576. # }]
  577. # result2c = await Runner.run(step2_c_agent, input=step2_messages)
  578. # print(f"\n✅ Step 2.C 结果:\n{result2c.final_output[:300]}...\n")
  579. # step2_results['C'] = extract_json(result2c.final_output)
  580. #
  581. # # ========== Step 3: 路径验证 ==========
  582. # print(f"\n{'='*60}")
  583. # print("Step 3: 路径验证")
  584. # print(f"{'='*60}")
  585. #
  586. # step3_results = []
  587. #
  588. # for source_type in selected_types:
  589. # print(f"\n▶ Step 3.{source_type}: 验证来源路径")
  590. #
  591. # step2_analysis = json.dumps(step2_results.get(source_type, {}), ensure_ascii=False, indent=2)
  592. #
  593. # step3_messages = [{
  594. # "role": "user",
  595. # "content": [{
  596. # "type": "input_text",
  597. # "text": f"""
  598. # 基于前面的分析:
  599. #
  600. # Step 1: 初筛选择了来源类型 {source_type}
  601. # Step 2: 深入分析结果:
  602. # {step2_analysis}
  603. #
  604. # 请对这个来源路径进行验证:列出支持证据、反驳点、给出评分。
  605. # """
  606. # }]
  607. # }]
  608. #
  609. # result3 = await Runner.run(step3_agent, input=step3_messages)
  610. # print(f"\n✅ Step 3.{source_type} 结果:\n{result3.final_output[:300]}...\n")
  611. # step3_results.append(extract_json(result3.final_output))
  612. #
  613. # # ========== Step 4: 综合结论 ==========
  614. # print(f"\n{'='*60}")
  615. # print("Step 4: 综合结论")
  616. # print(f"{'='*60}")
  617. #
  618. # all_analysis = {
  619. # "step1": step1_result,
  620. # "step2": step2_results,
  621. # "step3": step3_results
  622. # }
  623. #
  624. # step4_messages = [{
  625. # "role": "user",
  626. # "content": [{
  627. # "type": "input_text",
  628. # "text": f"""
  629. # 基于前面所有步骤的分析:
  630. #
  631. # {json.dumps(all_analysis, ensure_ascii=False, indent=2)}
  632. #
  633. # 请给出综合结论:最可能的来源路径、各来源占比、完整推导过程。
  634. # """
  635. # }]
  636. # }]
  637. #
  638. # result4 = await Runner.run(step4_agent, input=step4_messages)
  639. # print(f"\n✅ Step 4 结果:\n{result4.final_output[:300]}...\n")
  640. #
  641. # final_result = {
  642. # "灵感点": point['name'],
  643. # "step1_来源类型初筛": step1_result,
  644. # "step2_深入分析": step2_results,
  645. # "step3_路径验证": step3_results,
  646. # "step4_综合结论": extract_json(result4.final_output)
  647. # }
  648. #
  649. # return final_result
  650. def extract_json(text: str) -> Dict:
  651. """从文本中提取JSON"""
  652. try:
  653. if "```json" in text:
  654. json_start = text.index("```json") + 7
  655. json_end = text.index("```", json_start)
  656. json_text = text[json_start:json_end].strip()
  657. elif "```" in text:
  658. json_start = text.index("```") + 3
  659. json_end = text.index("```", json_start)
  660. json_text = text[json_start:json_end].strip()
  661. else:
  662. json_text = text
  663. return json.loads(json_text)
  664. except:
  665. return {"原始输出": text}
  666. # ============================================================================
  667. # Main
  668. # ============================================================================
  669. async def main(current_time, log_url):
  670. import sys
  671. # 默认测试文件
  672. DEFAULT_TEST_FILE = "examples_new/阿里多多酱/output/685b593800000000120141d3_20251104_111017.json"
  673. # 参数解析
  674. if len(sys.argv) < 2:
  675. print(f"未提供参数,使用默认测试文件: {DEFAULT_TEST_FILE}")
  676. what_result_file = DEFAULT_TEST_FILE
  677. else:
  678. what_result_file = sys.argv[1]
  679. # 解析文件名:{帖子ID}_{运行日期}_{运行时间}.json
  680. filename = os.path.basename(what_result_file)
  681. filename_without_ext = filename.replace('.json', '')
  682. parts = filename_without_ext.split('_')
  683. if len(parts) < 3:
  684. print(f"❌ 文件名格式不正确: {filename}")
  685. print("期望格式: {帖子ID}_{运行日期}_{运行时间}.json")
  686. sys.exit(1)
  687. post_id = parts[0]
  688. run_date = parts[1]
  689. run_time = parts[2]
  690. print("="*80)
  691. print("HOW 解构 V9 - 点依赖关系处理")
  692. print("="*80)
  693. print(f"\n目标帖子ID: {post_id}")
  694. print(f"运行日期: {run_date}")
  695. print(f"运行时间: {run_time}")
  696. # 读取 what 解构结果
  697. what_result = read_json(what_result_file)
  698. if not what_result:
  699. print(f"❌ 无法读取文件: {what_result_file}")
  700. sys.exit(1)
  701. # 从输入路径中提取账号名称
  702. # 路径格式: examples_new/{账号名}/output/{帖子ID}_{运行日期}_{运行时间}.json
  703. path_parts = what_result_file.split('/')
  704. if len(path_parts) >= 3 and path_parts[0] == 'examples_new':
  705. author_name = path_parts[1]
  706. else:
  707. print(f"❌ 无法从路径中提取账号名称: {what_result_file}")
  708. sys.exit(1)
  709. # 构建路径
  710. base_dir = f"examples_new/{author_name}"
  711. history_dir = f"{base_dir}/作者历史帖子"
  712. # 读取目标帖子信息(用于获取账号名称)
  713. target_post_file = f"{history_dir}/{post_id}.json"
  714. target_post = read_json(target_post_file)
  715. account_name = target_post.get('channel_account_name', author_name) if target_post else author_name
  716. # 加载博主历史数据
  717. print(f"\n加载博主历史数据...")
  718. blogger_history = load_blogger_history_v8(history_dir, post_id)
  719. print(f"✓ 已加载 {blogger_history['历史帖子数']} 个历史帖子")
  720. # 提取所有三类点(即使只分析部分类型,也需要提取所有点用于依赖关系判断)
  721. all_points = extract_all_points_v9(what_result)
  722. # 配置:决定分析哪些类型的点
  723. analyze_types = ['灵感点', '目的点', '关键点'] # 分析所有三类点
  724. # 统计提取的点
  725. print(f"\n从 WHAT 解构中提取的点:")
  726. for point_type in ['灵感点', '目的点', '关键点']:
  727. count = len(all_points[point_type])
  728. print(f" - {point_type}: {count} 个")
  729. # 显示要分析的点
  730. print(f"\n本次分析范围: {', '.join(analyze_types)}")
  731. points_to_analyze = []
  732. for point_type in analyze_types:
  733. for point in all_points[point_type]:
  734. points_to_analyze.append(point)
  735. print(f" - {point['id']}: {point['name']}")
  736. # 对每个点进行溯源分析
  737. source_analysis_results = []
  738. for idx, point in enumerate(points_to_analyze, 1):
  739. # 为每个点创建一个自定义span
  740. point_name_short = point['name'][:30] + "..." if len(point['name']) > 30 else point['name']
  741. with custom_span(
  742. name=f"{point['id']}: {point_name_short}",
  743. data={
  744. "point_index": f"{idx}/{len(points_to_analyze)}",
  745. "point_id": point['id'],
  746. "point_type": point['type'],
  747. "point_name": point['name'],
  748. "analysis_stage": "HOW解构溯源"
  749. }
  750. ):
  751. result = await analyze_point_step_by_step(
  752. point, all_points, blogger_history, account_name
  753. )
  754. source_analysis_results.append(result)
  755. # 添加延迟避免API限流
  756. await asyncio.sleep(2)
  757. # 保存结果
  758. now = datetime.now()
  759. output_filename = f"{post_id}_{now.strftime('%Y%m%d')}_{now.strftime('%H%M%S')}.json"
  760. output_dir = f"{base_dir}/how_output"
  761. os.makedirs(output_dir, exist_ok=True)
  762. output_file = f"{output_dir}/{output_filename}"
  763. # 统计各类型点的数量
  764. points_stats = {
  765. point_type: len(all_points[point_type])
  766. for point_type in ['灵感点', '目的点', '关键点']
  767. }
  768. final_result = {
  769. "how_解构_V9": {
  770. "版本说明": "V9 - 点依赖关系处理,支持机器可模拟的推测路径",
  771. "目标帖子ID": post_id,
  772. "运行时间": now.strftime('%Y-%m-%d %H:%M:%S'),
  773. "log_url": log_url,
  774. "历史数据统计": {
  775. "历史帖子数": blogger_history['历史帖子数'],
  776. "数据格式": "多模态(图片 + 结构化文本)"
  777. },
  778. "点统计": points_stats,
  779. "分析范围": ', '.join(analyze_types),
  780. "分析数量": len(points_to_analyze),
  781. "点依赖关系": {
  782. "说明": "灵感点 ↔ 目的点(双向互推),灵感点,目的点 → 关键点(单向推导)",
  783. "规则": "同类型的点不能互相推导"
  784. },
  785. "溯源分析结果": source_analysis_results
  786. }
  787. }
  788. with open(output_file, 'w', encoding='utf-8') as f:
  789. json.dump(final_result, f, ensure_ascii=False, indent=2)
  790. print("\n" + "="*80)
  791. print(f"✓ V9 溯源分析完成!结果已保存到:")
  792. print(f" {output_file}")
  793. print("="*80)
  794. if __name__ == "__main__":
  795. current_time, log_url = set_trace()
  796. with trace("how decode v9"):
  797. asyncio.run(main(current_time, log_url))