Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c479916b |
@@ -0,0 +1,31 @@
|
||||
## 任务背景
|
||||
|
||||
### 内容库搜索结果
|
||||
以下是从内容库中搜索到的相关文章数据位置:
|
||||
{{library_results}}
|
||||
|
||||
### 互联网搜索抓取内容
|
||||
以下是从互联网搜索并抓取的网页内容数据位置:
|
||||
{{internet_results}}
|
||||
|
||||
## 产品信息
|
||||
- **产品名称:** {{product_name}}
|
||||
- **产品类别:** {{category}}
|
||||
- **子类别:** {{subcategory}}
|
||||
|
||||
## 任务要求
|
||||
|
||||
请从上述搜索结果和抓取内容中,提取出与产品「{{product_name}}」直接相关的具体内容。
|
||||
|
||||
要求:
|
||||
1. 只提取与该产品直接相关的信息,排除其他无关产品的内容
|
||||
2. 提取的内容应包括但不限于:产品参数、规格、功能描述、发布信息、技术特点等
|
||||
3. 注明每条信息的来源(URL或文章标题)
|
||||
4. 如果某些信息在多个来源中都有提及,请综合整理
|
||||
5. 严格按照原始数据提取,不要编造或推测任何内容
|
||||
|
||||
请将提取结果以JSON格式输出,包含以下字段:
|
||||
- name: 产品名称
|
||||
- extracted_fields: 提取到的字段键值对
|
||||
- sources: 信息来源列表
|
||||
- confidence: 提取置信度(high/medium/low)
|
||||
@@ -4,10 +4,15 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from services.process_monitor import process_monitor, PROCESS_STEPS
|
||||
from models.database import db
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('process_monitor_api')
|
||||
|
||||
# 模板文件路径
|
||||
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config')
|
||||
AGENT_TEMPLATE_FILE = os.path.join(TEMPLATE_DIR, 'agent_task_template.txt')
|
||||
|
||||
bp = Blueprint('process_monitor', __name__, url_prefix='/api/process')
|
||||
|
||||
|
||||
@@ -177,4 +182,79 @@ def get_step_detail(session_id, step_num):
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'step': step
|
||||
})
|
||||
})
|
||||
|
||||
@bp.route('/agent-template', methods=['GET'])
|
||||
def get_agent_template():
|
||||
"""获取智能体任务文本模板"""
|
||||
try:
|
||||
if os.path.exists(AGENT_TEMPLATE_FILE):
|
||||
with open(AGENT_TEMPLATE_FILE, 'r', encoding='utf-8') as f:
|
||||
template = f.read()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'template': template
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '模板文件不存在'
|
||||
}), 404
|
||||
except Exception as e:
|
||||
logger.error(f"获取模板失败: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@bp.route('/agent-template', methods=['POST'])
|
||||
def save_agent_template():
|
||||
"""保存智能体任务文本模板"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
template = data.get('template', '')
|
||||
|
||||
if not template:
|
||||
return jsonify({'success': False, 'error': '模板内容不能为空'}), 400
|
||||
|
||||
os.makedirs(TEMPLATE_DIR, exist_ok=True)
|
||||
with open(AGENT_TEMPLATE_FILE, 'w', encoding='utf-8') as f:
|
||||
f.write(template)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': '模板已保存'
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"保存模板失败: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@bp.route('/agent-template/preview', methods=['POST'])
|
||||
def preview_agent_template():
|
||||
"""预览填充后的任务文本"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
product_name = data.get('product_name', '示例产品')
|
||||
category = data.get('category', '示例类别')
|
||||
subcategory = data.get('subcategory', '示例子类别')
|
||||
|
||||
# 读取模板
|
||||
if os.path.exists(AGENT_TEMPLATE_FILE):
|
||||
with open(AGENT_TEMPLATE_FILE, 'r', encoding='utf-8') as f:
|
||||
template = f.read()
|
||||
else:
|
||||
return jsonify({'success': False, 'error': '模板文件不存在'}), 404
|
||||
|
||||
# 填充示例数据
|
||||
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、标题、正文片段等]')
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'preview': filled
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"预览模板失败: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
+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', []):
|
||||
|
||||
+52
-1
@@ -350,4 +350,55 @@
|
||||
.steps-progress {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 智能体任务模板区域 */
|
||||
.template-section .panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.template-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.template-info {
|
||||
background: #f0f4ff;
|
||||
border: 1px solid #c7d2fe;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 15px;
|
||||
font-size: 13px;
|
||||
color: #4338ca;
|
||||
}
|
||||
|
||||
.template-info p {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.template-info code {
|
||||
background: #e0e7ff;
|
||||
padding: 1px 5px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
color: #3730a3;
|
||||
}
|
||||
|
||||
.template-editor {
|
||||
width: 100%;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
padding: 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
resize: vertical;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.template-editor:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
loadStepDefinitions();
|
||||
loadActiveProcesses();
|
||||
loadHistory();
|
||||
loadAgentTemplate();
|
||||
|
||||
// 启动自动刷新(每2秒)
|
||||
startAutoRefresh();
|
||||
@@ -428,4 +429,83 @@ function showToast(message, type = '') {
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('active');
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ===== 智能体任务模板 =====
|
||||
|
||||
// 加载模板
|
||||
async function loadAgentTemplate() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/process/agent-template`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('agent-template-editor').value = data.template;
|
||||
} else {
|
||||
document.getElementById('agent-template-editor').value = '// 模板加载失败: ' + (data.error || '未知错误');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载模板失败:', error);
|
||||
document.getElementById('agent-template-editor').value = '// 加载模板失败: ' + error.message;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存模板
|
||||
async function saveTemplate() {
|
||||
const template = document.getElementById('agent-template-editor').value;
|
||||
|
||||
if (!template.trim()) {
|
||||
showToast('模板内容不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/process/agent-template`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ template })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('模板已保存 ✓', 'success');
|
||||
} else {
|
||||
showToast('保存失败: ' + data.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('保存失败: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 预览模板
|
||||
function previewTemplate() {
|
||||
document.getElementById('template-preview-modal').classList.add('active');
|
||||
doPreview();
|
||||
}
|
||||
|
||||
// 执行预览
|
||||
async function doPreview() {
|
||||
const product = document.getElementById('preview-product').value || '示例产品';
|
||||
const category = document.getElementById('preview-category').value || 'AI模型';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/api/process/agent-template/preview`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
product_name: product,
|
||||
category: category,
|
||||
subcategory: ''
|
||||
})
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('template-preview-content').textContent = data.preview;
|
||||
} else {
|
||||
document.getElementById('template-preview-content').textContent = '预览失败: ' + data.error;
|
||||
}
|
||||
} catch (error) {
|
||||
document.getElementById('template-preview-content').textContent = '预览失败: ' + error.message;
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 智能体任务模板区域 -->
|
||||
<div class="panel template-section">
|
||||
<div class="panel-header">
|
||||
<h2><i class="ri-robot-line"></i> 智能体任务文本模板(步骤4:提取产品数据)</h2>
|
||||
<div class="template-actions">
|
||||
<button onclick="previewTemplate()" class="btn btn-secondary btn-sm">
|
||||
<i class="ri-eye-line"></i> 预览
|
||||
</button>
|
||||
<button onclick="saveTemplate()" class="btn btn-primary btn-sm">
|
||||
<i class="ri-save-line"></i> 保存模板
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="template-info">
|
||||
<p><strong>说明:</strong>此模板用于步骤4「提取产品数据」中调用智能体 <code>hz4th_editor</code> 的任务文本。</p>
|
||||
<p><strong>可用变量:</strong>
|
||||
<code>{{product_name}}</code> 产品名称、
|
||||
<code>{{category}}</code> 类别、
|
||||
<code>{{subcategory}}</code> 子类别、
|
||||
<code>{{library_results}}</code> 内容库搜索结果、
|
||||
<code>{{internet_results}}</code> 互联网抓取内容
|
||||
</p>
|
||||
<p><strong>调用命令:</strong><code>openclaw agent --agent hz4th_editor --message "[填充后的任务文本]"</code></p>
|
||||
</div>
|
||||
<textarea id="agent-template-editor" class="template-editor" rows="20" placeholder="加载模板中..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 预览模态框 -->
|
||||
<div id="template-preview-modal" class="modal">
|
||||
<div class="modal-content large">
|
||||
<div class="modal-header">
|
||||
<h3><i class="ri-eye-line"></i> 任务文本预览</h3>
|
||||
<button onclick="closeModal('template-preview-modal')" class="close-btn">
|
||||
<i class="ri-close-line"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="preview-meta" style="margin-bottom: 15px;">
|
||||
<label>产品名称: <input type="text" id="preview-product" value="示例产品" style="padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px;"></label>
|
||||
<label style="margin-left: 10px;">类别: <input type="text" id="preview-category" value="AI模型" style="padding: 4px 8px; border: 1px solid #ddd; border-radius: 4px;"></label>
|
||||
<button onclick="doPreview()" class="btn btn-sm btn-secondary" style="margin-left: 10px;"><i class="ri-refresh-line"></i> 刷新预览</button>
|
||||
</div>
|
||||
<pre id="template-preview-content" style="background: #f8f9fa; padding: 15px; border-radius: 8px; white-space: pre-wrap; font-size: 13px; max-height: 500px; overflow-y: auto;">加载中...</pre>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button onclick="closeModal('template-preview-modal')" class="btn btn-secondary">关闭</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 处理历史区域 -->
|
||||
<div class="panel history-section">
|
||||
<div class="panel-header">
|
||||
|
||||
Reference in New Issue
Block a user