feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
服务调用节点处理器
|
||||
处理外部 HTTP 服务调用
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, Optional, TYPE_CHECKING
|
||||
|
||||
import httpx
|
||||
|
||||
from online_dev.workflow.engine.handlers.base_handler import BaseNodeHandler
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from online_dev.workflow.engine.base import ExecutionContext
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceHandler(BaseNodeHandler):
|
||||
"""
|
||||
服务调用节点处理器
|
||||
|
||||
支持:
|
||||
- HTTP 方法: GET/POST/PUT/DELETE/PATCH
|
||||
- 请求头配置
|
||||
- 参数变量替换
|
||||
- 重试机制
|
||||
- 失败处理策略
|
||||
"""
|
||||
|
||||
async def execute(self, context: 'ExecutionContext', node: Dict) -> None:
|
||||
"""
|
||||
执行服务调用节点
|
||||
"""
|
||||
node_id = self.get_node_id(node)
|
||||
node_name = self.get_node_name(node)
|
||||
node_config = self.get_node_config(node)
|
||||
|
||||
url = node_config.get('url', '')
|
||||
method = node_config.get('method', 'POST')
|
||||
headers = {h['key']: h['value'] for h in node_config.get('headers', []) if h.get('key')}
|
||||
params = node_config.get('params', '')
|
||||
body = node_config.get('body', '')
|
||||
timeout = node_config.get('timeout', 30)
|
||||
retry_count = node_config.get('retryCount', 0)
|
||||
fail_action = node_config.get('failAction', 'stop')
|
||||
result_variable = node_config.get('resultVariable', '')
|
||||
|
||||
logger.info(f"服务调用节点 - {method} {url}")
|
||||
|
||||
# 更新实例当前节点
|
||||
await self.update_instance_node(context, node)
|
||||
|
||||
success = False
|
||||
response_data = None
|
||||
error_message = ''
|
||||
|
||||
# 尝试调用服务
|
||||
async with httpx.AsyncClient() as client:
|
||||
for attempt in range(retry_count + 1):
|
||||
try:
|
||||
# 解析参数和请求体(支持变量替换)
|
||||
parsed_params = self._parse_params(params, context.form_data)
|
||||
parsed_body = self._parse_params(body, context.form_data)
|
||||
|
||||
response = await client.request(
|
||||
method=method,
|
||||
url=url,
|
||||
headers=headers,
|
||||
params=parsed_params if method == 'GET' else None,
|
||||
json=parsed_body if method != 'GET' and parsed_body else None,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
response.raise_for_status()
|
||||
response_data = response.json() if response.text else {}
|
||||
success = True
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
error_message = str(e)
|
||||
logger.warning(f"服务调用失败 (尝试 {attempt + 1}/{retry_count + 1}): {e}")
|
||||
|
||||
# 记录日志
|
||||
await self.create_log(
|
||||
context, node, 'service_call',
|
||||
comment=f"{'成功' if success else '失败'}: {method} {url}",
|
||||
extra_data={
|
||||
'url': url,
|
||||
'method': method,
|
||||
'success': success,
|
||||
'response': response_data,
|
||||
'error': error_message,
|
||||
},
|
||||
)
|
||||
|
||||
if success:
|
||||
# 存储结果到流程变量
|
||||
if result_variable and response_data:
|
||||
context.form_data[result_variable] = response_data
|
||||
await self.advance_to_next(context, node)
|
||||
else:
|
||||
# 处理失败
|
||||
if fail_action == 'continue':
|
||||
logger.warning(f"服务调用失败,继续流程")
|
||||
await self.advance_to_next(context, node)
|
||||
elif fail_action == 'stop':
|
||||
logger.error(f"服务调用失败,终止流程")
|
||||
await self.engine._end_instance(context, 'rejected')
|
||||
|
||||
def _parse_params(self, params_str: str, form_data: Dict) -> Optional[Dict]:
|
||||
"""
|
||||
解析服务调用参数,支持变量替换
|
||||
|
||||
变量格式: ${field_name}
|
||||
"""
|
||||
if not params_str:
|
||||
return None
|
||||
|
||||
# 替换变量
|
||||
def replace_var(match):
|
||||
var_name = match.group(1)
|
||||
return str(form_data.get(var_name, ''))
|
||||
|
||||
replaced = re.sub(r'\$\{(\w+)\}', replace_var, params_str)
|
||||
|
||||
try:
|
||||
return json.loads(replaced)
|
||||
except json.JSONDecodeError:
|
||||
return None
|
||||
Reference in New Issue
Block a user