|
|
@@ -1,1846 +0,0 @@
|
|
|
-# What解构业务技术设计文档 v2.0
|
|
|
-
|
|
|
-> **文档目标**: 为开发者提供清晰、可执行的技术实现指导
|
|
|
->
|
|
|
-> **设计原则**: 每个决策都有明确的PRD依据和技术理由
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 目录
|
|
|
-1. [PRD目标解析](#1-prd目标解析)
|
|
|
-2. [功能需求拆解](#2-功能需求拆解)
|
|
|
-3. [工作流 vs 组件职责划分](#3-工作流-vs-组件职责划分)
|
|
|
-4. [工作流设计](#4-工作流设计)
|
|
|
-5. [组件设计](#5-组件设计)
|
|
|
-6. [数据结构设计](#6-数据结构设计)
|
|
|
-7. [实现路线图](#7-实现路线图)
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 1. PRD目标解析
|
|
|
-
|
|
|
-### 1.1 核心目标是什么?
|
|
|
-
|
|
|
-**PRD原文** (1. 需求目标):
|
|
|
-> 针对给定的小红书多模态内容(图文、视频、音频),从消费者视角进行充分必要的What要素逆向解构,通过层级化递归深入分析,识别和提取内容中所有构成成分"what"。
|
|
|
-
|
|
|
-**核心目标拆解**:
|
|
|
-
|
|
|
-| 维度 | 具体含义 | PRD依据 |
|
|
|
-|------|---------|---------|
|
|
|
-| **输入对象** | 小红书多模态内容(图+文+视频+音频) | 2.1.1节 |
|
|
|
-| **视角** | 从消费者视角分析 | 1. 需求目标 |
|
|
|
-| **方法** | 层级化递归深入分析 | 1. 需求目标、2.2.3节 |
|
|
|
-| **产出** | What要素树(JSON结构) | 2.2.1节、2.2.2节 |
|
|
|
-| **价值** | 通过聚合分析提取爆款特征和趋势 | 1. 需求目标(核心价值) |
|
|
|
-
|
|
|
-**目标提炼**:
|
|
|
-> **构建一个递归解构系统,将多模态帖子内容自上而下、从整体到局部地拆解为树状的"What元素"结构,每个元素包含动态描述维度(由知识库提供),并关联原始素材。**
|
|
|
-
|
|
|
-### 1.2 关键约束条件
|
|
|
-
|
|
|
-**PRD依据** (2.2.3节、4节):
|
|
|
-
|
|
|
-1. **递归深度**: 最多10层
|
|
|
-2. **停止条件**:
|
|
|
- - 已是不可再分的最小单元
|
|
|
- - 达到10层深度
|
|
|
-3. **解构原则**:
|
|
|
- - 由宏观到微观、由整体到局部
|
|
|
- - 子节点不重不漏(充分必要)
|
|
|
-4. **知识驱动**: 描述维度、分割判断都由知识库动态提供
|
|
|
-5. **客观性**: 严格基于帖子内容,禁止臆测
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 2. 功能需求拆解
|
|
|
-
|
|
|
-### 2.1 核心功能清单
|
|
|
-
|
|
|
-基于PRD 3.1节"总体解构流程"和3.3节"对单一节点的递归解构",提炼出以下核心功能:
|
|
|
-
|
|
|
-| 功能ID | 功能名称 | 功能描述 | PRD依据 | 实现方式 |
|
|
|
-|--------|---------|---------|---------|---------|
|
|
|
-| **F1** | 帖子初理解 | 分析帖子整体,提取品类、主题、关键词 | 3.1节步骤1 | Agent组件 |
|
|
|
-| **F2** | 知识需求生成 | 根据品类/主题/关键词生成解构所需的query | 3.2节 | 工作流逻辑 |
|
|
|
-| **F3** | 知识检索 | 调用知识库获取描述维度、工具推荐、分割判断 | 3.2节、3.3.2节 | Tool组件 |
|
|
|
-| **F4** | 评论理解 | 分析评论,提取消费者关注的亮点 | 3.1节步骤4 | Agent组件 |
|
|
|
-| **F5** | 图片递归解构 | 递归解构图片元素,直到不可再分 | 3.1节步骤6-7、3.3节 | Agent组件 |
|
|
|
-| **F6** | 图片分割 | 将图片分割为多个独立的视觉元素 | 3.3.1节、3.5节 | Tool组件 |
|
|
|
-| **F7** | 文本递归解构 | 递归解构文本元素(标题/正文/标签) | 3.1节步骤8-11、3.3节 | Agent组件 |
|
|
|
-| **F8** | 文本切分 | 根据知识库指导切分文本 | 3.5节 | Function组件 |
|
|
|
-| **F9** | 帖子整体解构 | 从点线面体维度总结帖子 | 3.1节步骤13、3.2节 | Agent组件 |
|
|
|
-| **F10** | 结果汇总 | 将所有解构结果组装为树状JSON | 2.2.2节 | Function组件 |
|
|
|
-
|
|
|
-### 2.2 功能依赖关系
|
|
|
-
|
|
|
-```
|
|
|
-F1(帖子初理解)
|
|
|
- ↓
|
|
|
-F2(知识需求生成) → F3(知识检索)
|
|
|
- ↓
|
|
|
-F4(评论理解)
|
|
|
- ↓
|
|
|
-[F5(图片递归解构) + F6(图片分割)] 并行 [F7(文本递归解构) + F8(文本切分)]
|
|
|
- ↓
|
|
|
-F9(帖子整体解构)
|
|
|
- ↓
|
|
|
-F10(结果汇总)
|
|
|
-```
|
|
|
-
|
|
|
-**关键观察**:
|
|
|
-- F5和F7内部都会多次调用F3(知识检索)
|
|
|
-- F6和F8是F5、F7的支撑工具
|
|
|
-- F2是一个轻量级逻辑,不需要独立组件
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 3. 工作流 vs 组件职责划分
|
|
|
-
|
|
|
-### 3.1 划分原则
|
|
|
-
|
|
|
-**CLAUDE.md原则**:
|
|
|
-> - **工作流**:负责编排组件执行顺序和流程逻辑
|
|
|
-> - **组件**:实现具体业务功能
|
|
|
-
|
|
|
-### 3.2 职责划分表
|
|
|
-
|
|
|
-| 功能 | 实现方式 | 决策依据 |
|
|
|
-|------|---------|---------|
|
|
|
-| **F1 帖子初理解** | **Agent组件** | 需要多模态理解+语义推理(不确定性任务) |
|
|
|
-| **F2 知识需求生成** | **工作流逻辑** | 简单的字符串拼接,不需要独立组件 |
|
|
|
-| **F3 知识检索** | **Tool组件** | 需要被Agent调用 + 确定性API调用 |
|
|
|
-| **F4 评论理解** | **Agent组件** | 需要情感分析+语义理解(不确定性任务) |
|
|
|
-| **F5 图片递归解构** | **Agent组件** | 需要LLM视觉理解+ReAct模式调用工具 |
|
|
|
-| **F6 图片分割** | **Tool组件** | 需要被Agent调用 + 确定性模型调用 |
|
|
|
-| **F7 文本递归解构** | **Agent组件** | 需要LLM语义理解+ReAct模式调用工具 |
|
|
|
-| **F8 文本切分** | **Function组件** | 确定性字符串处理 + 工作流直接调用 |
|
|
|
-| **F9 帖子整体解构** | **Agent组件** | 需要综合分析+抽象提炼(不确定性任务) |
|
|
|
-| **F10 结果汇总** | **Function组件** | 确定性数据结构转换 + 工作流直接调用 |
|
|
|
-| **递归控制** | **工作流逻辑** | 编排递归流程,管理状态和深度 |
|
|
|
-| **并行执行** | **工作流逻辑** | 编排图片和文本并行解构 |
|
|
|
-
|
|
|
-**总结**:
|
|
|
-- **1个主工作流**: WhatDeconstructionWorkflow
|
|
|
-- **5个Agent组件**: F1, F4, F5, F7, F9
|
|
|
-- **2个Tool组件**: F3, F6
|
|
|
-- **2个Function组件**: F8, F10
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 4. 工作流设计
|
|
|
-
|
|
|
-### 4.1 工作流核心职责
|
|
|
-
|
|
|
-**WhatDeconstructionWorkflow** 的核心职责:
|
|
|
-
|
|
|
-1. **编排执行顺序**: 按PRD 3.1节流程图编排各组件
|
|
|
-2. **管理状态传递**: 维护 WhatDeconstructionState,在各节点间传递数据
|
|
|
-3. **控制递归深度**: 确保不超过10层
|
|
|
-4. **并行执行控制**: 图片和文本解构并行执行
|
|
|
-5. **知识query生成**: 根据中间结果动态生成知识库query
|
|
|
-
|
|
|
-### 4.2 工作流流程图
|
|
|
-
|
|
|
-**PRD依据**: 3.1节"总体解构流程"
|
|
|
-
|
|
|
-```mermaid
|
|
|
-graph TD
|
|
|
- START([开始]) --> N1[节点1: 帖子初理解<br/>PostUnderstandingAgent]
|
|
|
- N1 --> N2[节点2: 评论理解<br/>CommentAnalysisAgent]
|
|
|
- N2 --> PARALLEL{并行分支}
|
|
|
-
|
|
|
- PARALLEL --> N3[节点3: 图片递归解构<br/>RecursiveImageDeconstructionAgent]
|
|
|
- PARALLEL --> N4[节点4: 文本递归解构<br/>RecursiveTextDeconstructionAgent]
|
|
|
-
|
|
|
- N3 --> N5[节点5: 帖子整体解构<br/>PostSummaryDeconstructionAgent]
|
|
|
- N4 --> N5
|
|
|
-
|
|
|
- N5 --> N6[节点6: 结果汇总<br/>ResultAggregationFunction]
|
|
|
- N6 --> END([结束])
|
|
|
-
|
|
|
- style N3 fill:#e1f5ff
|
|
|
- style N4 fill:#e1f5ff
|
|
|
- style PARALLEL fill:#fff3cd
|
|
|
-```
|
|
|
-
|
|
|
-**流程说明**:
|
|
|
-
|
|
|
-| 节点 | 输入 | 输出 | PRD依据 |
|
|
|
-|------|------|------|---------|
|
|
|
-| N1 | 帖子多模态内容 | category, theme, keywords | 3.1节步骤1 |
|
|
|
-| N2 | 评论列表 | consumer_highlights | 3.1节步骤4 |
|
|
|
-| N3 | images列表 | image_deconstruction_results | 3.1节步骤6-7 |
|
|
|
-| N4 | text对象 | text_deconstruction_results | 3.1节步骤8-11 |
|
|
|
-| N5 | 所有子元素解构结果 | post_summary | 3.1节步骤13 |
|
|
|
-| N6 | 所有解构结果 | final_result (JSON树) | 2.2.2节 |
|
|
|
-
|
|
|
-### 4.3 工作流核心代码框架
|
|
|
-
|
|
|
-```python
|
|
|
-from langgraph.graph import StateGraph, END
|
|
|
-from typing import TypedDict, List, Dict, Any
|
|
|
-
|
|
|
-# 1. 定义状态
|
|
|
-class WhatDeconstructionState(TypedDict):
|
|
|
- """工作流状态定义"""
|
|
|
- # 输入
|
|
|
- images: List[str]
|
|
|
- text: Dict[str, Any] # {title, body, hashtags}
|
|
|
- comments: List[Dict[str, str]]
|
|
|
-
|
|
|
- # 中间状态
|
|
|
- category: str
|
|
|
- theme: str
|
|
|
- keywords: List[str]
|
|
|
- consumer_highlights: List[Dict]
|
|
|
-
|
|
|
- # 解构结果
|
|
|
- image_deconstruction_results: List[Dict]
|
|
|
- text_deconstruction_results: List[Dict]
|
|
|
- post_summary: Dict[str, Any]
|
|
|
-
|
|
|
- # 最终输出
|
|
|
- final_result: Dict[str, Any]
|
|
|
-
|
|
|
-# 2. 工作流类
|
|
|
-class WhatDeconstructionWorkflow:
|
|
|
- """What解构主工作流"""
|
|
|
-
|
|
|
- def __init__(self, llm, knowledge_retrieval_tool, image_segment_tool):
|
|
|
- # 初始化所有组件
|
|
|
- self.post_understanding_agent = PostUnderstandingAgent(llm)
|
|
|
- self.comment_analysis_agent = CommentAnalysisAgent(llm)
|
|
|
- self.image_agent = RecursiveImageDeconstructionAgent(
|
|
|
- llm, knowledge_retrieval_tool, image_segment_tool, max_depth=10
|
|
|
- )
|
|
|
- self.text_agent = RecursiveTextDeconstructionAgent(
|
|
|
- llm, knowledge_retrieval_tool, max_depth=10
|
|
|
- )
|
|
|
- self.summary_agent = PostSummaryDeconstructionAgent(llm, knowledge_retrieval_tool)
|
|
|
- self.aggregation_func = ResultAggregationFunction()
|
|
|
-
|
|
|
- # 构建图
|
|
|
- self.graph = self._build_graph()
|
|
|
-
|
|
|
- def _build_graph(self) -> StateGraph:
|
|
|
- """构建LangGraph工作流"""
|
|
|
- workflow = StateGraph(WhatDeconstructionState)
|
|
|
-
|
|
|
- # 添加节点
|
|
|
- workflow.add_node("post_understanding", self._node_post_understanding)
|
|
|
- workflow.add_node("comment_analysis", self._node_comment_analysis)
|
|
|
- workflow.add_node("image_deconstruction", self._node_image_deconstruction)
|
|
|
- workflow.add_node("text_deconstruction", self._node_text_deconstruction)
|
|
|
- workflow.add_node("post_summary", self._node_post_summary)
|
|
|
- workflow.add_node("result_aggregation", self._node_result_aggregation)
|
|
|
-
|
|
|
- # 定义边(流程)
|
|
|
- workflow.set_entry_point("post_understanding")
|
|
|
- workflow.add_edge("post_understanding", "comment_analysis")
|
|
|
-
|
|
|
- # 并行分支:评论理解后,图片和文本并行解构
|
|
|
- workflow.add_edge("comment_analysis", "image_deconstruction")
|
|
|
- workflow.add_edge("comment_analysis", "text_deconstruction")
|
|
|
-
|
|
|
- # 汇聚:图片和文本都完成后,执行帖子整体解构
|
|
|
- workflow.add_edge("image_deconstruction", "post_summary")
|
|
|
- workflow.add_edge("text_deconstruction", "post_summary")
|
|
|
-
|
|
|
- workflow.add_edge("post_summary", "result_aggregation")
|
|
|
- workflow.add_edge("result_aggregation", END)
|
|
|
-
|
|
|
- return workflow.compile()
|
|
|
-
|
|
|
- # 3. 节点实现
|
|
|
- async def _node_post_understanding(self, state: WhatDeconstructionState):
|
|
|
- """节点1: 帖子初理解"""
|
|
|
- result = await self.post_understanding_agent.ainvoke({
|
|
|
- "images": state["images"],
|
|
|
- "text": state["text"]
|
|
|
- })
|
|
|
- return {
|
|
|
- "category": result["category"],
|
|
|
- "theme": result["theme"],
|
|
|
- "keywords": result["keywords"]
|
|
|
- }
|
|
|
-
|
|
|
- async def _node_comment_analysis(self, state: WhatDeconstructionState):
|
|
|
- """节点2: 评论理解"""
|
|
|
- result = await self.comment_analysis_agent.ainvoke({
|
|
|
- "comments": state["comments"],
|
|
|
- "post_content": state["text"]
|
|
|
- })
|
|
|
- return {"consumer_highlights": result["highlights"]}
|
|
|
-
|
|
|
- async def _node_image_deconstruction(self, state: WhatDeconstructionState):
|
|
|
- """节点3: 图片递归解构(处理所有图片)"""
|
|
|
- results = []
|
|
|
- for idx, image_path in enumerate(state["images"]):
|
|
|
- result = await self.image_agent.ainvoke({
|
|
|
- "image_path": image_path,
|
|
|
- "node_id": f"img_{idx+1}",
|
|
|
- "depth": 0,
|
|
|
- "category": state["category"],
|
|
|
- "theme": state["theme"]
|
|
|
- })
|
|
|
- results.append(result)
|
|
|
- return {"image_deconstruction_results": results}
|
|
|
-
|
|
|
- async def _node_text_deconstruction(self, state: WhatDeconstructionState):
|
|
|
- """节点4: 文本递归解构(标题+正文+标签)"""
|
|
|
- results = []
|
|
|
- text_data = state["text"]
|
|
|
-
|
|
|
- # 解构标题
|
|
|
- if text_data.get("title"):
|
|
|
- title_result = await self.text_agent.ainvoke({
|
|
|
- "text": text_data["title"],
|
|
|
- "text_type": "title",
|
|
|
- "node_id": "title",
|
|
|
- "depth": 0
|
|
|
- })
|
|
|
- results.append(title_result)
|
|
|
-
|
|
|
- # 解构正文
|
|
|
- if text_data.get("body"):
|
|
|
- body_result = await self.text_agent.ainvoke({
|
|
|
- "text": text_data["body"],
|
|
|
- "text_type": "body",
|
|
|
- "node_id": "body",
|
|
|
- "depth": 0
|
|
|
- })
|
|
|
- results.append(body_result)
|
|
|
-
|
|
|
- # 解构话题标签
|
|
|
- if text_data.get("hashtags"):
|
|
|
- hashtags_result = await self.text_agent.ainvoke({
|
|
|
- "text": " ".join(text_data["hashtags"]),
|
|
|
- "text_type": "hashtags",
|
|
|
- "node_id": "hashtags",
|
|
|
- "depth": 0
|
|
|
- })
|
|
|
- results.append(hashtags_result)
|
|
|
-
|
|
|
- return {"text_deconstruction_results": results}
|
|
|
-
|
|
|
- async def _node_post_summary(self, state: WhatDeconstructionState):
|
|
|
- """节点5: 帖子整体解构"""
|
|
|
- result = await self.summary_agent.ainvoke({
|
|
|
- "category": state["category"],
|
|
|
- "theme": state["theme"],
|
|
|
- "keywords": state["keywords"],
|
|
|
- "image_results": state["image_deconstruction_results"],
|
|
|
- "text_results": state["text_deconstruction_results"],
|
|
|
- "consumer_highlights": state["consumer_highlights"]
|
|
|
- })
|
|
|
- return {"post_summary": result["summary"]}
|
|
|
-
|
|
|
- async def _node_result_aggregation(self, state: WhatDeconstructionState):
|
|
|
- """节点6: 结果汇总"""
|
|
|
- final_result = self.aggregation_func.invoke({
|
|
|
- "post_summary": state["post_summary"],
|
|
|
- "image_results": state["image_deconstruction_results"],
|
|
|
- "text_results": state["text_deconstruction_results"]
|
|
|
- })
|
|
|
- return {"final_result": final_result}
|
|
|
-
|
|
|
- # 4. 入口方法
|
|
|
- async def ainvoke(self, input_data: Dict) -> Dict:
|
|
|
- """执行工作流"""
|
|
|
- initial_state = WhatDeconstructionState(
|
|
|
- images=input_data["multimedia_content"]["images"],
|
|
|
- text=input_data["multimedia_content"]["text"],
|
|
|
- comments=input_data["comments"]
|
|
|
- )
|
|
|
- result = await self.graph.ainvoke(initial_state)
|
|
|
- return result["final_result"]
|
|
|
-```
|
|
|
-
|
|
|
-**关键设计点**:
|
|
|
-
|
|
|
-1. **并行执行**: `image_deconstruction` 和 `text_deconstruction` 从 `comment_analysis` 同时出发
|
|
|
-2. **状态传递**: 通过 `WhatDeconstructionState` 在节点间传递数据
|
|
|
-3. **递归封装**: 图片/文本的递归逻辑封装在各自的Agent内部
|
|
|
-4. **知识库调用**: 在Agent内部调用 KnowledgeRetrievalTool
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 5. 组件设计
|
|
|
-
|
|
|
-### 5.1 Agent组件总览
|
|
|
-
|
|
|
-**CLAUDE.md原则**:
|
|
|
-> **Agent组件特征**: 不确定性任务 + LLM智能能力 + 推理/多模态理解/语义分析
|
|
|
-
|
|
|
-| Agent | 任务类型 | LLM能力需求 | PRD依据 |
|
|
|
-|-------|---------|------------|---------|
|
|
|
-| PostUnderstandingAgent | 多模态理解 | 图文融合理解 | 3.1节步骤1 |
|
|
|
-| CommentAnalysisAgent | 语义理解 | 情感分析、关键点提取 | 3.1节步骤4、2.1.4节 |
|
|
|
-| RecursiveImageDeconstructionAgent | 视觉理解+推理 | 图片理解、分割判断 | 3.3节、3.3.1节 |
|
|
|
-| RecursiveTextDeconstructionAgent | 语义理解+推理 | 文本分析、切分判断 | 3.3节 |
|
|
|
-| PostSummaryDeconstructionAgent | 综合分析 | 抽象提炼、多维度总结 | 3.2节 |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### 5.2 Agent组件详细设计
|
|
|
-
|
|
|
-#### 5.2.1 PostUnderstandingAgent(帖子初理解)
|
|
|
-
|
|
|
-**组件类型**: Agent (继承 BaseLLMAgent)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.1节步骤1 "帖子初理解,输出品类、主题、关键词"
|
|
|
-- **CLAUDE.md依据**: 需要多模态理解(图+文),属于不确定性任务
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 综合分析图片和文本内容
|
|
|
-2. 推理提取品类(如"时尚美妆"、"美食"等)
|
|
|
-3. 总结主题(如"夏日穿搭")
|
|
|
-4. 提取关键词(如"OOTD"、"小白裙")
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | 依据 |
|
|
|
-|--------|---------|------|
|
|
|
-| **多模态输入** | 将图片和文本一起输入给LLM | LLM具备多模态理解能力 |
|
|
|
-| **结构化输出** | 使用JSON格式约束LLM输出 | 需要提取固定字段 |
|
|
|
-| **品类识别准确性** | 在prompt中提供常见品类列表 | 提升品类识别准确性 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from agents.base import BaseLLMAgent
|
|
|
-from typing import List, Dict, Any
|
|
|
-
|
|
|
-class PostUnderstandingAgent(BaseLLMAgent):
|
|
|
- """帖子初理解Agent
|
|
|
-
|
|
|
- PRD依据: 3.1节步骤1
|
|
|
- 功能: 分析帖子多模态内容,提取品类、主题、关键词
|
|
|
- """
|
|
|
-
|
|
|
- async def ainvoke(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 执行帖子理解
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "images": List[str], # 图片路径列表
|
|
|
- "text": { # 文本内容
|
|
|
- "title": str,
|
|
|
- "body": str,
|
|
|
- "hashtags": List[str]
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- {
|
|
|
- "category": str, # 品类
|
|
|
- "theme": str, # 主题
|
|
|
- "keywords": List[str] # 关键词
|
|
|
- }
|
|
|
- """
|
|
|
- # 1. 构建多模态prompt
|
|
|
- prompt = self._build_prompt(state["images"], state["text"])
|
|
|
-
|
|
|
- # 2. 调用LLM
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
-
|
|
|
- # 3. 解析结果
|
|
|
- result = self._parse_response(response)
|
|
|
-
|
|
|
- return result
|
|
|
-
|
|
|
- def _build_prompt(self, images: List[str], text: Dict) -> str:
|
|
|
- """构建prompt
|
|
|
-
|
|
|
- 关键点:
|
|
|
- - 多模态输入(图片+文本)
|
|
|
- - 明确要求输出JSON格式
|
|
|
- - 提供品类参考列表
|
|
|
- """
|
|
|
- return f"""
|
|
|
-你是一个小红书内容分析专家。请分析以下帖子的多模态内容,提取品类、主题和关键词。
|
|
|
-
|
|
|
-【文本内容】
|
|
|
-标题: {text.get('title', '')}
|
|
|
-正文: {text.get('body', '')}
|
|
|
-话题标签: {', '.join(text.get('hashtags', []))}
|
|
|
-
|
|
|
-【图片】
|
|
|
-{self._format_images(images)}
|
|
|
-
|
|
|
-【常见品类参考】
|
|
|
-时尚美妆、美食、旅游、家居、母婴、运动健身、数码科技等
|
|
|
-
|
|
|
-请以JSON格式输出:
|
|
|
-{{
|
|
|
- "category": "品类",
|
|
|
- "theme": "主题",
|
|
|
- "keywords": ["关键词1", "关键词2", ...]
|
|
|
-}}
|
|
|
-"""
|
|
|
-
|
|
|
- def _parse_response(self, response: str) -> Dict:
|
|
|
- """解析LLM响应,提取JSON"""
|
|
|
- import json
|
|
|
- # 提取JSON部分
|
|
|
- json_str = self._extract_json(response)
|
|
|
- return json.loads(json_str)
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.2.2 CommentAnalysisAgent(评论理解)
|
|
|
-
|
|
|
-**组件类型**: Agent (继承 BaseLLMAgent)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 2.1.4节 "评论信息用于提取内容消费者关注的亮点"
|
|
|
-- **CLAUDE.md依据**: 需要情感分析和语义理解,属于不确定性任务
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 分析评论区文字内容
|
|
|
-2. 识别消费者关注的亮点(对应帖子的哪些元素)
|
|
|
-3. 提取高频关注点和情绪共鸣点
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | 依据 |
|
|
|
-|--------|---------|------|
|
|
|
-| **亮点映射** | 将评论中的关注点映射到帖子具体元素 | PRD 2.1.4节要求 |
|
|
|
-| **情感分析** | 识别评论的情感倾向(共鸣、兴趣、疑问等) | 理解消费者反应 |
|
|
|
-| **高频提取** | 聚合多条评论的共同关注点 | 识别关键吸引点 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-class CommentAnalysisAgent(BaseLLMAgent):
|
|
|
- """评论理解Agent
|
|
|
-
|
|
|
- PRD依据: 2.1.4节
|
|
|
- 功能: 分析评论,提取消费者关注的亮点
|
|
|
- """
|
|
|
-
|
|
|
- async def ainvoke(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 执行评论分析
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "comments": List[Dict], # [{user, content}, ...]
|
|
|
- "post_content": Dict # 帖子内容(用于映射)
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- {
|
|
|
- "highlights": List[Dict] # 消费者关注的亮点列表
|
|
|
- # [
|
|
|
- # {
|
|
|
- # "element": "封面图中的小白裙",
|
|
|
- # "reason": "多条评论询问购买链接",
|
|
|
- # "emotion": "兴趣、购买意愿"
|
|
|
- # },
|
|
|
- # ...
|
|
|
- # ]
|
|
|
- }
|
|
|
- """
|
|
|
- prompt = self._build_prompt(state["comments"], state["post_content"])
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
- return {"highlights": self._parse_highlights(response)}
|
|
|
-
|
|
|
- def _build_prompt(self, comments: List[Dict], post_content: Dict) -> str:
|
|
|
- """构建prompt
|
|
|
-
|
|
|
- 关键点:
|
|
|
- - 提供帖子内容作为参考(用于映射)
|
|
|
- - 要求识别具体元素
|
|
|
- - 要求分析情感和关注原因
|
|
|
- """
|
|
|
- comments_text = "\n".join([
|
|
|
- f"- {c['content']}" for c in comments
|
|
|
- ])
|
|
|
-
|
|
|
- return f"""
|
|
|
-你是一个内容分析专家。请分析评论区内容,提取消费者关注的亮点。
|
|
|
-
|
|
|
-【帖子内容】
|
|
|
-标题: {post_content.get('title', '')}
|
|
|
-正文: {post_content.get('body', '')}
|
|
|
-
|
|
|
-【评论区】
|
|
|
-{comments_text}
|
|
|
-
|
|
|
-请分析:
|
|
|
-1. 评论中提到了帖子的哪些具体元素(封面、图片、标题、正文中的哪部分)
|
|
|
-2. 消费者对这些元素的关注原因(为什么吸引他们)
|
|
|
-3. 评论的情感倾向(共鸣、兴趣、疑问、赞美等)
|
|
|
-
|
|
|
-以JSON格式输出亮点列表:
|
|
|
-{{
|
|
|
- "highlights": [
|
|
|
- {{
|
|
|
- "element": "具体元素描述",
|
|
|
- "reason": "关注原因",
|
|
|
- "emotion": "情感倾向"
|
|
|
- }},
|
|
|
- ...
|
|
|
- ]
|
|
|
-}}
|
|
|
-"""
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.2.3 RecursiveImageDeconstructionAgent(图片递归解构)
|
|
|
-
|
|
|
-**组件类型**: Agent (继承 BaseReactAgent)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.3节 "对单一节点的递归解构"、3.3.1节 "判断图片元素是否要继续分割拆解"
|
|
|
-- **CLAUDE.md依据**: 需要LLM视觉理解 + 动态调用工具(ReAct模式)
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 粗理解图片,生成what字段
|
|
|
-2. 调用知识库获取描述维度
|
|
|
-3. 细致理解图片,填充描述值
|
|
|
-4. 判断是否需要分割(调用知识库)
|
|
|
-5. 如需分割,调用图片分割工具
|
|
|
-6. 递归处理子元素(最多10层)
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **ReAct模式** | 思考→行动→观察循环 | 需要多次调用工具 |
|
|
|
-| **递归控制** | 深度参数 + 停止条件判断 | 2.2.3节(最多10层) |
|
|
|
-| **知识驱动** | 描述维度和分割判断都由知识库提供 | 3.3.2节 |
|
|
|
-| **分割优先判断** | 优先判断是否为多子图拼接 | 3.3.1节 |
|
|
|
-| **what字段生成** | 对图片的文本描述 | 2.2.2节 |
|
|
|
-| **判断不依赖分割** | 判断是否拆解基于完整原图+文字描述,不依赖分割结果 | PRD 3.3.1节原则 |
|
|
|
-
|
|
|
-**递归流程图**:
|
|
|
-
|
|
|
-```mermaid
|
|
|
-graph TD
|
|
|
- START([接收图片节点]) --> DEPTH{检查深度<br/>是否≥10层?}
|
|
|
- DEPTH -->|是| STOP([返回结果<br/>不再递归])
|
|
|
- DEPTH -->|否| ROUGH[粗理解图片<br/>基于完整原图+节点描述<br/>生成what字段]
|
|
|
-
|
|
|
- ROUGH --> QUERY1[生成query:<br/>获取描述维度]
|
|
|
- QUERY1 --> KB1[调用知识库]
|
|
|
- KB1 --> DETAIL[细致理解图片<br/>基于完整原图+节点描述+维度<br/>填充描述维度值]
|
|
|
-
|
|
|
- DETAIL --> QUERY2[生成query:<br/>是否需要分割?]
|
|
|
- QUERY2 --> KB2[调用知识库<br/>基于完整原图+what+描述]
|
|
|
- KB2 --> DECISION{知识库判断:<br/>是否分割?}
|
|
|
-
|
|
|
- DECISION -->|否| STOP
|
|
|
- DECISION -->|是| IDENTIFY[识别子元素<br/>基于完整原图<br/>生成子元素描述]
|
|
|
- IDENTIFY --> SEGMENT[调用图片分割工具<br/>传入完整原图<br/>获得子图片列表]
|
|
|
-
|
|
|
- SEGMENT --> RECURSE[遍历子图片<br/>递归调用自身<br/>传递完整原图]
|
|
|
- RECURSE --> STOP
|
|
|
-
|
|
|
- style ROUGH fill:#e1f5ff
|
|
|
- style DETAIL fill:#e1f5ff
|
|
|
- style KB1 fill:#fff3cd
|
|
|
- style KB2 fill:#fff3cd
|
|
|
- style SEGMENT fill:#d4edda
|
|
|
-```
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from agents.base import BaseReactAgent
|
|
|
-from tools.base import BaseTool
|
|
|
-from typing import List, Dict, Any
|
|
|
-
|
|
|
-class RecursiveImageDeconstructionAgent(BaseReactAgent):
|
|
|
- """图片递归解构Agent
|
|
|
-
|
|
|
- PRD依据: 3.3节、3.3.1节
|
|
|
- 功能: 递归解构图片元素,最多10层
|
|
|
- """
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- llm,
|
|
|
- knowledge_retrieval_tool: BaseTool,
|
|
|
- image_segment_tool: BaseTool,
|
|
|
- max_depth: int = 10
|
|
|
- ):
|
|
|
- super().__init__(llm, tools=[knowledge_retrieval_tool, image_segment_tool])
|
|
|
- self.knowledge_tool = knowledge_retrieval_tool
|
|
|
- self.segment_tool = image_segment_tool
|
|
|
- self.max_depth = max_depth
|
|
|
-
|
|
|
- async def ainvoke(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 递归解构图片
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "original_image_path": str, # **完整原图路径**(PRD 3.3.1:始终传递未分割的完整图)
|
|
|
- "current_image_path": str, # 当前节点对应的图片路径(首次调用时=original_image_path)
|
|
|
- "node_description": str, # 当前节点的文字描述(首次为空,递归时为子元素描述)
|
|
|
- "node_id": str, # 节点ID(如"1", "1_1")
|
|
|
- "depth": int, # 当前递归深度
|
|
|
- "category": str, # 帖子品类(用于生成query)
|
|
|
- "theme": str # 帖子主题(用于生成query)
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- {
|
|
|
- "id": str,
|
|
|
- "what": str,
|
|
|
- "description": Dict,
|
|
|
- "children": List[Dict], # 子元素(递归结构)
|
|
|
- "importance_weight": float
|
|
|
- }
|
|
|
- """
|
|
|
- # 步骤1: 检查递归深度(PRD 2.2.3节)
|
|
|
- if state["depth"] >= self.max_depth:
|
|
|
- return self._build_leaf_node(state)
|
|
|
-
|
|
|
- # 步骤2: 粗理解当前节点,生成what字段(PRD 3.3节流程步骤B0)
|
|
|
- # **关键**: 基于完整原图 + 节点描述进行理解(PRD 3.3.1原则)
|
|
|
- what_value = await self._rough_understanding(
|
|
|
- original_image=state["original_image_path"],
|
|
|
- current_image=state["current_image_path"],
|
|
|
- node_description=state.get("node_description", "")
|
|
|
- )
|
|
|
-
|
|
|
- # 步骤3: 调用知识库获取描述维度(PRD 3.3.2节)
|
|
|
- description_query = self._generate_description_query(what_value)
|
|
|
- description_dimensions = await self.knowledge_tool.ainvoke({
|
|
|
- "query": description_query,
|
|
|
- "query_type": "description"
|
|
|
- })
|
|
|
-
|
|
|
- # 步骤4: 根据描述维度细致理解当前节点(PRD 3.3节流程步骤C)
|
|
|
- # **关键**: 基于完整原图 + 节点描述 + 描述维度进行理解(PRD 3.3.1原则)
|
|
|
- description_values = await self._detailed_understanding(
|
|
|
- original_image=state["original_image_path"],
|
|
|
- current_image=state["current_image_path"],
|
|
|
- node_description=state.get("node_description", ""),
|
|
|
- description_dimensions=description_dimensions
|
|
|
- )
|
|
|
-
|
|
|
- # 步骤5: 判断是否需要分割(PRD 3.3节流程步骤D0)
|
|
|
- # **关键**: 判断分割的输入是完整原图 + what + 描述,不依赖分割结果(PRD 3.3.1原则)
|
|
|
- split_query = self._generate_split_query(what_value, description_values)
|
|
|
- split_decision = await self.knowledge_tool.ainvoke({
|
|
|
- "query": split_query,
|
|
|
- "query_type": "split_decision"
|
|
|
- })
|
|
|
-
|
|
|
- # 构建当前节点结果
|
|
|
- result = {
|
|
|
- "id": state["node_id"],
|
|
|
- "what": what_value,
|
|
|
- "description": description_values,
|
|
|
- "children": [],
|
|
|
- "importance_weight": 0.5 # TODO: 计算权重逻辑
|
|
|
- }
|
|
|
-
|
|
|
- # 步骤6: 如需分割,执行分割并递归(PRD 3.3节流程步骤E1-E2)
|
|
|
- if split_decision.get("should_split", False):
|
|
|
- # 识别子元素(基于完整原图 + 当前节点描述)
|
|
|
- sub_elements = await self._identify_sub_elements(
|
|
|
- original_image=state["original_image_path"],
|
|
|
- current_node_what=what_value,
|
|
|
- current_node_description=state.get("node_description", "")
|
|
|
- )
|
|
|
-
|
|
|
- # 调用分割工具(输入:完整原图 + 子元素描述列表)
|
|
|
- sub_images = await self.segment_tool.ainvoke({
|
|
|
- "image_path": state["original_image_path"], # **传入完整原图**
|
|
|
- "elements_description": sub_elements
|
|
|
- })
|
|
|
-
|
|
|
- # 递归处理每个子元素
|
|
|
- children = []
|
|
|
- for idx, (sub_img_path, sub_element_desc) in enumerate(zip(sub_images, sub_elements)):
|
|
|
- child_state = {
|
|
|
- "original_image_path": state["original_image_path"], # **传递完整原图**
|
|
|
- "current_image_path": sub_img_path, # 分割后的子图片
|
|
|
- "node_description": sub_element_desc, # 子元素的文字描述
|
|
|
- "node_id": f"{state['node_id']}_{idx+1}",
|
|
|
- "depth": state["depth"] + 1,
|
|
|
- "category": state["category"],
|
|
|
- "theme": state["theme"]
|
|
|
- }
|
|
|
- child_result = await self.ainvoke(child_state)
|
|
|
- children.append(child_result)
|
|
|
-
|
|
|
- result["children"] = children
|
|
|
-
|
|
|
- return result
|
|
|
-
|
|
|
- async def _rough_understanding(
|
|
|
- self,
|
|
|
- original_image: str,
|
|
|
- current_image: str,
|
|
|
- node_description: str
|
|
|
- ) -> str:
|
|
|
- """粗理解当前节点,生成what字段
|
|
|
-
|
|
|
- PRD依据: 3.3节步骤B0, 3.3.1节原则
|
|
|
- 关键点: 基于完整原图 + 节点描述进行理解
|
|
|
-
|
|
|
- Args:
|
|
|
- original_image: 完整原图路径(提供上下文)
|
|
|
- current_image: 当前节点对应的图片路径
|
|
|
- node_description: 当前节点的文字描述(首次为空,递归时为子元素描述)
|
|
|
- """
|
|
|
- # 构建prompt:如果有节点描述,说明是递归调用,需要结合原图理解特定部分
|
|
|
- if node_description:
|
|
|
- prompt = f"""
|
|
|
-请基于完整原图的上下文,描述指定的视觉元素。
|
|
|
-
|
|
|
-【完整原图】: {original_image}
|
|
|
-【当前关注的元素描述】: {node_description}
|
|
|
-【当前元素图片】: {current_image}
|
|
|
-
|
|
|
-请用一句话描述这个视觉元素的核心内容。
|
|
|
-要求: 简洁准确,聚焦于该元素本身。
|
|
|
-"""
|
|
|
- else:
|
|
|
- # 首次调用,直接理解整图
|
|
|
- prompt = f"""
|
|
|
-请用一句话描述这张图片的核心内容和视觉元素。
|
|
|
-要求: 简洁准确,涵盖主要视觉要素。
|
|
|
-
|
|
|
-图片: {original_image}
|
|
|
-"""
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
- return response.strip()
|
|
|
-
|
|
|
- def _generate_description_query(self, what_value: str) -> str:
|
|
|
- """生成描述维度query
|
|
|
-
|
|
|
- PRD依据: 3.3.2节、2.2.2节
|
|
|
- Query句式: PRD 3.3.2节定义
|
|
|
- """
|
|
|
- return f'刻画描述"{what_value}"核心特征的角度和维度有哪些?请尽可能不重不漏列举全。'
|
|
|
-
|
|
|
- def _generate_split_query(self, what_value: str, description: Dict) -> str:
|
|
|
- """生成分割判断query
|
|
|
-
|
|
|
- PRD依据: 3.3节步骤D0
|
|
|
- """
|
|
|
- return f'该节点"{what_value}"是否需要继续拆解分割?'
|
|
|
-
|
|
|
- async def _detailed_understanding(
|
|
|
- self,
|
|
|
- original_image: str,
|
|
|
- current_image: str,
|
|
|
- node_description: str,
|
|
|
- dimensions: Dict
|
|
|
- ) -> Dict:
|
|
|
- """根据描述维度细致理解当前节点
|
|
|
-
|
|
|
- PRD依据: 3.3节步骤C, 3.3.1节原则
|
|
|
- 关键点: 基于完整原图 + 节点描述 + 描述维度进行理解
|
|
|
-
|
|
|
- Args:
|
|
|
- original_image: 完整原图路径(提供上下文)
|
|
|
- current_image: 当前节点对应的图片路径
|
|
|
- node_description: 当前节点的文字描述
|
|
|
- dimensions: 知识库返回的描述维度
|
|
|
- """
|
|
|
- # 构建prompt:结合原图上下文和节点描述
|
|
|
- if node_description:
|
|
|
- prompt = f"""
|
|
|
-请基于完整原图的上下文,分析指定视觉元素的特征。
|
|
|
-
|
|
|
-【完整原图】: {original_image}
|
|
|
-【当前关注的元素描述】: {node_description}
|
|
|
-【当前元素图片】: {current_image}
|
|
|
-
|
|
|
-请从以下维度分析这个视觉元素:
|
|
|
-{self._format_dimensions(dimensions)}
|
|
|
-
|
|
|
-以JSON格式输出各维度的值。
|
|
|
-"""
|
|
|
- else:
|
|
|
- # 首次调用,分析整图
|
|
|
- prompt = f"""
|
|
|
-请从以下维度分析图片:
|
|
|
-{self._format_dimensions(dimensions)}
|
|
|
-
|
|
|
-图片: {original_image}
|
|
|
-
|
|
|
-以JSON格式输出各维度的值。
|
|
|
-"""
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
- return self._parse_description(response)
|
|
|
-
|
|
|
- async def _identify_sub_elements(
|
|
|
- self,
|
|
|
- original_image: str,
|
|
|
- current_node_what: str,
|
|
|
- current_node_description: str
|
|
|
- ) -> List[str]:
|
|
|
- """识别当前节点中的子元素
|
|
|
-
|
|
|
- PRD依据: 3.3.1节
|
|
|
- 关键点: 基于完整原图 + 当前节点描述识别子元素(不依赖分割结果)
|
|
|
-
|
|
|
- Args:
|
|
|
- original_image: 完整原图路径(提供上下文)
|
|
|
- current_node_what: 当前节点的what字段值
|
|
|
- current_node_description: 当前节点的文字描述
|
|
|
- """
|
|
|
- # 构建prompt:基于原图和当前节点描述识别子元素
|
|
|
- if current_node_description:
|
|
|
- prompt = f"""
|
|
|
-请基于完整原图的上下文,识别指定视觉元素中包含的子元素。
|
|
|
-
|
|
|
-【完整原图】: {original_image}
|
|
|
-【当前关注的元素】: {current_node_what}
|
|
|
-【当前元素的详细描述】: {current_node_description}
|
|
|
-
|
|
|
-判断步骤:
|
|
|
-1. 该元素是否为多张子图拼接?(如4宫格、9宫格)
|
|
|
-2. 如果不是,该元素包含哪些独立的视觉对象?(如人物、商品、背景元素)
|
|
|
-
|
|
|
-请列出所有子元素的文字描述,每个元素一行。
|
|
|
-"""
|
|
|
- else:
|
|
|
- # 首次调用,分析整图
|
|
|
- prompt = f"""
|
|
|
-分析图片"{current_node_what}",识别其中包含的子视觉元素。
|
|
|
-
|
|
|
-【图片】: {original_image}
|
|
|
-
|
|
|
-判断步骤:
|
|
|
-1. 是否为多张子图拼接?(如4宫格、9宫格)
|
|
|
-2. 如果不是,包含哪些独立的视觉对象?(如人物、商品、背景元素)
|
|
|
-
|
|
|
-请列出所有子元素的文字描述,每个元素一行。
|
|
|
-"""
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
- return [line.strip() for line in response.strip().split('\n') if line.strip()]
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.2.4 RecursiveTextDeconstructionAgent(文本递归解构)
|
|
|
-
|
|
|
-**组件类型**: Agent (继承 BaseReactAgent)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.3节 "对单一节点的递归解构"
|
|
|
-- **CLAUDE.md依据**: 需要LLM语义理解 + 动态调用工具
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 提取文本原文作为what字段(不可改写)
|
|
|
-2. 调用知识库获取描述维度
|
|
|
-3. 分析文本,填充描述值
|
|
|
-4. 判断是否需要切分
|
|
|
-5. 如需切分,调用文本切分函数
|
|
|
-6. 递归处理子文本
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **what字段是原文** | 直接使用文本原文,不可总结改写 | 2.2.2节 |
|
|
|
-| **充分必要原则** | 子文本合并后必须等于父文本 | 3.4节 |
|
|
|
-| **递归控制** | 深度限制 + 最小语义单元判断 | 2.2.3节 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-class RecursiveTextDeconstructionAgent(BaseReactAgent):
|
|
|
- """文本递归解构Agent
|
|
|
-
|
|
|
- PRD依据: 3.3节
|
|
|
- 功能: 递归解构文本元素(标题/正文/标签)
|
|
|
- """
|
|
|
-
|
|
|
- def __init__(
|
|
|
- self,
|
|
|
- llm,
|
|
|
- knowledge_retrieval_tool: BaseTool,
|
|
|
- max_depth: int = 10
|
|
|
- ):
|
|
|
- super().__init__(llm, tools=[knowledge_retrieval_tool])
|
|
|
- self.knowledge_tool = knowledge_retrieval_tool
|
|
|
- self.max_depth = max_depth
|
|
|
-
|
|
|
- async def ainvoke(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 递归解构文本
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "text": str, # 当前文本内容
|
|
|
- "text_type": str, # 文本类型(title/body/hashtags)
|
|
|
- "node_id": str, # 节点ID
|
|
|
- "depth": int # 当前递归深度
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- {
|
|
|
- "id": str,
|
|
|
- "what": str, # 文本原文
|
|
|
- "description": Dict,
|
|
|
- "children": List[Dict]
|
|
|
- }
|
|
|
- """
|
|
|
- # 步骤1: 检查递归深度
|
|
|
- if state["depth"] >= self.max_depth:
|
|
|
- return self._build_leaf_node(state)
|
|
|
-
|
|
|
- # 步骤2: what字段直接使用原文(PRD 2.2.2节)
|
|
|
- what_value = state["text"]
|
|
|
-
|
|
|
- # 步骤3: 调用知识库获取描述维度
|
|
|
- description_query = self._generate_description_query(
|
|
|
- what_value,
|
|
|
- state["text_type"]
|
|
|
- )
|
|
|
- description_dimensions = await self.knowledge_tool.ainvoke({
|
|
|
- "query": description_query,
|
|
|
- "query_type": "description"
|
|
|
- })
|
|
|
-
|
|
|
- # 步骤4: 根据描述维度分析文本
|
|
|
- description_values = await self._analyze_text(
|
|
|
- what_value,
|
|
|
- description_dimensions,
|
|
|
- state["text_type"]
|
|
|
- )
|
|
|
-
|
|
|
- # 步骤5: 判断是否需要切分
|
|
|
- split_query = self._generate_split_query(what_value)
|
|
|
- split_decision = await self.knowledge_tool.ainvoke({
|
|
|
- "query": split_query,
|
|
|
- "query_type": "split_decision"
|
|
|
- })
|
|
|
-
|
|
|
- result = {
|
|
|
- "id": state["node_id"],
|
|
|
- "what": what_value, # 原文,不可改写
|
|
|
- "description": description_values,
|
|
|
- "children": []
|
|
|
- }
|
|
|
-
|
|
|
- # 步骤6: 如需切分,执行切分并递归
|
|
|
- if split_decision.get("should_split", False):
|
|
|
- # 获取切分规则
|
|
|
- split_rules = split_decision.get("split_rules", {})
|
|
|
-
|
|
|
- # 执行文本切分(调用Function组件)
|
|
|
- sub_texts = self._split_text(what_value, split_rules)
|
|
|
-
|
|
|
- # 递归处理子文本
|
|
|
- children = []
|
|
|
- for idx, sub_text in enumerate(sub_texts):
|
|
|
- child_state = {
|
|
|
- "text": sub_text,
|
|
|
- "text_type": state["text_type"],
|
|
|
- "node_id": f"{state['node_id']}_{idx+1}",
|
|
|
- "depth": state["depth"] + 1
|
|
|
- }
|
|
|
- child_result = await self.ainvoke(child_state)
|
|
|
- children.append(child_result)
|
|
|
-
|
|
|
- result["children"] = children
|
|
|
-
|
|
|
- return result
|
|
|
-
|
|
|
- def _split_text(self, text: str, split_rules: Dict) -> List[str]:
|
|
|
- """切分文本
|
|
|
-
|
|
|
- PRD依据: 3.5节
|
|
|
- 关键点: 保证子文本合并后等于父文本(充分必要)
|
|
|
- """
|
|
|
- strategy = split_rules.get("strategy", "sentence")
|
|
|
-
|
|
|
- if strategy == "sentence":
|
|
|
- # 按句子切分
|
|
|
- import re
|
|
|
- sentences = re.split(r'[。!?\n]', text)
|
|
|
- return [s.strip() for s in sentences if s.strip()]
|
|
|
- elif strategy == "paragraph":
|
|
|
- # 按段落切分
|
|
|
- paragraphs = text.split('\n\n')
|
|
|
- return [p.strip() for p in paragraphs if p.strip()]
|
|
|
- else:
|
|
|
- return [text]
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.2.5 PostSummaryDeconstructionAgent(帖子整体解构)
|
|
|
-
|
|
|
-**组件类型**: Agent (继承 BaseLLMAgent)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.2节 "帖子整体解构环节"
|
|
|
-- **CLAUDE.md依据**: 需要综合分析和抽象提炼能力
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 调用知识库获取总结维度
|
|
|
-2. 综合所有子元素解构结果
|
|
|
-3. 从点线面体维度总结帖子
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **动态总结维度** | 由知识库根据品类/主题/关键词提供 | 2.2.2节、3.2节 |
|
|
|
-| **综合分析** | 整合图片、文本、评论的解构结果 | 3.1节步骤13 |
|
|
|
-| **维度数量** | 不少于3个维度 | 2.2.2节 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-class PostSummaryDeconstructionAgent(BaseLLMAgent):
|
|
|
- """帖子整体解构Agent
|
|
|
-
|
|
|
- PRD依据: 3.2节
|
|
|
- 功能: 从点线面体维度总结帖子
|
|
|
- """
|
|
|
-
|
|
|
- def __init__(self, llm, knowledge_retrieval_tool: BaseTool):
|
|
|
- super().__init__(llm)
|
|
|
- self.knowledge_tool = knowledge_retrieval_tool
|
|
|
-
|
|
|
- async def ainvoke(self, state: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 执行帖子整体解构
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "category": str,
|
|
|
- "theme": str,
|
|
|
- "keywords": List[str],
|
|
|
- "image_results": List[Dict],
|
|
|
- "text_results": List[Dict],
|
|
|
- "consumer_highlights": List[Dict]
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- {
|
|
|
- "summary": Dict # 动态维度的总结
|
|
|
- }
|
|
|
- """
|
|
|
- # 步骤1: 生成总结维度query(PRD 3.2节)
|
|
|
- summary_query = self._generate_summary_query(
|
|
|
- state["category"],
|
|
|
- state["theme"],
|
|
|
- state["keywords"]
|
|
|
- )
|
|
|
-
|
|
|
- # 步骤2: 调用知识库获取总结维度
|
|
|
- summary_dimensions = await self.knowledge_tool.ainvoke({
|
|
|
- "query": summary_query,
|
|
|
- "query_type": "description"
|
|
|
- })
|
|
|
-
|
|
|
- # 步骤3: 根据总结维度分析帖子
|
|
|
- prompt = self._build_summary_prompt(
|
|
|
- summary_dimensions,
|
|
|
- state["image_results"],
|
|
|
- state["text_results"],
|
|
|
- state["consumer_highlights"]
|
|
|
- )
|
|
|
-
|
|
|
- response = await self.llm.ainvoke(prompt)
|
|
|
- summary = self._parse_summary(response)
|
|
|
-
|
|
|
- return {"summary": summary}
|
|
|
-
|
|
|
- def _generate_summary_query(
|
|
|
- self,
|
|
|
- category: str,
|
|
|
- theme: str,
|
|
|
- keywords: List[str]
|
|
|
- ) -> str:
|
|
|
- """生成总结维度query
|
|
|
-
|
|
|
- PRD依据: 3.2节
|
|
|
- Query句式: PRD 3.2节定义
|
|
|
- """
|
|
|
- keywords_str = ', '.join(keywords)
|
|
|
- return f'对于一篇主题为"{theme}",品类为"{category}",关键词包含"{keywords_str}"的多模态社交媒体帖子,从内容创作者视角进行What要素的初步识别和分类,需要使用哪些通用工具?'
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### 5.3 Tool组件详细设计
|
|
|
-
|
|
|
-**CLAUDE.md原则**:
|
|
|
-> **Tool组件特征**: 确定性任务 + 可被Agent调用
|
|
|
-
|
|
|
-#### 5.3.1 KnowledgeRetrievalTool(知识检索工具)
|
|
|
-
|
|
|
-**组件类型**: Tool (继承 BaseTool)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.2节、3.3.2节 "对单一节点解构如何获取知识"
|
|
|
-- **CLAUDE.md依据**: 确定性API调用 + 需要被Agent调用
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 接收query和query类型
|
|
|
-2. 调用外部知识库API
|
|
|
-3. 根据query类型解析返回结果
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **支持多种query类型** | description/tool/split_decision | 3.3.2节 |
|
|
|
-| **降级策略** | 知识库不可用时使用LLM默认知识 | 容错处理 |
|
|
|
-| **结果格式化** | 转换为Agent可用的结构化数据 | 提升可用性 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from tools.base import BaseTool
|
|
|
-from typing import Dict, Any
|
|
|
-
|
|
|
-class KnowledgeRetrievalTool(BaseTool):
|
|
|
- """知识检索工具
|
|
|
-
|
|
|
- PRD依据: 3.2节、3.3.2节
|
|
|
- 功能: 根据query检索解构所需的知识
|
|
|
- """
|
|
|
-
|
|
|
- name: str = "knowledge_retrieval"
|
|
|
- description: str = "根据query检索What解构所需的知识(描述维度、工具推荐、分割判断等)"
|
|
|
-
|
|
|
- def __init__(self, knowledge_base_url: str, fallback_llm=None):
|
|
|
- super().__init__()
|
|
|
- self.knowledge_base_url = knowledge_base_url
|
|
|
- self.fallback_llm = fallback_llm # 降级LLM
|
|
|
-
|
|
|
- async def _arun(
|
|
|
- self,
|
|
|
- query: str,
|
|
|
- query_type: str = "description"
|
|
|
- ) -> Dict[str, Any]:
|
|
|
- """
|
|
|
- 调用知识库检索
|
|
|
-
|
|
|
- Args:
|
|
|
- query: 查询语句(PRD 3.3.2节定义的句式)
|
|
|
- query_type: 查询类型
|
|
|
- - description: 获取描述维度
|
|
|
- - tool: 获取工具推荐
|
|
|
- - split_decision: 获取分割判断
|
|
|
-
|
|
|
- Returns:
|
|
|
- 根据query_type返回不同格式的结果
|
|
|
- """
|
|
|
- try:
|
|
|
- # 调用知识库API
|
|
|
- response = await self._call_knowledge_base(query)
|
|
|
-
|
|
|
- # 根据query类型解析结果
|
|
|
- if query_type == "description":
|
|
|
- return self._parse_description_dimensions(response)
|
|
|
- elif query_type == "tool":
|
|
|
- return self._parse_tool_recommendations(response)
|
|
|
- elif query_type == "split_decision":
|
|
|
- return self._parse_split_decision(response)
|
|
|
-
|
|
|
- return response
|
|
|
-
|
|
|
- except Exception as e:
|
|
|
- # 降级策略: 使用LLM默认知识
|
|
|
- if self.fallback_llm:
|
|
|
- return await self._fallback_with_llm(query, query_type)
|
|
|
- raise e
|
|
|
-
|
|
|
- def _parse_description_dimensions(self, response) -> Dict[str, Any]:
|
|
|
- """解析描述维度
|
|
|
-
|
|
|
- 返回格式:
|
|
|
- {
|
|
|
- "dimensions": ["维度1", "维度2", "维度3", ...],
|
|
|
- "count": 3
|
|
|
- }
|
|
|
- """
|
|
|
- dimensions = response.get("dimensions", [])
|
|
|
- return {
|
|
|
- "dimensions": dimensions,
|
|
|
- "count": len(dimensions)
|
|
|
- }
|
|
|
-
|
|
|
- def _parse_split_decision(self, response) -> Dict[str, Any]:
|
|
|
- """解析分割判断
|
|
|
-
|
|
|
- 返回格式:
|
|
|
- {
|
|
|
- "should_split": True/False,
|
|
|
- "split_rules": { # 如果should_split=True
|
|
|
- "strategy": "sentence/paragraph/grid",
|
|
|
- "params": {...}
|
|
|
- }
|
|
|
- }
|
|
|
- """
|
|
|
- return {
|
|
|
- "should_split": response.get("should_split", False),
|
|
|
- "split_rules": response.get("split_rules", {})
|
|
|
- }
|
|
|
-```
|
|
|
-
|
|
|
-**复用决策**:
|
|
|
-- **现有组件**: `tools/knowledge_retrieval_tools.py`
|
|
|
-- **决策**: **复用并修改**
|
|
|
-- **修改点**: 增强支持多种query_type,增加降级策略
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.3.2 ImageSegmentTool(图片分割工具)
|
|
|
-
|
|
|
-**组件类型**: Tool (继承 BaseTool)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.3.1节 "判断图片元素是否要继续分割拆解"、3.5节
|
|
|
-- **CLAUDE.md依据**: 确定性模型调用 + 需要被Agent调用
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 接收图片路径和子元素描述
|
|
|
-2. 调用分割模型(SAM/GroundingDINO)
|
|
|
-3. 保存分割后的子图片
|
|
|
-4. 返回子图片路径列表
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **支持多种分割模式** | grid(子图拼接)/ object(视觉元素) | 3.3.1节 |
|
|
|
-| **分割质量验证** | 验证mask是否覆盖元素 | 保证准确性 |
|
|
|
-| **文件管理** | 统一命名规范和存储路径 | 2.1.1节 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from tools.base import BaseTool
|
|
|
-from typing import List
|
|
|
-
|
|
|
-class ImageSegmentTool(BaseTool):
|
|
|
- """图片分割工具
|
|
|
-
|
|
|
- PRD依据: 3.3.1节、3.5节
|
|
|
- 功能: 根据文字描述将图片分割为多个独立的视觉元素
|
|
|
- """
|
|
|
-
|
|
|
- name: str = "image_segment"
|
|
|
- description: str = "根据文字描述将图片分割为多个独立的视觉元素"
|
|
|
-
|
|
|
- def __init__(self, segment_model, output_dir: str):
|
|
|
- super().__init__()
|
|
|
- self.segment_model = segment_model
|
|
|
- self.output_dir = output_dir
|
|
|
-
|
|
|
- async def _arun(
|
|
|
- self,
|
|
|
- image_path: str,
|
|
|
- elements_description: List[str],
|
|
|
- segment_type: str = "object"
|
|
|
- ) -> List[str]:
|
|
|
- """
|
|
|
- 分割图片
|
|
|
-
|
|
|
- Args:
|
|
|
- image_path: 原始图片路径
|
|
|
- elements_description: 子元素文字描述列表
|
|
|
- segment_type: 分割类型
|
|
|
- - object: 视觉元素分割(抠图)
|
|
|
- - grid: 子图拼接拆解(简单切割)
|
|
|
-
|
|
|
- Returns:
|
|
|
- 分割后的子图片路径列表
|
|
|
- """
|
|
|
- # 1. 加载图片
|
|
|
- image = self._load_image(image_path)
|
|
|
-
|
|
|
- # 2. 根据分割类型选择策略
|
|
|
- if segment_type == "grid":
|
|
|
- # 拼接图拆解(简单切割)
|
|
|
- sub_images = self._grid_split(image, len(elements_description))
|
|
|
- else:
|
|
|
- # 视觉元素分割(调用分割模型)
|
|
|
- sub_images = await self._segment_objects(image, elements_description)
|
|
|
-
|
|
|
- # 3. 保存子图片
|
|
|
- saved_paths = []
|
|
|
- for idx, sub_img in enumerate(sub_images):
|
|
|
- save_path = f"{self.output_dir}/{self._generate_filename(image_path, idx)}"
|
|
|
- self._save_image(sub_img, save_path)
|
|
|
- saved_paths.append(save_path)
|
|
|
-
|
|
|
- return saved_paths
|
|
|
-
|
|
|
- def _grid_split(self, image, num_elements: int):
|
|
|
- """拼接图拆解
|
|
|
-
|
|
|
- PRD依据: 3.3.1节(优先判断是否为多子图拼接)
|
|
|
- 支持: 2x2, 3x3, 1xN等常见拼接模式
|
|
|
- """
|
|
|
- # TODO: 根据num_elements推测拼接模式
|
|
|
- pass
|
|
|
-
|
|
|
- async def _segment_objects(self, image, descriptions: List[str]):
|
|
|
- """视觉元素分割
|
|
|
-
|
|
|
- 调用SAM/GroundingDINO等模型
|
|
|
- """
|
|
|
- masks = await self.segment_model.segment(image, descriptions)
|
|
|
- return self._extract_objects(image, masks)
|
|
|
-```
|
|
|
-
|
|
|
-**复用决策**:
|
|
|
-- **现有组件**: `tools/segment_tools.py`
|
|
|
-- **决策**: **直接复用**
|
|
|
-- **理由**: 已实现图片分割功能,完全符合PRD需求
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-### 5.4 Function组件详细设计
|
|
|
-
|
|
|
-**CLAUDE.md原则**:
|
|
|
-> **Function组件特征**: 确定性任务 + 不需要被Agent调用
|
|
|
-
|
|
|
-#### 5.4.1 TextSplitFunction(文本切分函数)
|
|
|
-
|
|
|
-**组件类型**: Function (继承 BaseFunction)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 3.5节 "文本切分工具"
|
|
|
-- **CLAUDE.md依据**: 确定性字符串处理 + 工作流直接调用
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 接收文本和切分规则
|
|
|
-2. 按规则切分文本
|
|
|
-3. 保证不重不漏(子文本合并=父文本)
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **充分必要原则** | 验证子文本合并后等于父文本 | 3.4节 |
|
|
|
-| **多种切分策略** | sentence/paragraph/semantic | 3.5节 |
|
|
|
-| **保留原文** | 不修改文本内容 | 2.2.2节 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from functions.base import BaseFunction
|
|
|
-from typing import List, Dict
|
|
|
-
|
|
|
-class TextSplitFunction(BaseFunction):
|
|
|
- """文本切分函数
|
|
|
-
|
|
|
- PRD依据: 3.5节
|
|
|
- 功能: 根据切分规则切分文本
|
|
|
- """
|
|
|
-
|
|
|
- def invoke(self, text: str, split_rules: Dict) -> List[str]:
|
|
|
- """
|
|
|
- 切分文本
|
|
|
-
|
|
|
- Args:
|
|
|
- text: 待切分文本
|
|
|
- split_rules: 切分规则
|
|
|
- {
|
|
|
- "strategy": "sentence/paragraph/semantic",
|
|
|
- "params": {...}
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- 切分后的子文本列表
|
|
|
- """
|
|
|
- strategy = split_rules.get("strategy", "sentence")
|
|
|
-
|
|
|
- if strategy == "sentence":
|
|
|
- sub_texts = self._split_by_sentence(text)
|
|
|
- elif strategy == "paragraph":
|
|
|
- sub_texts = self._split_by_paragraph(text)
|
|
|
- elif strategy == "semantic":
|
|
|
- sub_texts = self._split_by_semantic(text, split_rules.get("params"))
|
|
|
- else:
|
|
|
- sub_texts = [text]
|
|
|
-
|
|
|
- # 验证充分必要原则(PRD 3.4节)
|
|
|
- self._validate_completeness(text, sub_texts)
|
|
|
-
|
|
|
- return sub_texts
|
|
|
-
|
|
|
- def _split_by_sentence(self, text: str) -> List[str]:
|
|
|
- """按句子切分"""
|
|
|
- import re
|
|
|
- sentences = re.split(r'[。!?\n]', text)
|
|
|
- return [s.strip() for s in sentences if s.strip()]
|
|
|
-
|
|
|
- def _validate_completeness(self, parent_text: str, child_texts: List[str]):
|
|
|
- """验证子文本合并后是否等于父文本
|
|
|
-
|
|
|
- PRD依据: 3.4节 "充分必要原则"
|
|
|
- """
|
|
|
- merged = ''.join(child_texts)
|
|
|
- if merged.replace(' ', '') != parent_text.replace(' ', ''):
|
|
|
- raise ValueError("子文本合并后不等于父文本,违反充分必要原则")
|
|
|
-```
|
|
|
-
|
|
|
-**复用决策**:
|
|
|
-- **现有组件**: 无
|
|
|
-- **决策**: **完全新增**
|
|
|
-- **理由**: 项目中不存在文本切分功能
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-#### 5.4.2 ResultAggregationFunction(结果汇总函数)
|
|
|
-
|
|
|
-**组件类型**: Function (继承 BaseFunction)
|
|
|
-
|
|
|
-**选择依据**:
|
|
|
-- **PRD依据**: 2.2.2节 "输出格式定义"
|
|
|
-- **CLAUDE.md依据**: 确定性数据结构转换
|
|
|
-
|
|
|
-**核心职责**:
|
|
|
-1. 接收所有解构结果
|
|
|
-2. 组装为树状JSON结构
|
|
|
-3. 生成节点ID
|
|
|
-4. 验证JSON格式
|
|
|
-
|
|
|
-**关键点**:
|
|
|
-
|
|
|
-| 关键点 | 实现方式 | PRD依据 |
|
|
|
-|--------|---------|---------|
|
|
|
-| **递归构建树** | 递归函数处理children | 2.2.2节 |
|
|
|
-| **ID生成规则** | "1", "1_1", "1_1_2"格式 | 2.2.2节 |
|
|
|
-| **格式验证** | 验证所有必需字段存在 | 2.2.2节 |
|
|
|
-
|
|
|
-**代码框架**:
|
|
|
-
|
|
|
-```python
|
|
|
-from functions.base import BaseFunction
|
|
|
-from typing import Dict, List
|
|
|
-
|
|
|
-class ResultAggregationFunction(BaseFunction):
|
|
|
- """结果汇总函数
|
|
|
-
|
|
|
- PRD依据: 2.2.2节
|
|
|
- 功能: 将所有解构结果组装为树状JSON
|
|
|
- """
|
|
|
-
|
|
|
- def invoke(self, state: Dict) -> Dict:
|
|
|
- """
|
|
|
- 汇总所有解构结果
|
|
|
-
|
|
|
- Args:
|
|
|
- state: {
|
|
|
- "post_summary": Dict,
|
|
|
- "image_results": List[Dict],
|
|
|
- "text_results": List[Dict]
|
|
|
- }
|
|
|
-
|
|
|
- Returns:
|
|
|
- 最终的树状JSON结构(PRD 2.2.2节格式)
|
|
|
- """
|
|
|
- # 1. 提取各部分结果
|
|
|
- post_summary = state["post_summary"]
|
|
|
- image_results = state["image_results"]
|
|
|
- text_results = state["text_results"]
|
|
|
-
|
|
|
- # 2. 构建元素列表
|
|
|
- elements = []
|
|
|
-
|
|
|
- # 添加图片元素
|
|
|
- for idx, img_result in enumerate(image_results):
|
|
|
- element = self._build_element_tree(
|
|
|
- id=str(idx + 1),
|
|
|
- result=img_result
|
|
|
- )
|
|
|
- elements.append(element)
|
|
|
-
|
|
|
- # 添加文本元素
|
|
|
- text_id_start = len(image_results) + 1
|
|
|
- for idx, text_result in enumerate(text_results):
|
|
|
- element = self._build_element_tree(
|
|
|
- id=str(text_id_start + idx),
|
|
|
- result=text_result
|
|
|
- )
|
|
|
- elements.append(element)
|
|
|
-
|
|
|
- # 3. 组装最终JSON
|
|
|
- final_result = {
|
|
|
- "帖子总结": post_summary,
|
|
|
- "帖子包含元素": elements
|
|
|
- }
|
|
|
-
|
|
|
- # 4. 验证格式
|
|
|
- self._validate_result(final_result)
|
|
|
-
|
|
|
- return final_result
|
|
|
-
|
|
|
- def _build_element_tree(self, id: str, result: Dict) -> Dict:
|
|
|
- """递归构建元素树
|
|
|
-
|
|
|
- PRD依据: 2.2.2节
|
|
|
- """
|
|
|
- element = {
|
|
|
- "id": id,
|
|
|
- "what": result["what"],
|
|
|
- "描述": result["description"],
|
|
|
- "子节点元素关系": result.get("relationships", []),
|
|
|
- "元素重要性权重": result.get("importance_weight", 0.5),
|
|
|
- "子节点元素": []
|
|
|
- }
|
|
|
-
|
|
|
- # 递归处理children
|
|
|
- if "children" in result and result["children"]:
|
|
|
- for idx, child in enumerate(result["children"]):
|
|
|
- child_element = self._build_element_tree(
|
|
|
- id=f"{id}_{idx+1}",
|
|
|
- result=child
|
|
|
- )
|
|
|
- element["子节点元素"].append(child_element)
|
|
|
-
|
|
|
- return element
|
|
|
-
|
|
|
- def _validate_result(self, result: Dict):
|
|
|
- """验证JSON格式
|
|
|
-
|
|
|
- PRD依据: 2.2.2节
|
|
|
- 检查: 必需字段、维度数量≥3等
|
|
|
- """
|
|
|
- # 验证帖子总结
|
|
|
- assert "帖子总结" in result, "缺少'帖子总结'字段"
|
|
|
- assert len(result["帖子总结"]) >= 3, "帖子总结维度不足3个"
|
|
|
-
|
|
|
- # 验证帖子包含元素
|
|
|
- assert "帖子包含元素" in result, "缺少'帖子包含元素'字段"
|
|
|
-
|
|
|
- # 递归验证每个元素节点
|
|
|
- for element in result["帖子包含元素"]:
|
|
|
- self._validate_element(element)
|
|
|
-
|
|
|
- def _validate_element(self, element: Dict):
|
|
|
- """递归验证元素节点"""
|
|
|
- required_fields = ["id", "what", "描述", "子节点元素关系", "元素重要性权重", "子节点元素"]
|
|
|
- for field in required_fields:
|
|
|
- assert field in element, f"元素节点缺少'{field}'字段"
|
|
|
-
|
|
|
- # 验证描述维度数量
|
|
|
- assert len(element["描述"]) >= 3, f"元素{element['id']}的描述维度不足3个"
|
|
|
-
|
|
|
- # 递归验证子节点
|
|
|
- for child in element["子节点元素"]:
|
|
|
- self._validate_element(child)
|
|
|
-```
|
|
|
-
|
|
|
-**复用决策**:
|
|
|
-- **现有组件**: `functions/result_aggregation.py`
|
|
|
-- **决策**: **复用并修改**
|
|
|
-- **理由**: 可复用结果聚合逻辑,需新增树状结构构建
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 6. 数据结构设计
|
|
|
-
|
|
|
-### 6.1 WhatDeconstructionState(工作流状态)
|
|
|
-
|
|
|
-**PRD依据**: 2.1节(输入)、2.2节(输出)
|
|
|
-
|
|
|
-```python
|
|
|
-from typing import TypedDict, List, Dict, Any, Optional
|
|
|
-
|
|
|
-class WhatDeconstructionState(TypedDict):
|
|
|
- """What解构工作流状态
|
|
|
-
|
|
|
- PRD依据: 2.1节、2.2节
|
|
|
- """
|
|
|
-
|
|
|
- # ========== 输入数据(PRD 2.1节) ==========
|
|
|
- images: List[str] # 图片路径列表(1-9张)
|
|
|
- text: Dict[str, Any] # {title, body, hashtags}
|
|
|
- comments: List[Dict[str, str]] # [{user, content}, ...]
|
|
|
- creator_info: Optional[Dict] # 创作者信息(可选)
|
|
|
-
|
|
|
- # ========== 中间状态 ==========
|
|
|
- category: str # 品类(F1输出)
|
|
|
- theme: str # 主题(F1输出)
|
|
|
- keywords: List[str] # 关键词(F1输出)
|
|
|
- consumer_highlights: List[Dict] # 消费者亮点(F4输出)
|
|
|
-
|
|
|
- # ========== 解构结果 ==========
|
|
|
- image_deconstruction_results: List[Dict] # 图片解构结果(F5输出)
|
|
|
- text_deconstruction_results: List[Dict] # 文本解构结果(F7输出)
|
|
|
- post_summary: Dict[str, Any] # 帖子总结(F9输出)
|
|
|
-
|
|
|
- # ========== 最终输出(PRD 2.2.2节) ==========
|
|
|
- final_result: Dict[str, Any] # 树状JSON结果
|
|
|
-```
|
|
|
-
|
|
|
-### 6.2 ElementNode(元素节点结构)
|
|
|
-
|
|
|
-**PRD依据**: 2.2.2节 "输出格式定义"
|
|
|
-
|
|
|
-```python
|
|
|
-class ElementNode(TypedDict):
|
|
|
- """元素节点结构(递归树的节点)
|
|
|
-
|
|
|
- PRD依据: 2.2.2节
|
|
|
- """
|
|
|
-
|
|
|
- id: str # 节点ID(如"1", "1_1", "1_2_3")
|
|
|
- what: str # 元素概述(文本原文 or 图片描述)
|
|
|
- 描述: Dict[str, Any] # 动态描述维度(≥3个)
|
|
|
- 子节点元素关系: List[str] # 子节点之间的关系描述
|
|
|
- 元素重要性权重: float # 0-1之间
|
|
|
- 子节点元素: List['ElementNode'] # 递归的子节点列表
|
|
|
-```
|
|
|
-
|
|
|
-### 6.3 FinalOutput(最终输出结构)
|
|
|
-
|
|
|
-**PRD依据**: 2.2.2节
|
|
|
-
|
|
|
-```python
|
|
|
-class FinalOutput(TypedDict):
|
|
|
- """最终输出的JSON结构
|
|
|
-
|
|
|
- PRD依据: 2.2.2节
|
|
|
- """
|
|
|
-
|
|
|
- 帖子总结: Dict[str, Any] # 动态总结维度(≥3个)
|
|
|
- 帖子包含元素: List[ElementNode] # 元素树的根节点列表
|
|
|
-```
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 7. 实现路线图
|
|
|
-
|
|
|
-### Phase 1: 基础框架搭建
|
|
|
-
|
|
|
-**目标**: 建立工作流骨架和基础组件
|
|
|
-
|
|
|
-| 任务 | 组件 | 优先级 | 依赖 |
|
|
|
-|------|------|--------|------|
|
|
|
-| 1.1 定义数据结构 | WhatDeconstructionState | P0 | 无 |
|
|
|
-| 1.2 实现PostUnderstandingAgent | Agent | P0 | 1.1 |
|
|
|
-| 1.3 修改KnowledgeRetrievalTool | Tool | P0 | 无 |
|
|
|
-| 1.4 搭建工作流骨架 | Workflow | P0 | 1.1, 1.2, 1.3 |
|
|
|
-| 1.5 端到端测试(简化版) | - | P0 | 1.4 |
|
|
|
-
|
|
|
-**验收标准**:
|
|
|
-- 工作流能够运行
|
|
|
-- PostUnderstandingAgent能够输出品类/主题/关键词
|
|
|
-- 知识库工具能够返回模拟数据
|
|
|
-
|
|
|
-### Phase 2: 核心解构能力
|
|
|
-
|
|
|
-**目标**: 实现图片和文本的递归解构
|
|
|
-
|
|
|
-| 任务 | 组件 | 优先级 | 依赖 |
|
|
|
-|------|------|--------|------|
|
|
|
-| 2.1 实现RecursiveImageDeconstructionAgent | Agent | P0 | Phase 1 |
|
|
|
-| 2.2 复用ImageSegmentTool | Tool | P0 | 2.1 |
|
|
|
-| 2.3 实现RecursiveTextDeconstructionAgent | Agent | P0 | Phase 1 |
|
|
|
-| 2.4 实现TextSplitFunction | Function | P0 | 2.3 |
|
|
|
-| 2.5 集成测试(单个图片+单个文本) | - | P0 | 2.1-2.4 |
|
|
|
-
|
|
|
-**验收标准**:
|
|
|
-- 能够递归解构单张图片(至少2层)
|
|
|
-- 能够递归解构单段文本(至少2层)
|
|
|
-- 递归深度控制生效(不超过10层)
|
|
|
-
|
|
|
-### Phase 3: 增强功能
|
|
|
-
|
|
|
-**目标**: 完成评论理解、帖子总结、结果汇总
|
|
|
-
|
|
|
-| 任务 | 组件 | 优先级 | 依赖 |
|
|
|
-|------|------|--------|------|
|
|
|
-| 3.1 实现CommentAnalysisAgent | Agent | P1 | Phase 2 |
|
|
|
-| 3.2 实现PostSummaryDeconstructionAgent | Agent | P1 | Phase 2 |
|
|
|
-| 3.3 修改ResultAggregationFunction | Function | P0 | Phase 2 |
|
|
|
-| 3.4 完整端到端测试 | - | P0 | 3.1-3.3 |
|
|
|
-
|
|
|
-**验收标准**:
|
|
|
-- 能够分析评论并提取亮点
|
|
|
-- 能够生成帖子整体总结
|
|
|
-- 能够输出符合PRD 2.2.2节格式的完整JSON
|
|
|
-
|
|
|
-### Phase 4: 完善优化
|
|
|
-
|
|
|
-**目标**: 优化性能、容错、测试覆盖
|
|
|
-
|
|
|
-| 任务 | 优先级 |
|
|
|
-|------|--------|
|
|
|
-| 4.1 递归深度优化(提前停止) | P2 |
|
|
|
-| 4.2 错误处理和降级策略 | P1 |
|
|
|
-| 4.3 并行执行优化 | P2 |
|
|
|
-| 4.4 单元测试覆盖 | P1 |
|
|
|
-| 4.5 性能基准测试 | P2 |
|
|
|
-
|
|
|
-**验收标准**:
|
|
|
-- 单元测试覆盖率 > 80%
|
|
|
-- 错误情况下有合理降级
|
|
|
-- 性能满足预期(待定义)
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 8. 决策依据总结表
|
|
|
-
|
|
|
-### 8.1 工作流设计决策
|
|
|
-
|
|
|
-| 决策点 | 选择 | PRD依据 | CLAUDE.md依据 | 技术理由 |
|
|
|
-|--------|------|---------|---------------|---------|
|
|
|
-| 工作流框架 | LangGraph | - | 要求使用LangGraph | 支持状态管理、节点编排 |
|
|
|
-| 并行执行 | 图片和文本并行 | 3.1节流程图 | 工作流负责编排 | 两者无依赖,可并行提升效率 |
|
|
|
-| 递归深度 | 最多10层 | 2.2.3节 | - | 防止无限递归,控制成本 |
|
|
|
-| 知识query生成 | 工作流逻辑 | 3.2节 | 轻量级逻辑不需组件 | 简单字符串拼接 |
|
|
|
-
|
|
|
-### 8.2 组件类型选择决策
|
|
|
-
|
|
|
-| 组件 | 类型 | PRD依据 | CLAUDE.md依据 | 选择理由 |
|
|
|
-|------|------|---------|---------------|---------|
|
|
|
-| PostUnderstandingAgent | Agent | 3.1步骤1 | 多模态理解(不确定性) | 需要LLM理解图文 |
|
|
|
-| CommentAnalysisAgent | Agent | 2.1.4节 | 语义理解(不确定性) | 需要LLM情感分析 |
|
|
|
-| RecursiveImageDeconstructionAgent | Agent (ReAct) | 3.3节 | 视觉理解+工具调用 | 需要多次调用知识库和分割工具 |
|
|
|
-| RecursiveTextDeconstructionAgent | Agent (ReAct) | 3.3节 | 语义理解+工具调用 | 需要多次调用知识库 |
|
|
|
-| PostSummaryDeconstructionAgent | Agent | 3.2节 | 综合分析(不确定性) | 需要LLM抽象提炼 |
|
|
|
-| KnowledgeRetrievalTool | Tool | 3.3.2节 | Agent需调用 | 确定性API调用 |
|
|
|
-| ImageSegmentTool | Tool | 3.3.1节 | Agent需调用 | 确定性模型调用 |
|
|
|
-| TextSplitFunction | Function | 3.5节 | 确定性+工作流调用 | 简单字符串处理 |
|
|
|
-| ResultAggregationFunction | Function | 2.2.2节 | 确定性+工作流调用 | 数据结构转换 |
|
|
|
-
|
|
|
-### 8.3 复用决策
|
|
|
-
|
|
|
-| 组件 | 决策 | 理由 | 修改点 |
|
|
|
-|------|------|------|--------|
|
|
|
-| knowledge_retrieval_tools.py | 复用并修改 | 已有基础查询功能 | 增强query类型支持、降级策略 |
|
|
|
-| segment_tools.py | 直接复用 | 功能完全匹配 | 无 |
|
|
|
-| result_aggregation.py | 复用并修改 | 可复用聚合逻辑 | 新增树状结构构建 |
|
|
|
-| TextSplitFunction | 完全新增 | 项目中无此功能 | - |
|
|
|
-| 所有Agent | 完全新增 | PRD业务逻辑全新 | - |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## 附录: Query模板速查表
|
|
|
-
|
|
|
-**PRD依据**: 3.2节、3.3.2节
|
|
|
-
|
|
|
-| Query类型 | 句式模板 | 使用场景 |
|
|
|
-|-----------|---------|---------|
|
|
|
-| 帖子整体总结维度 | 对于一篇主题为"{主题}",品类为"{品类}",关键词包含"{关键词}"的多模态社交媒体帖子,从内容创作者视角进行What要素的初步识别和分类,需要使用哪些通用工具? | PostSummaryDeconstructionAgent |
|
|
|
-| 元素描述维度 | 刻画描述"{元素的what字段值}"核心特征的角度和维度有哪些?请尽可能不重不漏列举全。 | 所有递归解构Agent |
|
|
|
-| 分割判断 | 该节点"{元素的what字段值}"是否需要继续拆解分割? | 所有递归解构Agent |
|
|
|
-| 图片整体分割工具 | 对一张描述为"{图片描述}"的图片,从内容创作者视角进行视觉元素分割,需要使用哪些图片分割/抠图工具? | RecursiveImageDeconstructionAgent |
|
|
|
-| 文本段落分割工具 | 对一段描述为"{文本摘要}"的文本段落,从内容创作者视角进行结构化分割拆解,需要使用什么文本切分工具或文本结构化大模型? | RecursiveTextDeconstructionAgent |
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-**文档版本**: v2.0
|
|
|
-**最后更新**: 2025-10-21
|
|
|
-**适用PRD版本**: v1.3
|