feat: 步骤4提取产品数据改用智能体hz4th_editor执行
- 步骤4调用 openclaw agent --agent hz4th_editor --message 执行提取任务
- 新增任务文本模板(config/agent_task_template.txt),支持变量填充
- /process页面新增模板编辑面板,可查看/编辑/保存/预览模板
- 模板变量: {{product_name}} {{category}} {{subcategory}} {{library_results}} {{internet_results}}
- 新增API: GET/POST /api/process/agent-template, POST /api/process/agent-template/preview
This commit is contained in:
+166
-10
@@ -1,9 +1,11 @@
|
||||
"""
|
||||
处理步骤监控服务 - 记录和监控产品处理流程
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
import json
|
||||
import subprocess
|
||||
import threading
|
||||
import logging
|
||||
from datetime import datetime
|
||||
@@ -18,7 +20,7 @@ PROCESS_STEPS = [
|
||||
{'num': 1, 'name': '搜索内容库', 'description': '从内容库搜索相关文章'},
|
||||
{'num': 2, 'name': '搜索互联网', 'description': '从互联网搜索最新数据'},
|
||||
{'num': 3, 'name': '抓取网页内容', 'description': '抓取搜索结果网页的详细内容'},
|
||||
{'num': 4, 'name': '提取产品数据', 'description': '从抓取内容中提取产品相关数据'},
|
||||
{'num': 4, 'name': '提取产品数据(智能体)', 'description': '调用hz4th_editor智能体提取产品相关内容'},
|
||||
{'num': 5, 'name': '填充字段', 'description': '根据分类字段配置填充数据'},
|
||||
{'num': 6, 'name': '提交审核', 'description': '提交到ParamHub待审核区'},
|
||||
]
|
||||
@@ -154,18 +156,47 @@ class ProcessMonitor:
|
||||
except Exception as e:
|
||||
self._fail_step(session_id, 3, str(e))
|
||||
|
||||
# 步骤4: 提取产品数据
|
||||
# 步骤4: 提取产品数据(调用智能体执行)
|
||||
if not self._check_pause(session_id):
|
||||
self._start_step(session_id, product_name, 4, '提取产品数据')
|
||||
self._start_step(session_id, product_name, 4, '提取产品数据(智能体)')
|
||||
try:
|
||||
extracted = self._extract_data(product_name, all_data)
|
||||
all_data['extracted_data'] = extracted
|
||||
# 构建任务文本
|
||||
task_text = self._build_agent_task(
|
||||
product_name, category, subcategory, all_data
|
||||
)
|
||||
|
||||
if extracted:
|
||||
self._complete_step(session_id, 4, {'has_data': True})
|
||||
# 记录任务文本
|
||||
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
|
||||
|
||||
if extracted:
|
||||
self._complete_step(session_id, 4, {
|
||||
'has_data': True,
|
||||
'agent': 'hz4th_editor',
|
||||
'task_text': task_text,
|
||||
'agent_output': agent_result.get('output', '')[:2000]
|
||||
})
|
||||
else:
|
||||
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'] = '智能体无法提取有效数据'
|
||||
else:
|
||||
self._complete_step(session_id, 4, {'has_data': False}, status='skipped')
|
||||
result['message'] = '无法提取有效数据'
|
||||
self._fail_step(session_id, 4, f"智能体调用失败: {agent_result.get('error', '未知错误')}")
|
||||
result['message'] = f'智能体调用失败: {agent_result.get("error")}'
|
||||
except Exception as e:
|
||||
self._fail_step(session_id, 4, str(e))
|
||||
|
||||
@@ -302,8 +333,133 @@ class ProcessMonitor:
|
||||
return {'session': session, 'steps': steps}
|
||||
return None
|
||||
|
||||
def _build_agent_task(self, product_name, category, subcategory, all_data):
|
||||
"""构建智能体任务文本"""
|
||||
# 读取模板
|
||||
template_file = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)),
|
||||
'config', 'agent_task_template.txt'
|
||||
)
|
||||
|
||||
if os.path.exists(template_file):
|
||||
with open(template_file, 'r', encoding='utf-8') as f:
|
||||
template = f.read()
|
||||
else:
|
||||
# 默认模板
|
||||
template = (
|
||||
"请从以下数据中提取产品「{{product_name}}」的相关内容。\n"
|
||||
"类别: {{category}} / {{subcategory}}\n\n"
|
||||
"内容库结果:\n{{library_results}}\n\n"
|
||||
"互联网抓取内容:\n{{internet_results}}\n\n"
|
||||
"要求:只提取与该产品信息直接相关的内容,排除无关产品。以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 '(无内容库搜索结果)'
|
||||
|
||||
# 构建互联网抓取内容
|
||||
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 '(无互联网抓取内容)'
|
||||
|
||||
# 填充模板
|
||||
task = template.replace('{{product_name}}', product_name or '未知')
|
||||
task = task.replace('{{category}}', category or '未分类')
|
||||
task = task.replace('{{subcategory}}', subcategory or '无')
|
||||
task = task.replace('{{library_results}}', library_text)
|
||||
task = task.replace('{{internet_results}}', internet_text)
|
||||
|
||||
return task
|
||||
|
||||
def _call_agent(self, task_text):
|
||||
"""调用智能体执行任务"""
|
||||
try:
|
||||
cmd = [
|
||||
'openclaw', 'agent',
|
||||
'--agent', 'hz4th_editor',
|
||||
'--message', task_text
|
||||
]
|
||||
|
||||
logger.info(f"调用智能体命令: openclaw agent --agent hz4th_editor --message '[任务文本 {len(task_text)} 字符]'")
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300 # 5分钟超时
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
output = result.stdout.strip()
|
||||
logger.info(f"智能体返回: {output[:500]}...")
|
||||
return {'success': True, 'output': output}
|
||||
else:
|
||||
error = result.stderr.strip() or result.stdout.strip()
|
||||
logger.error(f"智能体调用失败: {error}")
|
||||
return {'success': False, 'error': error}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {'success': False, 'error': '智能体执行超时(>5分钟)'}
|
||||
except FileNotFoundError:
|
||||
return {'success': False, 'error': 'openclaw命令未找到'}
|
||||
except Exception as e:
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
def _parse_agent_response(self, output):
|
||||
"""解析智能体返回的结果"""
|
||||
if not output:
|
||||
return None
|
||||
|
||||
# 尝试从输出中提取JSON
|
||||
import re
|
||||
|
||||
# 查找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
|
||||
}
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 尝试直接解析整个输出为JSON
|
||||
try:
|
||||
data = json.loads(output)
|
||||
return {
|
||||
'name': data.get('name', ''),
|
||||
'extracted_fields': data.get('extracted_fields', {}),
|
||||
'sources': data.get('sources', []),
|
||||
'confidence': data.get('confidence', 'unknown'),
|
||||
'raw_output': output
|
||||
}
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
# 如果无法解析为JSON,将原始输出作为raw_content保存
|
||||
return {
|
||||
'name': '',
|
||||
'raw_content': output,
|
||||
'raw_output': output
|
||||
}
|
||||
|
||||
def _extract_data(self, product_name, all_data):
|
||||
"""提取产品数据"""
|
||||
"""提取产品数据(备用,已被智能体替代)"""
|
||||
all_content = []
|
||||
|
||||
for article in all_data.get('library_results', []):
|
||||
|
||||
Reference in New Issue
Block a user