diff --git a/config/agent_task_template.txt b/config/agent_task_template.txt index 85a6515..b307b8f 100644 --- a/config/agent_task_template.txt +++ b/config/agent_task_template.txt @@ -1,11 +1,11 @@ ## 任务背景 -### 内容库搜索结果 -以下是从内容库中搜索到的相关文章数据位置: +### 内容库相关数据ID列表 +以下是从内容库中搜索到的相关文章数据ID: {{library_results}} -### 互联网搜索抓取内容 -以下是从互联网搜索并抓取的网页内容数据位置: +### 互联网搜索已入库数据ID列表 +以下是从互联网搜索并已成功抓取入库的数据ID: {{internet_results}} ## 产品信息 @@ -15,17 +15,39 @@ ## 任务要求 -请从上述搜索结果和抓取内容中,提取出与产品「{{product_name}}」直接相关的具体内容。 +请分析上述内容库ID对应的数据,判断哪些与产品「{{product_name}}」**直接相关**且**对提取产品参数有用**。 -要求: -1. 只提取与该产品直接相关的信息,排除其他无关产品的内容 -2. 提取的内容应包括但不限于:产品参数、规格、功能描述、发布信息、技术特点等 -3. 注明每条信息的来源(URL或文章标题) -4. 如果某些信息在多个来源中都有提及,请综合整理 -5. 严格按照原始数据提取,不要编造或推测任何内容 +### 判断标准: +1. **直接相关性**:内容必须明确提及该产品名称或其主要型号,排除仅提及相似产品或竞品的内容 +2. **参数提取价值**:内容应包含可用于填充产品字段的信息,如: + - 产品规格参数(尺寸、重量、容量等) + - 技术规格(性能指标、接口、兼容性等) + - 功能特性 + - 发布信息(发布日期、价格等) + - 其他结构化产品数据 -请将提取结果以JSON格式输出,包含以下字段: -- name: 产品名称 -- extracted_fields: 提取到的字段键值对 -- sources: 信息来源列表 -- confidence: 提取置信度(high/medium/low) +### 输出要求: +请以JSON格式输出分析结果,包含以下字段: +```json +{ + "relevant_ids": [1, 2, 3], + "analysis": { + "1": "简要说明为什么这条数据相关且有用", + "2": "...", + "3": "..." + }, + "excluded_ids": [4, 5], + "exclusion_reasons": { + "4": "简要说明排除原因", + "5": "..." + }, + "confidence": "high/medium/low" +} +``` + +**注意:** +- `relevant_ids`:与产品直接相关且对参数提取有用的数据ID列表 +- `analysis`:每个相关ID的简要分析说明 +- `excluded_ids`:被排除的ID列表(可选) +- `exclusion_reasons`:排除原因说明(可选) +- `confidence`:整体判断的置信度 diff --git a/routes/process_monitor.py b/routes/process_monitor.py index 11747a5..44e7cfc 100644 --- a/routes/process_monitor.py +++ b/routes/process_monitor.py @@ -248,8 +248,8 @@ def preview_agent_template(): filled = template.replace('{{product_name}}', product_name) filled = filled.replace('{{category}}', category) filled = filled.replace('{{subcategory}}', subcategory or '无') - filled = filled.replace('{{library_results}}', '[内容库搜索结果将在此处列出,包含文章标题、URL、摘要等]') - filled = filled.replace('{{internet_results}}', '[互联网抓取内容将在此处列出,包含URL、标题、正文片段等]') + filled = filled.replace('{{library_results}}', 'ID 101: 示例文章标题A\nID 102: 示例文章标题B\nID 103: 示例文章标题C') + filled = filled.replace('{{internet_results}}', 'ID 104: 示例互联网抓取标题X\nID 105: 示例互联网抓取标题Y') return jsonify({ 'success': True, diff --git a/services/process_monitor.py b/services/process_monitor.py index 5a7c6aa..002d901 100644 --- a/services/process_monitor.py +++ b/services/process_monitor.py @@ -115,17 +115,18 @@ class ProcessMonitor: if fetch_result.get('success'): title = fetch_result.get('title', '') content = fetch_result.get('content', '') - fetched.append({ - 'url': url, - 'title': title, - 'content': content[:500] - }) + article_id = None # 保存到内容库 try: existing = db.search_articles(url) - if not any(a.get('url') == url for a in existing): - db.add_article( + if existing and len(existing) > 0: + # 已存在,使用现有ID + article_id = existing[0].get('id') + logger.info(f"[{session_id}] 内容库已存在: {title[:30]}, ID={article_id}") + else: + # 新增,获取返回的ID + article_id = db.add_article( product_names=[], category=category or '', keywords=[], @@ -135,9 +136,16 @@ class ProcessMonitor: url=url, search_title=title ) - logger.info(f"[{session_id}] 已保存到内容库: {title[:30]}") + logger.info(f"[{session_id}] 已保存到内容库: {title[:30]}, ID={article_id}") except Exception as save_error: logger.warning(f"[{session_id}] 保存内容库失败: {save_error}") + + fetched.append({ + 'id': article_id, + 'url': url, + 'title': title, + 'content': content[:500] + }) else: # 记录失败URL failed_count += 1 @@ -165,35 +173,54 @@ class ProcessMonitor: product_name, category, subcategory, all_data ) - # 记录任务文本 - self._complete_step(session_id, 4, { - 'agent': 'hz4th_editor', - 'task_text': task_text, - 'status': 'calling_agent' - }) - # 调用智能体 agent_result = self._call_agent(task_text) if agent_result.get('success'): - extracted = self._parse_agent_response(agent_result.get('output', '')) - all_data['extracted_data'] = extracted + parsed = self._parse_agent_response(agent_result.get('output', '')) - if extracted: + if parsed and parsed.get('relevant_ids'): + # 根据ID从内容库获取实际内容 + relevant_contents = [] + for aid in parsed['relevant_ids']: + article = db.get_article_by_id(aid) + if article: + relevant_contents.append({ + 'id': aid, + 'title': article.get('search_title', ''), + 'url': article.get('url', ''), + 'content': article.get('content', ''), + 'summary': article.get('summary', ''), + 'analysis': parsed.get('analysis', {}).get(str(aid), '') + }) + + all_data['extracted_data'] = { + 'name': product_name, + 'relevant_ids': parsed['relevant_ids'], + 'relevant_contents': relevant_contents, + 'confidence': parsed.get('confidence', 'unknown'), + 'raw_output': agent_result.get('output', '') + } + self._complete_step(session_id, 4, { 'has_data': True, 'agent': 'hz4th_editor', 'task_text': task_text, + 'relevant_ids': parsed['relevant_ids'], + 'relevant_count': len(relevant_contents), + 'confidence': parsed.get('confidence', 'unknown'), 'agent_output': agent_result.get('output', '')[:2000] }) + logger.info(f"[{session_id}] 步骤4完成: 智能体返回 {len(parsed['relevant_ids'])} 个相关ID") else: + all_data['extracted_data'] = None self._complete_step(session_id, 4, { 'has_data': False, 'agent': 'hz4th_editor', 'task_text': task_text, 'agent_output': agent_result.get('output', '')[:2000] }, status='skipped') - result['message'] = '智能体无法提取有效数据' + result['message'] = '智能体未找到相关数据ID' else: self._fail_step(session_id, 4, f"智能体调用失败: {agent_result.get('error', '未知错误')}") result['message'] = f'智能体调用失败: {agent_result.get("error")}' @@ -347,30 +374,30 @@ class ProcessMonitor: else: # 默认模板 template = ( - "请从以下数据中提取产品「{{product_name}}」的相关内容。\n" + "请分析以下数据ID是否与产品「{{product_name}}」相关且对提取参数有用。\n" "类别: {{category}} / {{subcategory}}\n\n" - "内容库结果:\n{{library_results}}\n\n" - "互联网抓取内容:\n{{internet_results}}\n\n" - "要求:只提取与该产品信息直接相关的内容,排除无关产品。以JSON格式输出。" + "内容库结果ID: {{library_results}}\n\n" + "互联网已入库ID: {{internet_results}}\n\n" + "要求:输出相关且有用的ID列表,以JSON格式输出。" ) - # 构建内容库搜索结果 - library_lines = [] - for i, article in enumerate(all_data.get('library_results', [])[:10], 1): - title = article.get('search_title', article.get('title', '无标题')) - url = article.get('url', article.get('source', '无URL')) - summary = article.get('summary', '')[:200] - library_lines.append(f" [{i}] 标题: {title}\n URL: {url}\n 摘要: {summary}") - library_text = '\n'.join(library_lines) if library_lines else '(无内容库搜索结果)' + # 构建内容库搜索结果ID列表 + library_ids = [] + for article in all_data.get('library_results', []): + aid = article.get('id') + if aid: + title = article.get('search_title', article.get('title', '')) + library_ids.append(f"ID {aid}: {title}") + library_text = '\n'.join(library_ids) if library_ids else '(无内容库搜索结果)' - # 构建互联网抓取内容 - internet_lines = [] - for i, item in enumerate(all_data.get('fetched_contents', [])[:10], 1): - title = item.get('title', '无标题') - url = item.get('url', '无URL') - content = item.get('content', '')[:300] - internet_lines.append(f" [{i}] 标题: {title}\n URL: {url}\n 内容片段: {content}") - internet_text = '\n'.join(internet_lines) if internet_lines else '(无互联网抓取内容)' + # 构建互联网已入库数据ID列表 + internet_ids = [] + for item in all_data.get('fetched_contents', []): + aid = item.get('id') + if aid: + title = item.get('title', '') + internet_ids.append(f"ID {aid}: {title}") + internet_text = '\n'.join(internet_ids) if internet_ids else '(无互联网已入库数据)' # 填充模板 task = template.replace('{{product_name}}', product_name or '未知') @@ -416,47 +443,55 @@ class ProcessMonitor: return {'success': False, 'error': str(e)} def _parse_agent_response(self, output): - """解析智能体返回的结果""" + """解析智能体返回的结果,提取relevant_ids""" if not output: return None # 尝试从输出中提取JSON import re + parsed_data = None + # 查找JSON块 json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', output, re.DOTALL) if json_match: try: - data = json.loads(json_match.group(1)) - return { - 'name': data.get('name', ''), - 'extracted_fields': data.get('extracted_fields', {}), - 'sources': data.get('sources', []), - 'confidence': data.get('confidence', 'unknown'), - 'raw_output': output - } + parsed_data = json.loads(json_match.group(1)) except json.JSONDecodeError: pass # 尝试直接解析整个输出为JSON - try: - data = json.loads(output) + if not parsed_data: + try: + parsed_data = json.loads(output) + except json.JSONDecodeError: + pass + + if parsed_data: + relevant_ids = parsed_data.get('relevant_ids', []) + # 确保都是整数 + relevant_ids = [int(x) for x in relevant_ids if str(x).isdigit()] + return { - 'name': data.get('name', ''), - 'extracted_fields': data.get('extracted_fields', {}), - 'sources': data.get('sources', []), - 'confidence': data.get('confidence', 'unknown'), + 'relevant_ids': relevant_ids, + 'analysis': parsed_data.get('analysis', {}), + 'excluded_ids': parsed_data.get('excluded_ids', []), + 'exclusion_reasons': parsed_data.get('exclusion_reasons', {}), + 'confidence': parsed_data.get('confidence', 'unknown'), 'raw_output': output } - except json.JSONDecodeError: - pass - # 如果无法解析为JSON,将原始输出作为raw_content保存 - return { - 'name': '', - 'raw_content': output, - 'raw_output': output - } + # 无法解析为JSON,尝试从文本中提取ID + id_matches = re.findall(r'(?:ID|id)[\s:]*(\d+)', output) + if id_matches: + return { + 'relevant_ids': [int(x) for x in id_matches], + 'analysis': {}, + 'confidence': 'low', + 'raw_output': output + } + + return None def _extract_data(self, product_name, all_data): """提取产品数据(备用,已被智能体替代)""" @@ -488,23 +523,34 @@ class ProcessMonitor: import re filled = { - 'name': extracted_data['name'], + 'name': extracted_data.get('name', ''), 'visible': True, 'is_pinned': False } - content = extracted_data.get('raw_content', '') + # 从relevant_contents中拼接所有内容 + relevant_contents = extracted_data.get('relevant_contents', []) + all_content = '\n---\n'.join([ + c.get('content', '') or c.get('summary', '') + for c in relevant_contents + if c.get('content') or c.get('summary') + ]) - params_match = re.search(r'(\d+(?:\.\d+)?)\s*[Bb]', content) + # 兼容旧格式 + if not all_content: + all_content = extracted_data.get('raw_content', '') + + params_match = re.search(r'(\d+(?:\.\d+)?)\s*[Bb]', all_content) if params_match: filled['parameters'] = f"{params_match.group(1)}B" - date_match = re.search(r'(\d{4}[-/]\d{1,2}[-/]\d{1,2})', content) + date_match = re.search(r'(\d{4}[-/]\d{1,2}[-/]\d{1,2})', all_content) if date_match: filled['publish_date'] = date_match.group(1).replace('/', '-') filled['_source'] = 'auto_manager' filled['_extracted_at'] = datetime.now().isoformat() + filled['_relevant_ids'] = extracted_data.get('relevant_ids', []) return filled