diff --git a/backend/app/api/agents.py b/backend/app/api/agents.py index 048e714..8643a9b 100644 --- a/backend/app/api/agents.py +++ b/backend/app/api/agents.py @@ -331,3 +331,69 @@ async def stop_agent( logger.info(f"用户 {current_user.username} 停止了Agent: {agent.name} ({agent_id})") return agent + + +class AgentDuplicateRequest(BaseModel): + """Agent复制请求模型""" + name: Optional[str] = None # 如果提供,使用此名称;否则自动生成 + + +@router.post("/{agent_id}/duplicate", response_model=AgentResponse, status_code=status.HTTP_201_CREATED) +async def duplicate_agent( + agent_id: str, + duplicate_data: Optional[AgentDuplicateRequest] = None, + db: Session = Depends(get_db), + current_user: User = Depends(get_current_user) +): + """ + 复制Agent + + 创建一个新的Agent副本,包含原Agent的所有配置(工作流、描述等) + 新Agent的状态为draft,版本号为1 + """ + # 获取原Agent + original_agent = db.query(Agent).filter(Agent.id == agent_id).first() + + if not original_agent: + raise NotFoundError(f"Agent不存在: {agent_id}") + + # 检查权限:read权限(需要能读取原Agent才能复制) + if not check_agent_permission(db, current_user, original_agent, "read"): + raise HTTPException(status_code=403, detail="无权复制此Agent") + + # 生成新名称 + if duplicate_data and duplicate_data.name: + new_name = duplicate_data.name + else: + # 自动生成名称:原名称 + " (副本)" + base_name = original_agent.name + new_name = f"{base_name} (副本)" + + # 如果名称已存在,添加序号 + counter = 1 + while db.query(Agent).filter( + Agent.name == new_name, + Agent.user_id == current_user.id + ).first(): + counter += 1 + new_name = f"{base_name} (副本 {counter})" + + # 深拷贝工作流配置(避免引用问题) + import copy + new_workflow_config = copy.deepcopy(original_agent.workflow_config) + + # 创建新Agent + new_agent = Agent( + name=new_name, + description=original_agent.description, + workflow_config=new_workflow_config, + user_id=current_user.id, + status="draft", # 复制的Agent状态为草稿 + version=1 # 版本号从1开始 + ) + db.add(new_agent) + db.commit() + db.refresh(new_agent) + + logger.info(f"用户 {current_user.username} 复制了Agent: {original_agent.name} ({agent_id}) -> {new_agent.name} ({new_agent.id})") + return new_agent diff --git a/backend/app/services/workflow_engine.py b/backend/app/services/workflow_engine.py index 1ac2bee..b55a81a 100644 --- a/backend/app/services/workflow_engine.py +++ b/backend/app/services/workflow_engine.py @@ -6,6 +6,7 @@ import asyncio from collections import defaultdict, deque import json import logging +import re from app.services.llm_service import llm_service from app.services.condition_parser import condition_parser from app.services.data_transformer import data_transformer @@ -123,10 +124,89 @@ class WorkflowEngine: else: # 否则合并所有输入 if isinstance(source_output, dict): - input_data.update(source_output) + # 如果source_output包含output字段,展开它 + if 'output' in source_output and isinstance(source_output['output'], dict): + # 将output中的内容展开到顶层 + input_data.update(source_output['output']) + # 保留其他字段(如status) + for key, value in source_output.items(): + if key != 'output': + input_data[key] = value + else: + input_data.update(source_output) else: input_data['input'] = source_output + # 如果input_data中没有query字段,尝试从所有已执行的节点中查找(特别是start节点) + if 'query' not in input_data: + # 优先查找start节点 + for node_id_key in ['start-1', 'start']: + if node_id_key in node_outputs: + node_output = node_outputs[node_id_key] + if isinstance(node_output, dict): + # 检查顶层字段(因为node_outputs存储的是output字段的内容) + if 'query' in node_output: + input_data['query'] = node_output['query'] + logger.debug(f"[rjb] 从节点 {node_id_key} 中获取query: {input_data['query']}") + break + # 检查output字段(兼容性) + elif 'output' in node_output and isinstance(node_output['output'], dict): + if 'query' in node_output['output']: + input_data['query'] = node_output['output']['query'] + logger.debug(f"[rjb] 从节点 {node_id_key} 的output中获取query: {input_data['query']}") + break + + # 如果还没找到,遍历所有节点 + if 'query' not in input_data: + for node_id_key, node_output in node_outputs.items(): + if isinstance(node_output, dict): + # 检查顶层字段 + if 'query' in node_output: + input_data['query'] = node_output['query'] + logger.debug(f"[rjb] 从节点 {node_id_key} 中获取query: {input_data['query']}") + break + # 检查output字段(兼容性) + elif 'output' in node_output and isinstance(node_output['output'], dict): + if 'query' in node_output['output']: + input_data['query'] = node_output['output']['query'] + logger.debug(f"[rjb] 从节点 {node_id_key} 的output中获取query: {input_data['query']}") + break + + # 如果input_data中没有requirement_analysis字段,尝试从所有已执行的节点中查找 + if 'requirement_analysis' not in input_data: + # 优先查找requirement-analysis节点 + for node_id_key in ['llm-requirement-analysis', 'requirement-analysis']: + if node_id_key in node_outputs: + node_output = node_outputs[node_id_key] + if isinstance(node_output, dict): + # 检查顶层字段(因为node_outputs存储的是output字段的内容) + if 'requirement_analysis' in node_output: + input_data['requirement_analysis'] = node_output['requirement_analysis'] + logger.debug(f"[rjb] 从节点 {node_id_key} 中获取requirement_analysis") + break + # 检查output字段(兼容性) + elif 'output' in node_output and isinstance(node_output['output'], dict): + if 'requirement_analysis' in node_output['output']: + input_data['requirement_analysis'] = node_output['output']['requirement_analysis'] + logger.debug(f"[rjb] 从节点 {node_id_key} 的output中获取requirement_analysis") + break + + # 如果还没找到,遍历所有节点 + if 'requirement_analysis' not in input_data: + for node_id_key, node_output in node_outputs.items(): + if isinstance(node_output, dict): + # 检查顶层字段 + if 'requirement_analysis' in node_output: + input_data['requirement_analysis'] = node_output['requirement_analysis'] + logger.debug(f"[rjb] 从节点 {node_id_key} 中获取requirement_analysis") + break + # 检查output字段(兼容性) + elif 'output' in node_output and isinstance(node_output['output'], dict): + if 'requirement_analysis' in node_output['output']: + input_data['requirement_analysis'] = node_output['output']['requirement_analysis'] + logger.debug(f"[rjb] 从节点 {node_id_key} 的output中获取requirement_analysis") + break + logger.debug(f"[rjb] 节点输入结果: node_id={node_id}, input_data={input_data}") return input_data @@ -344,27 +424,50 @@ class WorkflowEngine: try: # 将input_data转换为字符串用于格式化 if isinstance(input_data, dict): - # 如果prompt中包含变量,尝试格式化 - if '{' in prompt and '}' in prompt: - # 尝试格式化所有input_data中的键 - formatted_prompt = prompt - for key, value in input_data.items(): - placeholder = f'{{{key}}}' - if placeholder in formatted_prompt: - formatted_prompt = formatted_prompt.replace( - placeholder, - json_module.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else str(value) - ) - # 如果还有{input}占位符,替换为整个input_data - if '{input}' in formatted_prompt: + # 支持两种格式的变量:{key} 和 {{key}} + formatted_prompt = prompt + has_unfilled_variables = False + + # 首先处理 {{variable}} 格式(模板节点常用) + import re + double_brace_vars = re.findall(r'\{\{(\w+)\}\}', prompt) + for var_name in double_brace_vars: + if var_name in input_data: + # 替换 {{variable}} 为实际值 + value = input_data[var_name] + replacement = json_module.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else str(value) + formatted_prompt = formatted_prompt.replace(f'{{{{{var_name}}}}}', replacement) + else: + has_unfilled_variables = True + + # 然后处理 {key} 格式 + for key, value in input_data.items(): + placeholder = f'{{{key}}}' + if placeholder in formatted_prompt: formatted_prompt = formatted_prompt.replace( - '{input}', - json_module.dumps(input_data, ensure_ascii=False) + placeholder, + json_module.dumps(value, ensure_ascii=False) if isinstance(value, (dict, list)) else str(value) ) - prompt = formatted_prompt - else: - # 如果没有占位符,将input_data作为JSON附加到prompt - prompt = f"{prompt}\n\n输入数据:\n{json_module.dumps(input_data, ensure_ascii=False)}" + + # 如果还有{input}占位符,替换为整个input_data + if '{input}' in formatted_prompt: + formatted_prompt = formatted_prompt.replace( + '{input}', + json_module.dumps(input_data, ensure_ascii=False) + ) + + # 如果仍有未填充的变量({{variable}}格式),将用户输入作为上下文附加 + if has_unfilled_variables or re.search(r'\{\{(\w+)\}\}', formatted_prompt): + # 提取用户的实际查询内容 + user_query = input_data.get('query', input_data.get('input', input_data.get('text', ''))) + if not user_query and isinstance(input_data, dict): + # 如果没有明确的query字段,尝试从整个input_data中提取文本内容 + user_query = json_module.dumps(input_data, ensure_ascii=False) + + if user_query: + formatted_prompt = f"{formatted_prompt}\n\n用户需求:{user_query}\n\n请根据以上用户需求,忽略未填充的变量占位符(如{{{{variable}}}}),直接基于用户需求来完成任务。" + + prompt = formatted_prompt else: # 如果input_data不是dict,直接转换为字符串 if '{input}' in prompt: @@ -373,6 +476,7 @@ class WorkflowEngine: prompt = f"{prompt}\n\n输入:{str(input_data)}" except Exception as e: # 格式化失败,使用原始prompt和input_data + logger.warning(f"[rjb] Prompt格式化失败: {str(e)}") try: prompt = f"{prompt}\n\n输入数据:\n{json_module.dumps(input_data, ensure_ascii=False)}" except: @@ -381,8 +485,27 @@ class WorkflowEngine: # 获取LLM配置 provider = node_data.get('provider', 'openai') model = node_data.get('model', 'gpt-3.5-turbo') - temperature = node_data.get('temperature', 0.7) - max_tokens = node_data.get('max_tokens') + # 确保temperature是浮点数(节点模板中可能是字符串) + temperature_raw = node_data.get('temperature', 0.7) + if isinstance(temperature_raw, str): + try: + temperature = float(temperature_raw) + except (ValueError, TypeError): + temperature = 0.7 + else: + temperature = float(temperature_raw) if temperature_raw is not None else 0.7 + # 确保max_tokens是整数(节点模板中可能是字符串) + max_tokens_raw = node_data.get('max_tokens') + if max_tokens_raw is not None: + if isinstance(max_tokens_raw, str): + try: + max_tokens = int(max_tokens_raw) + except (ValueError, TypeError): + max_tokens = None + else: + max_tokens = int(max_tokens_raw) if max_tokens_raw is not None else None + else: + max_tokens = None # 不传递 api_key 和 base_url,让 LLM 服务使用系统默认配置(与节点测试保持一致) api_key = None base_url = None @@ -461,13 +584,68 @@ class WorkflowEngine: mode = node_data.get('mode', 'mapping') try: - result = data_transformer.transform_data( - input_data=input_data, - mapping=mapping, - filter_rules=filter_rules, - compute_rules=compute_rules, - mode=mode - ) + # 处理mapping中的{{variable}}格式,从input_data中提取值 + # 首先,如果input_data包含output字段,需要展开它 + expanded_input = input_data.copy() + if 'output' in input_data and isinstance(input_data['output'], dict): + # 将output中的内容展开到顶层,但保留output字段 + expanded_input.update(input_data['output']) + + processed_mapping = {} + import re + for target_key, source_expr in mapping.items(): + if isinstance(source_expr, str): + # 支持{{variable}}格式 + double_brace_vars = re.findall(r'\{\{(\w+)\}\}', source_expr) + if double_brace_vars: + # 从expanded_input中获取变量值 + var_value = None + for var_name in double_brace_vars: + # 尝试从expanded_input中获取,支持嵌套路径 + var_value = self._get_nested_value(expanded_input, var_name) + if var_value is not None: + break + + if var_value is not None: + # 如果只有一个变量,直接使用值;否则替换表达式 + if len(double_brace_vars) == 1: + processed_mapping[target_key] = var_value + else: + # 多个变量,替换表达式 + processed_expr = source_expr + for var_name in double_brace_vars: + var_val = self._get_nested_value(expanded_input, var_name) + if var_val is not None: + replacement = json_module.dumps(var_val, ensure_ascii=False) if isinstance(var_val, (dict, list)) else str(var_val) + processed_expr = processed_expr.replace(f'{{{{{var_name}}}}}', replacement) + processed_mapping[target_key] = processed_expr + else: + # 变量不存在,保持原表达式 + processed_mapping[target_key] = source_expr + else: + # 不是{{variable}}格式,直接使用 + processed_mapping[target_key] = source_expr + else: + # 不是字符串,直接使用 + processed_mapping[target_key] = source_expr + + # 如果mode是merge,需要合并所有输入数据 + if mode == 'merge': + # 合并所有上游节点的输出(使用展开后的数据) + result = expanded_input.copy() + # 添加mapping的结果 + for key, value in processed_mapping.items(): + result[key] = value + else: + # 使用处理后的mapping进行转换(使用展开后的数据) + result = data_transformer.transform_data( + input_data=expanded_input, + mapping=processed_mapping, + filter_rules=filter_rules, + compute_rules=compute_rules, + mode=mode + ) + exec_result = {'output': result, 'status': 'success'} if self.logger: duration = int((time.time() - start_time) * 1000) @@ -477,6 +655,7 @@ class WorkflowEngine: if self.logger: duration = int((time.time() - start_time) * 1000) self.logger.log_node_error(node_id, node_type, e, duration) + logger.error(f"[rjb] Transform节点执行失败: {str(e)}", exc_info=True) return { 'output': None, 'status': 'failed', @@ -1430,45 +1609,120 @@ class WorkflowEngine: elif node_type == 'output' or node_type == 'end': # 输出节点:返回最终结果 - # 对于人机交互场景,End节点应该返回纯文本字符串,而不是JSON - logger.debug(f"[rjb] End节点处理: node_id={node_id}, input_data={input_data}, input_data type={type(input_data)}") + # 读取节点配置中的输出格式设置 + node_data = node.get('data', {}) + output_format = node_data.get('output_format', 'text') # 默认纯文本 + + logger.debug(f"[rjb] End节点处理: node_id={node_id}, output_format={output_format}, input_data={input_data}, input_data type={type(input_data)}") final_output = input_data - # 递归解包,提取实际的文本内容 - if isinstance(input_data, dict): - # 如果只有一个 key 且是 'input',提取其值 - if len(input_data) == 1 and 'input' in input_data: - final_output = input_data['input'] - logger.debug(f"[rjb] End节点提取第一层: final_output={final_output}, type={type(final_output)}") - # 如果提取的值仍然是字典且只有一个 'input' key,继续提取 - if isinstance(final_output, dict) and len(final_output) == 1 and 'input' in final_output: - final_output = final_output['input'] - logger.debug(f"[rjb] End节点提取第二层: final_output={final_output}, type={type(final_output)}") - - # 确保最终输出是字符串(对于人机交互场景) - # 如果是字典,尝试转换为字符串;如果是其他类型,也转换为字符串 - if not isinstance(final_output, str): - if isinstance(final_output, dict): - # 如果是字典,尝试提取文本内容或转换为JSON字符串 - # 优先查找常见的文本字段 - if 'text' in final_output: - final_output = str(final_output['text']) - elif 'content' in final_output: - final_output = str(final_output['content']) - elif 'message' in final_output: - final_output = str(final_output['message']) - elif 'response' in final_output: - final_output = str(final_output['response']) - elif len(final_output) == 1: - # 如果只有一个key,直接使用其值 - final_output = str(list(final_output.values())[0]) - else: - # 否则转换为JSON字符串 - final_output = json_module.dumps(final_output, ensure_ascii=False) + # 如果配置为JSON格式,直接返回原始数据(或格式化的JSON) + if output_format == 'json': + # 如果是字典,直接返回JSON格式 + if isinstance(input_data, dict): + final_output = json_module.dumps(input_data, ensure_ascii=False, indent=2) + elif isinstance(input_data, str): + # 尝试解析为JSON,如果成功则格式化,否则直接返回 + try: + parsed = json_module.loads(input_data) + final_output = json_module.dumps(parsed, ensure_ascii=False, indent=2) + except: + final_output = input_data else: - final_output = str(final_output) + final_output = json_module.dumps({'output': input_data}, ensure_ascii=False, indent=2) + else: + # 默认纯文本格式:递归解包,提取实际的文本内容 + if isinstance(input_data, dict): + # 优先提取 'output' 字段(LLM节点的标准输出格式) + if 'output' in input_data and isinstance(input_data['output'], str): + final_output = input_data['output'] + # 如果只有一个 key 且是 'input',提取其值 + elif len(input_data) == 1 and 'input' in input_data: + final_output = input_data['input'] + # 如果包含 'solution' 字段,提取其值 + elif 'solution' in input_data and isinstance(input_data['solution'], str): + final_output = input_data['solution'] + # 如果input_data是字符串类型的字典(JSON字符串),尝试解析 + elif isinstance(input_data, str): + try: + parsed = json_module.loads(input_data) + if isinstance(parsed, dict) and 'output' in parsed: + final_output = parsed['output'] + elif isinstance(parsed, str): + final_output = parsed + except: + final_output = input_data + logger.debug(f"[rjb] End节点提取第一层: final_output={final_output}, type={type(final_output)}") + # 如果提取的值仍然是字典且只有一个 'input' key,继续提取 + if isinstance(final_output, dict) and len(final_output) == 1 and 'input' in final_output: + final_output = final_output['input'] + logger.debug(f"[rjb] End节点提取第二层: final_output={final_output}, type={type(final_output)}") + + # 确保最终输出是字符串(对于人机交互场景) + # 如果是字典,尝试转换为字符串;如果是其他类型,也转换为字符串 + if not isinstance(final_output, str): + if isinstance(final_output, dict): + # 如果是字典,尝试提取文本内容或转换为JSON字符串 + # 优先查找常见的文本字段 + if 'text' in final_output: + final_output = str(final_output['text']) + elif 'content' in final_output: + final_output = str(final_output['content']) + elif 'message' in final_output: + final_output = str(final_output['message']) + elif 'response' in final_output: + final_output = str(final_output['response']) + elif len(final_output) == 1: + # 如果只有一个key,直接使用其值 + final_output = str(list(final_output.values())[0]) + else: + # 否则转换为纯文本(不是JSON) + # 尝试提取所有文本字段并组合,但排除系统字段 + text_parts = [] + exclude_keys = {'status', 'error', 'timestamp', 'node_id', 'execution_time'} + for key, value in final_output.items(): + if key in exclude_keys: + continue + if isinstance(value, str) and value.strip(): + # 如果值本身已经包含 "key: " 格式,直接使用 + if value.strip().startswith(f"{key}:"): + text_parts.append(value.strip()) + else: + text_parts.append(value.strip()) + elif isinstance(value, (int, float, bool)): + text_parts.append(f"{key}: {value}") + if text_parts: + final_output = "\n".join(text_parts) + else: + final_output = str(final_output) + else: + final_output = str(final_output) + + # 清理输出文本:移除常见的字段前缀(如 "input: ", "query: " 等) + if isinstance(final_output, str): + import re + # 移除行首的 "input: ", "query: ", "output: " 等前缀 + lines = final_output.split('\n') + cleaned_lines = [] + for line in lines: + # 匹配行首的 "字段名: " 格式并移除 + # 但保留内容本身 + line = re.sub(r'^(input|query|output|result|response|message|content|text):\s*', '', line, flags=re.IGNORECASE) + if line.strip(): # 只保留非空行 + cleaned_lines.append(line) + + # 如果清理后还有内容,使用清理后的版本 + if cleaned_lines: + final_output = '\n'.join(cleaned_lines) + # 如果清理后为空,使用原始输出(避免丢失所有内容) + elif final_output.strip(): + # 如果原始输出不为空,但清理后为空,说明可能格式特殊,尝试更宽松的清理 + # 只移除明显的 "input: " 和 "query: " 前缀,保留其他内容 + final_output = re.sub(r'^(input|query):\s*', '', final_output, flags=re.IGNORECASE | re.MULTILINE) + if not final_output.strip(): + final_output = str(input_data) # 如果还是空,使用原始输入 - logger.debug(f"[rjb] End节点最终输出: final_output={final_output}, type={type(final_output)}") + logger.debug(f"[rjb] End节点最终输出: output_format={output_format}, final_output={final_output[:100] if isinstance(final_output, str) else type(final_output)}") result = {'output': final_output, 'status': 'success'} if self.logger: duration = int((time.time() - start_time) * 1000) diff --git a/backend/scripts/generate_complex_agent.py b/backend/scripts/generate_complex_agent.py new file mode 100755 index 0000000..df0811b --- /dev/null +++ b/backend/scripts/generate_complex_agent.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +""" +生成包含多个节点模板的复杂Agent +""" +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sqlalchemy.orm import Session +from app.core.database import SessionLocal +from app.models.agent import Agent +from app.models.user import User +from app.models.node_template import NodeTemplate +from datetime import datetime +import uuid + + +def generate_complex_agent_with_templates(db: Session, username: str = "admin"): + """生成包含多个节点模板的复杂Agent""" + print("=" * 60) + print("生成复杂Agent(包含多个节点模板)") + print("=" * 60) + print() + + # 查找用户 + user = db.query(User).filter(User.username == username).first() + if not user: + print(f"❌ 未找到用户 '{username}',请先创建该用户") + return + print(f"✅ 找到用户: {user.username} (ID: {user.id})") + print() + + # 查找可用的节点模板(公开的或用户自己的) + templates = db.query(NodeTemplate).filter( + (NodeTemplate.is_public == True) | (NodeTemplate.user_id == user.id) + ).limit(10).all() + + if len(templates) < 3: + print(f"⚠️ 警告: 只找到 {len(templates)} 个节点模板,建议至少3个") + if len(templates) == 0: + print("❌ 没有可用的节点模板,请先创建节点模板") + return + + print(f"📋 找到 {len(templates)} 个可用节点模板:") + for i, template in enumerate(templates[:5], 1): + print(f" {i}. {template.name} (ID: {template.id})") + print() + + # 选择3-5个模板用于创建复杂Agent + selected_templates = templates[:min(5, len(templates))] + template_ids = [t.id for t in selected_templates] + + # 生成复杂的工作流配置 + # 工作流结构:开始 -> 模板1 -> 条件判断 -> 模板2/模板3 -> 转换 -> 模板4 -> 结束 + nodes = [] + edges = [] + + # 开始节点 + start_node = { + "id": "start-1", + "type": "start", + "position": {"x": 100, "y": 200}, + "data": {"label": "开始"} + } + nodes.append(start_node) + + # 第一个模板节点 + if len(template_ids) > 0: + template1_node = { + "id": "template-1", + "type": "template", + "position": {"x": 300, "y": 200}, + "data": { + "label": selected_templates[0].name, + "template_id": template_ids[0], + "provider": selected_templates[0].provider, + "model": selected_templates[0].model, + "temperature": selected_templates[0].temperature, + "max_tokens": selected_templates[0].max_tokens, + "prompt": selected_templates[0].prompt + } + } + nodes.append(template1_node) + edges.append({ + "id": "e1", + "source": "start-1", + "target": "template-1" + }) + + # 条件判断节点 + condition_node = { + "id": "condition-1", + "type": "condition", + "position": {"x": 500, "y": 200}, + "data": { + "label": "判断处理结果", + "condition": "{{result}} contains '需要' or {{result}} contains '重要'" + } + } + nodes.append(condition_node) + if len(template_ids) > 0: + edges.append({ + "id": "e2", + "source": "template-1", + "target": "condition-1" + }) + + # 条件分支:如果满足条件,使用模板2;否则使用模板3 + if len(template_ids) > 1: + template2_node = { + "id": "template-2", + "type": "template", + "position": {"x": 700, "y": 100}, + "data": { + "label": selected_templates[1].name, + "template_id": template_ids[1], + "provider": selected_templates[1].provider, + "model": selected_templates[1].model, + "temperature": selected_templates[1].temperature, + "max_tokens": selected_templates[1].max_tokens, + "prompt": selected_templates[1].prompt + } + } + nodes.append(template2_node) + edges.append({ + "id": "e3", + "source": "condition-1", + "target": "template-2", + "sourceHandle": "true" + }) + + if len(template_ids) > 2: + template3_node = { + "id": "template-3", + "type": "template", + "position": {"x": 700, "y": 300}, + "data": { + "label": selected_templates[2].name, + "template_id": template_ids[2], + "provider": selected_templates[2].provider, + "model": selected_templates[2].model, + "temperature": selected_templates[2].temperature, + "max_tokens": selected_templates[2].max_tokens, + "prompt": selected_templates[2].prompt + } + } + nodes.append(template3_node) + edges.append({ + "id": "e4", + "source": "condition-1", + "target": "template-3", + "sourceHandle": "false" + }) + + # 数据转换节点 + transform_node = { + "id": "transform-1", + "type": "transform", + "position": {"x": 900, "y": 200}, + "data": { + "label": "数据转换", + "mode": "mapping", + "mapping": { + "final_result": "{{result}}", + "processed": "true" + } + } + } + nodes.append(transform_node) + + # 连接条件分支到转换节点 + if len(template_ids) > 1: + edges.append({ + "id": "e5", + "source": "template-2", + "target": "transform-1" + }) + if len(template_ids) > 2: + edges.append({ + "id": "e6", + "source": "template-3", + "target": "transform-1" + }) + + # 最后一个模板节点(如果还有模板) + if len(template_ids) > 3: + template4_node = { + "id": "template-4", + "type": "template", + "position": {"x": 1100, "y": 200}, + "data": { + "label": selected_templates[3].name, + "template_id": template_ids[3], + "provider": selected_templates[3].provider, + "model": selected_templates[3].model, + "temperature": selected_templates[3].temperature, + "max_tokens": selected_templates[3].max_tokens, + "prompt": selected_templates[3].prompt + } + } + nodes.append(template4_node) + edges.append({ + "id": "e7", + "source": "transform-1", + "target": "template-4" + }) + # 结束节点连接到最后一个模板 + end_node = { + "id": "end-1", + "type": "end", + "position": {"x": 1300, "y": 200}, + "data": {"label": "结束"} + } + nodes.append(end_node) + edges.append({ + "id": "e8", + "source": "template-4", + "target": "end-1" + }) + else: + # 如果没有更多模板,转换节点直接连接到结束节点 + end_node = { + "id": "end-1", + "type": "end", + "position": {"x": 1100, "y": 200}, + "data": {"label": "结束"} + } + nodes.append(end_node) + edges.append({ + "id": "e7", + "source": "transform-1", + "target": "end-1" + }) + + # 检查是否已存在同名Agent + agent_name = "复杂模板Agent(多节点)" + existing = db.query(Agent).filter( + Agent.name == agent_name, + Agent.user_id == user.id + ).first() + + if existing: + print(f"⚠️ Agent '{agent_name}' 已存在,将更新它...") + existing.workflow_config = {"nodes": nodes, "edges": edges} + existing.description = f"包含 {len(selected_templates)} 个节点模板的复杂Agent,支持条件分支和数据转换" + existing.updated_at = datetime.now() + agent = existing + else: + # 创建Agent + agent = Agent( + name=agent_name, + description=f"包含 {len(selected_templates)} 个节点模板的复杂Agent,支持条件分支和数据转换", + workflow_config={"nodes": nodes, "edges": edges}, + status="published", + user_id=user.id, + version=1 + ) + db.add(agent) + + try: + db.commit() + db.refresh(agent) + print() + print("=" * 60) + print(f"✅ 成功创建/更新复杂Agent: {agent.name}") + print(f" ID: {agent.id}") + print(f" 状态: {agent.status}") + print(f" 节点数量: {len(nodes)}") + print(f" 连接数量: {len(edges)}") + print(f" 使用的模板节点: {len(selected_templates)}") + print("=" * 60) + print() + print("工作流结构:") + print(" 开始 -> 模板1 -> 条件判断 -> 模板2/模板3 -> 数据转换 -> 模板4 -> 结束") + print() + return agent + except Exception as e: + db.rollback() + print() + print("=" * 60) + print(f"❌ 创建失败: {e}") + print("=" * 60) + import traceback + traceback.print_exc() + raise + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="生成包含多个节点模板的复杂Agent") + parser.add_argument( + "--username", + type=str, + default="admin", + help="创建Agent的用户名(默认: admin)" + ) + + args = parser.parse_args() + + db = SessionLocal() + try: + generate_complex_agent_with_templates(db, username=args.username) + except Exception as e: + print(f"❌ 发生错误: {e}") + import traceback + traceback.print_exc() + finally: + db.close() diff --git a/backend/scripts/generate_content_agent.py b/backend/scripts/generate_content_agent.py new file mode 100755 index 0000000..142bfaf --- /dev/null +++ b/backend/scripts/generate_content_agent.py @@ -0,0 +1,490 @@ +#!/usr/bin/env python3 +""" +生成内容生成助手Agent +这是一个专门用于内容生成的Agent,能够根据用户需求生成各种类型的内容 +""" +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sqlalchemy.orm import Session +from app.core.database import SessionLocal +from app.models.agent import Agent +from app.models.user import User +from app.models.node_template import NodeTemplate +from datetime import datetime +import uuid +import json + + +def generate_content_agent(db: Session, username: str = "admin"): + """生成内容生成助手Agent""" + print("=" * 60) + print("生成内容生成助手Agent") + print("=" * 60) + print() + + # 查找用户 + user = db.query(User).filter(User.username == username).first() + if not user: + print(f"❌ 未找到用户 '{username}',请先创建该用户") + return + print(f"✅ 找到用户: {user.username} (ID: {user.id})") + print() + + # 生成内容生成工作流配置 + # 工作流结构: + # 开始 -> 需求分析 -> 内容类型判断 -> [文章生成分支 | 文案生成分支 | 脚本生成分支] -> 内容优化 -> 格式检查 -> 结束 + + nodes = [] + edges = [] + + # 1. 开始节点 + start_node = { + "id": "start-1", + "type": "start", + "position": {"x": 50, "y": 300}, + "data": { + "label": "开始", + "output_format": "json" + } + } + nodes.append(start_node) + + # 2. 需求分析节点(LLM节点) + requirement_analysis_node = { + "id": "llm-requirement-analysis", + "type": "llm", + "position": {"x": 250, "y": 300}, + "data": { + "label": "需求分析", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.7", + "max_tokens": "1500", + "prompt": """你是一位专业的内容需求分析师。请分析用户的内容生成需求,提取关键信息。 + +用户输入:{{query}} + +请分析并提取以下信息: +1. **内容类型**:文章、博客、营销文案、视频脚本、产品描述、社交媒体内容等 +2. **主题/话题**:用户想要生成什么主题的内容 +3. **目标受众**:内容面向的读者群体 +4. **内容风格**:正式、轻松、专业、幽默等 +5. **字数要求**:大概的字数范围 +6. **特殊要求**:格式要求、关键词、结构要求等 + +请以JSON格式输出分析结果: +{ + "content_type": "内容类型", + "topic": "主题", + "target_audience": "目标受众", + "style": "内容风格", + "word_count": "字数要求", + "special_requirements": "特殊要求", + "keywords": ["关键词1", "关键词2"] +}""" + } + } + nodes.append(requirement_analysis_node) + edges.append({ + "id": "e1", + "source": "start-1", + "target": "llm-requirement-analysis" + }) + + # 3. 内容整合节点(Transform节点)- 用于传递数据 + content_prepare_node = { + "id": "transform-prepare", + "type": "transform", + "position": {"x": 450, "y": 300}, + "data": { + "label": "准备数据", + "mode": "merge", + "mapping": { + "requirement_analysis": "{{output}}", + "query": "{{query}}" + } + } + } + nodes.append(content_prepare_node) + edges.append({ + "id": "e2", + "source": "llm-requirement-analysis", + "target": "transform-prepare" + }) + + # 4. 内容类型判断节点(条件节点) + # 简化:直接根据需求分析结果判断,使用LLM输出中的关键词 + content_type_condition = { + "id": "condition-content-type", + "type": "condition", + "position": {"x": 650, "y": 300}, + "data": { + "label": "内容类型判断", + "condition": "{requirement_analysis} contains '文章' or {requirement_analysis} contains '博客' or {requirement_analysis} contains 'article'" + } + } + nodes.append(content_type_condition) + edges.append({ + "id": "e2-1", + "source": "transform-prepare", + "target": "condition-content-type" + }) + + # 4. 文章生成节点(LLM节点) + article_generation_node = { + "id": "llm-article-generation", + "type": "llm", + "position": {"x": 650, "y": 200}, + "data": { + "label": "文章生成", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.8", + "max_tokens": "4000", + "prompt": """你是一位专业的内容创作专家。请根据用户需求生成一篇高质量的文章。 + +**用户原始需求**:{{query}} + +**需求分析结果**: +- 主题:{{topic}} +- 目标受众:{{target_audience}} +- 内容风格:{{style}} +- 字数要求:{{word_count}} +- 特殊要求:{{special_requirements}} +- 关键词:{{keywords}} + +请生成一篇结构完整、内容丰富的文章,要求: +1. **标题**:吸引人且准确反映内容 +2. **引言**:引人入胜的开头,概述文章要点 +3. **正文**: + - 分多个段落,逻辑清晰 + - 每个段落有明确的主题 + - 使用适当的过渡词连接段落 + - 包含具体例子、数据或案例(如适用) +4. **结论**:总结要点,给出行动建议或思考 +5. **格式**:使用Markdown格式,包含标题、列表、加粗等 + +**重要**:确保内容完全符合用户需求,不要偏离主题。""" + } + } + nodes.append(article_generation_node) + + # 6. 文案生成节点(LLM节点) + copywriting_node = { + "id": "llm-copywriting", + "type": "llm", + "position": {"x": 850, "y": 300}, + "data": { + "label": "文案生成", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.9", + "max_tokens": "2000", + "prompt": """你是一位资深的营销文案专家。请根据用户需求生成吸引人的营销文案。 + +**用户原始需求**:{{query}} + +**需求分析结果**: +- 主题:{{topic}} +- 目标受众:{{target_audience}} +- 内容风格:{{style}} +- 字数要求:{{word_count}} +- 特殊要求:{{special_requirements}} +- 关键词:{{keywords}} + +请生成营销文案,要求: +1. **标题/口号**:简洁有力,吸引眼球 +2. **核心卖点**:突出产品或服务的核心价值 +3. **情感共鸣**:触动目标受众的情感 +4. **行动号召**:明确的行动指引 +5. **语言风格**:符合目标受众的阅读习惯 + +**重要**:确保文案具有说服力和吸引力,能够有效传达信息并促使行动。""" + } + } + nodes.append(copywriting_node) + + # 7. 脚本生成节点(LLM节点) + script_generation_node = { + "id": "llm-script-generation", + "type": "llm", + "position": {"x": 850, "y": 400}, + "data": { + "label": "脚本生成", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.8", + "max_tokens": "3000", + "prompt": """你是一位专业的视频/音频脚本创作专家。请根据用户需求生成专业的脚本。 + +**用户原始需求**:{{query}} + +**需求分析结果**: +- 主题:{{topic}} +- 目标受众:{{target_audience}} +- 内容风格:{{style}} +- 字数要求:{{word_count}} +- 特殊要求:{{special_requirements}} +- 关键词:{{keywords}} + +请生成脚本,要求: +1. **开场**:吸引注意力的开头 +2. **主体内容**: + - 分场景/段落 + - 标注说话者(如适用) + - 包含动作提示(如适用) + - 时间控制建议 +3. **结尾**:有力的总结或行动号召 +4. **格式**:清晰标注场景、角色、动作等 + +**重要**:确保脚本结构清晰,适合视频或音频制作。""" + } + } + nodes.append(script_generation_node) + + # 8. 通用内容生成节点(兜底) + general_generation_node = { + "id": "llm-general-generation", + "type": "llm", + "position": {"x": 850, "y": 500}, + "data": { + "label": "通用内容生成", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.8", + "max_tokens": "3000", + "prompt": """你是一位专业的内容创作专家。请根据用户需求生成高质量的内容。 + +**用户原始需求**:{{query}} + +**需求分析结果**: +- 内容类型:{{content_type}} +- 主题:{{topic}} +- 目标受众:{{target_audience}} +- 内容风格:{{style}} +- 字数要求:{{word_count}} +- 特殊要求:{{special_requirements}} +- 关键词:{{keywords}} + +请生成符合要求的内容,确保: +1. 内容完整、结构清晰 +2. 符合目标受众的阅读习惯 +3. 风格与要求一致 +4. 包含必要的关键词 +5. 满足字数要求 + +**重要**:确保生成的内容完全符合用户需求。""" + } + } + nodes.append(general_generation_node) + + # 连接条件节点到各个生成节点 + edges.append({ + "id": "e3", + "source": "condition-content-type", + "target": "llm-article-generation", + "sourceHandle": "true", + "targetHandle": "input", + "condition": "article" + }) + edges.append({ + "id": "e4", + "source": "condition-content-type", + "target": "llm-copywriting", + "sourceHandle": "true", + "targetHandle": "input", + "condition": "copywriting" + }) + edges.append({ + "id": "e5", + "source": "condition-content-type", + "target": "llm-script-generation", + "sourceHandle": "true", + "targetHandle": "input", + "condition": "script" + }) + edges.append({ + "id": "e6", + "source": "condition-content-type", + "target": "llm-general-generation", + "sourceHandle": "false", + "targetHandle": "input" + }) + + # 9. 内容整合节点(Transform节点) + content_integration_node = { + "id": "transform-integration", + "type": "transform", + "position": {"x": 1050, "y": 300}, + "data": { + "label": "内容整合", + "mode": "merge", + "mapping": { + "generated_content": "{{output}}", + "original_query": "{{query}}", + "requirement_analysis": "{{requirement_analysis}}" + } + } + } + nodes.append(content_integration_node) + + # 连接所有生成节点到整合节点 + for gen_node_id in ["llm-article-generation", "llm-copywriting", "llm-script-generation", "llm-general-generation"]: + edges.append({ + "id": f"e-{gen_node_id}-integration", + "source": gen_node_id, + "target": "transform-integration" + }) + + # 10. 内容优化节点(LLM节点) + content_optimization_node = { + "id": "llm-optimization", + "type": "llm", + "position": {"x": 1250, "y": 300}, + "data": { + "label": "内容优化", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.7", + "max_tokens": "4000", + "prompt": """你是一位专业的内容编辑和优化专家。请对生成的内容进行优化和润色。 + +**用户原始需求**:{{original_query}} + +**需求分析结果**:{{requirement_analysis}} + +**生成的内容**:{{generated_content}} + +请对内容进行优化,确保: +1. **内容质量**: + - 检查逻辑是否清晰 + - 确保信息准确、完整 + - 优化表达,使其更流畅 +2. **格式规范**: + - 检查Markdown格式是否正确 + - 确保标题层级合理 + - 优化段落结构 +3. **风格一致性**: + - 确保风格符合要求 + - 统一用词和语调 +4. **用户体验**: + - 确保内容易读易懂 + - 优化可读性 + +**输出要求**: +- 直接输出优化后的完整内容(纯文本Markdown格式) +- 不要包含JSON格式或其他包装 +- 确保内容完整、专业、易读""" + } + } + nodes.append(content_optimization_node) + edges.append({ + "id": "e7", + "source": "transform-integration", + "target": "llm-optimization" + }) + + # 11. 结束节点 + end_node = { + "id": "end-1", + "type": "end", + "position": {"x": 1450, "y": 300}, + "data": { + "label": "结束", + "output_format": "text" # 默认纯文本格式,适合对话场景 + } + } + nodes.append(end_node) + edges.append({ + "id": "e8", + "source": "llm-optimization", + "target": "end-1" + }) + + # 创建或更新Agent + workflow_config = { + "nodes": nodes, + "edges": edges + } + + agent = db.query(Agent).filter( + Agent.name == "内容生成助手", + Agent.user_id == user.id + ).first() + + if agent: + agent.workflow_config = workflow_config + agent.updated_at = datetime.now() + print("⚠️ Agent '内容生成助手' 已存在,将更新它...") + else: + agent = Agent( + id=str(uuid.uuid4()), + name="内容生成助手", + description="智能内容生成助手,能够根据用户需求生成各种类型的高质量内容,包括文章、博客、营销文案、视频脚本等。支持需求分析、内容生成、优化润色等完整流程。", + workflow_config=workflow_config, + status="published", + user_id=user.id, + version=1 + ) + db.add(agent) + + try: + db.commit() + db.refresh(agent) + print() + print("=" * 60) + print(f"✅ 成功创建/更新内容生成助手Agent") + print(f" ID: {agent.id}") + print(f" 状态: {agent.status}") + print(f" 节点数量: {len(nodes)}") + print(f" 连接数量: {len(edges)}") + print("=" * 60) + print() + print("工作流结构:") + print(" 开始 -> 需求分析 -> 内容类型判断 -> [文章生成 | 文案生成 | 脚本生成 | 通用生成] -> 内容整合 -> 内容优化 -> 结束") + print() + print("节点说明:") + print(" 1. 开始节点:接收用户输入(主题、类型、要求等)") + print(" 2. 需求分析节点:分析用户需求,提取内容类型、主题、风格等关键信息") + print(" 3. 数据准备节点:整合需求分析结果和原始查询") + print(" 4. 内容类型判断节点:根据内容类型进行分支处理") + print(" 5. 文章生成节点:生成结构完整的文章内容") + print(" 6. 文案生成节点:生成营销文案、广告文案等") + print(" 7. 脚本生成节点:生成视频/音频脚本") + print(" 8. 通用内容生成节点:处理其他类型的内容生成需求") + print(" 9. 内容整合节点:整合生成的内容和需求信息") + print(" 10. 内容优化节点:优化和润色内容,确保质量") + print(" 11. 结束节点:返回最终优化后的内容") + print() + print("使用示例:") + print(" 输入:'帮我写一篇关于人工智能发展趋势的博客文章,2000字左右,风格轻松易懂'") + print(" 输入:'生成一个产品推广的营销文案,面向年轻白领群体'") + print(" 输入:'写一个5分钟的产品介绍视频脚本'") + print() + return agent + except Exception as e: + db.rollback() + print() + print("=" * 60) + print(f"❌ 创建失败: {e}") + print("=" * 60) + import traceback + traceback.print_exc() + raise + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="生成内容生成助手Agent") + parser.add_argument("--username", type=str, default="admin", help="用户名") + + args = parser.parse_args() + + db = SessionLocal() + try: + generate_content_agent(db, username=args.username) + finally: + db.close() diff --git a/backend/scripts/generate_smart_agent.py b/backend/scripts/generate_smart_agent.py new file mode 100755 index 0000000..82e4c9c --- /dev/null +++ b/backend/scripts/generate_smart_agent.py @@ -0,0 +1,468 @@ +#!/usr/bin/env python3 +""" +生成智能需求分析与解决方案生成Agent +这是一个复杂的多节点Agent,能够理解用户需求并生成专业的解决方案 +""" +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from sqlalchemy.orm import Session +from app.core.database import SessionLocal +from app.models.agent import Agent +from app.models.user import User +from app.models.node_template import NodeTemplate +from datetime import datetime +import uuid +import json + + +def generate_smart_agent(db: Session, username: str = "admin"): + """生成智能需求分析与解决方案生成Agent""" + print("=" * 60) + print("生成智能需求分析与解决方案生成Agent") + print("=" * 60) + print() + + # 查找用户 + user = db.query(User).filter(User.username == username).first() + if not user: + print(f"❌ 未找到用户 '{username}',请先创建该用户") + return + print(f"✅ 找到用户: {user.username} (ID: {user.id})") + print() + + # 查找可用的节点模板(优先选择特定类型的模板) + # 优先查找:工作流设计、API集成、数据分析、业务流程等类型的模板 + preferred_categories = ["工作流设计", "API集成", "数据分析", "业务流程", "技术方案"] + + templates = [] + for category in preferred_categories: + category_templates = db.query(NodeTemplate).filter( + ((NodeTemplate.is_public == True) | (NodeTemplate.user_id == user.id)) & + (NodeTemplate.category == category) + ).limit(2).all() + templates.extend(category_templates) + + # 如果没找到足够的模板,补充其他公开模板 + if len(templates) < 3: + other_templates = db.query(NodeTemplate).filter( + (NodeTemplate.is_public == True) | (NodeTemplate.user_id == user.id) + ).limit(5).all() + for t in other_templates: + if t not in templates: + templates.append(t) + if len(templates) >= 5: + break + + if len(templates) < 2: + print(f"⚠️ 警告: 只找到 {len(templates)} 个节点模板,将使用LLM节点代替") + use_templates = False + else: + use_templates = True + print(f"📋 找到 {len(templates)} 个可用节点模板") + for i, template in enumerate(templates[:5], 1): + print(f" {i}. {template.name} ({template.category})") + print() + + # 生成智能工作流配置 + # 工作流结构: + # 开始 -> 需求理解 -> 需求分类 -> [技术方案分支 | 业务流程分支 | 数据分析分支] -> 方案整合 -> 输出优化 -> 结束 + + nodes = [] + edges = [] + + # 1. 开始节点 + start_node = { + "id": "start-1", + "type": "start", + "position": {"x": 50, "y": 300}, + "data": { + "label": "开始", + "output_format": "json" + } + } + nodes.append(start_node) + + # 2. 需求理解节点(LLM节点) + requirement_analysis_node = { + "id": "llm-requirement-analysis", + "type": "llm", + "position": {"x": 250, "y": 300}, + "data": { + "label": "需求理解与分析", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.7", + "max_tokens": "2000", + "prompt": """你是一位专业的需求分析师。请分析用户的需求,并提取关键信息。 + +用户需求:{input} + +请按照以下格式输出JSON: +{{ + "requirement_type": "技术方案|业务流程|数据分析|工作流设计|其他", + "key_points": ["关键点1", "关键点2", ...], + "complexity": "简单|中等|复杂", + "domain": "领域(如:电商、金融、教育等)", + "summary": "需求摘要" +}}""" + } + } + nodes.append(requirement_analysis_node) + edges.append({ + "id": "e1", + "source": "start-1", + "target": "llm-requirement-analysis" + }) + + # 3. 需求分类节点(条件节点) + classification_node = { + "id": "condition-classify", + "type": "condition", + "position": {"x": 450, "y": 300}, + "data": { + "label": "需求分类", + "condition": "{{requirement_type}} == '技术方案' or {{requirement_type}} == 'API集成'" + } + } + nodes.append(classification_node) + edges.append({ + "id": "e2", + "source": "llm-requirement-analysis", + "target": "condition-classify" + }) + + # 4. 技术方案分支(如果使用模板) + if use_templates and len(templates) > 0: + # 查找技术方案相关的模板 + tech_template = None + for t in templates: + if "技术" in t.category or "API" in t.category or "集成" in t.name: + tech_template = t + break + if not tech_template: + tech_template = templates[0] + + tech_solution_node = { + "id": "template-tech-solution", + "type": "template", + "position": {"x": 650, "y": 200}, + "data": { + "label": tech_template.name, + "template_id": tech_template.id, + "provider": tech_template.provider or "deepseek", + "model": tech_template.model or "deepseek-chat", + "temperature": str(tech_template.temperature) if tech_template.temperature else "0.7", + "max_tokens": str(tech_template.max_tokens) if tech_template.max_tokens else "2000", + "prompt": tech_template.prompt + } + } + nodes.append(tech_solution_node) + edges.append({ + "id": "e3", + "source": "condition-classify", + "target": "template-tech-solution", + "sourceHandle": "true" + }) + else: + # 使用LLM节点代替 + tech_solution_node = { + "id": "llm-tech-solution", + "type": "llm", + "position": {"x": 650, "y": 200}, + "data": { + "label": "技术方案设计", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.7", + "max_tokens": "3000", + "prompt": """你是一位资深的技术架构师。根据需求分析结果,设计一个完整的技术方案。 + +需求分析结果:{{requirement_analysis}} + +请提供: +1. 技术选型建议 +2. 架构设计 +3. 实施步骤 +4. 风险评估 +5. 最佳实践建议 + +输出格式:结构化的Markdown文档""" + } + } + nodes.append(tech_solution_node) + edges.append({ + "id": "e3", + "source": "condition-classify", + "target": "llm-tech-solution", + "sourceHandle": "true" + }) + + # 5. 业务流程分支(优先使用LLM节点,确保能理解用户需求) + # 使用LLM节点而不是模板节点,因为LLM节点可以更好地理解需求上下文 + process_solution_node = { + "id": "llm-process-solution", + "type": "llm", + "position": {"x": 650, "y": 400}, + "data": { + "label": "业务流程设计", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.7", + "max_tokens": "4000", + "prompt": """你是一位资深的业务流程设计专家。请根据用户的需求,设计一个完整、详细的业务流程方案。 + +**重要**:请仔细阅读用户原始需求,确保设计方案完全符合用户的具体需求。 + +用户原始需求:{{query}} + +需求分析结果:{{requirement_analysis}} + +请提供以下内容: +1. **流程概述**:整体流程的目标、范围和价值 +2. **详细流程设计**: + - 流程的各个阶段和环节 + - 每个环节的输入、输出和处理逻辑 + - 状态流转图(用Mermaid或文字描述) +3. **关键步骤说明**: + - 每个步骤的具体操作 + - 前置条件和后置条件 + - 异常情况处理 +4. **角色与职责**: + - 涉及的角色/系统 + - 每个角色的职责和权限 +5. **数据流转**: + - 关键数据在各环节的流转 + - 数据格式和验证规则 +6. **异常处理机制**: + - 常见异常场景 + - 异常处理流程 +7. **流程优化建议**: + - 性能优化点 + - 用户体验优化 +8. **实施路线图**: + - 分阶段实施计划 + - 关键里程碑 + +**重要**:请确保设计方案完全符合用户的需求,不要偏离主题。如果用户需求是电商订单流程,就设计电商订单流程;如果是其他业务,就设计对应的业务流程。 + +输出格式:结构化的Markdown文档,使用清晰的标题和列表""" + } + } + nodes.append(process_solution_node) + edges.append({ + "id": "e4", + "source": "condition-classify", + "target": "llm-process-solution", + "sourceHandle": "false" + }) + + # 6. 方案整合节点(Transform节点) + integration_node = { + "id": "transform-integration", + "type": "transform", + "position": {"x": 850, "y": 300}, + "data": { + "label": "方案整合", + "mode": "merge", + "mapping": { + "solution": "{{result}}", + "requirement_analysis": "{{requirement_analysis}}", + "query": "{{query}}", + "timestamp": "{{timestamp}}" + } + } + } + nodes.append(integration_node) + + # 连接分支到整合节点 + if use_templates and len(templates) > 0: + edges.append({ + "id": "e5", + "source": "template-tech-solution" if use_templates else "llm-tech-solution", + "target": "transform-integration" + }) + else: + edges.append({ + "id": "e5", + "source": "llm-tech-solution", + "target": "transform-integration" + }) + + # 业务流程分支始终使用LLM节点 + edges.append({ + "id": "e6", + "source": "llm-process-solution", + "target": "transform-integration" + }) + + # 7. 输出优化节点(LLM节点) + optimization_node = { + "id": "llm-optimization", + "type": "llm", + "position": {"x": 1050, "y": 300}, + "data": { + "label": "输出优化与格式化", + "provider": "deepseek", + "model": "deepseek-chat", + "temperature": "0.5", + "max_tokens": "4000", + "prompt": """你是一位专业的技术文档编辑。请对方案进行优化和格式化,确保方案完全符合用户需求。 + +用户原始需求:{{query}} +需求分析结果:{{requirement_analysis}} +整合后的方案:{{solution}} + +**重要检查**: +1. 确保方案内容与用户需求完全匹配 +2. 如果方案偏离了用户需求,请重新生成符合需求的方案 +3. 如果方案是关于数据清洗的,但用户需求是业务流程设计,请重新生成业务流程设计方案 + +请: +1. 检查方案是否完全符合用户需求 +2. 优化文档结构,使其更清晰 +3. 补充关键细节和实施建议 +4. 确保格式统一、专业 +5. 如果发现方案不符合需求,请重新生成正确的方案 + +**输出要求**: +- 只输出纯文本的Markdown文档内容 +- 不要包含任何JSON格式、代码块标记或其他格式包装 +- 直接输出方案文档的正文内容 +- 确保内容完整、专业、易读 + +输出格式:直接输出完整的Markdown文档正文(纯文本)""" + } + } + nodes.append(optimization_node) + edges.append({ + "id": "e7", + "source": "transform-integration", + "target": "llm-optimization" + }) + + # 8. 结束节点 + end_node = { + "id": "end-1", + "type": "end", + "position": {"x": 1250, "y": 300}, + "data": { + "label": "结束", + "output_format": "text" # 默认纯文本格式,适合对话场景 + } + } + nodes.append(end_node) + edges.append({ + "id": "e8", + "source": "llm-optimization", + "target": "end-1" + }) + + # 检查是否已存在同名Agent + agent_name = "智能需求分析与解决方案生成器" + existing = db.query(Agent).filter( + Agent.name == agent_name, + Agent.user_id == user.id + ).first() + + if existing: + print(f"⚠️ Agent '{agent_name}' 已存在,将更新它...") + existing.workflow_config = {"nodes": nodes, "edges": edges} + existing.description = """智能需求分析与解决方案生成Agent + +功能特点: +1. 自动理解用户需求并提取关键信息 +2. 根据需求类型智能分类(技术方案/业务流程/数据分析等) +3. 调用专业模板或LLM生成针对性解决方案 +4. 整合并优化输出,生成专业文档 + +适用场景: +- 技术方案设计 +- 业务流程优化 +- 系统架构设计 +- 问题分析与解决""" + existing.updated_at = datetime.now() + agent = existing + else: + # 创建Agent + agent = Agent( + name=agent_name, + description="""智能需求分析与解决方案生成Agent + +功能特点: +1. 自动理解用户需求并提取关键信息 +2. 根据需求类型智能分类(技术方案/业务流程/数据分析等) +3. 调用专业模板或LLM生成针对性解决方案 +4. 整合并优化输出,生成专业文档 + +适用场景: +- 技术方案设计 +- 业务流程优化 +- 系统架构设计 +- 问题分析与解决""", + workflow_config={"nodes": nodes, "edges": edges}, + status="published", # 直接发布,可以直接使用 + user_id=user.id, + version=1 + ) + db.add(agent) + + try: + db.commit() + db.refresh(agent) + print() + print("=" * 60) + print(f"✅ 成功创建/更新智能Agent: {agent.name}") + print(f" ID: {agent.id}") + print(f" 状态: {agent.status}") + print(f" 节点数量: {len(nodes)}") + print(f" 连接数量: {len(edges)}") + print("=" * 60) + print() + print("工作流结构:") + print(" 开始 -> 需求理解 -> 需求分类 -> [技术方案分支 | 业务流程分支] -> 方案整合 -> 输出优化 -> 结束") + print() + print("节点说明:") + print(" 1. 开始节点:接收用户输入") + print(" 2. 需求理解节点:分析用户需求,提取关键信息") + print(" 3. 需求分类节点:根据需求类型进行分支") + print(" 4. 技术方案/业务流程节点:生成针对性解决方案") + print(" 5. 方案整合节点:整合各分支结果") + print(" 6. 输出优化节点:优化和格式化最终输出") + print(" 7. 结束节点:返回最终结果") + print() + return agent + except Exception as e: + db.rollback() + print() + print("=" * 60) + print(f"❌ 创建失败: {e}") + print("=" * 60) + import traceback + traceback.print_exc() + raise + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser(description="生成智能需求分析与解决方案生成Agent") + parser.add_argument( + "--username", + type=str, + default="admin", + help="创建Agent的用户名(默认: admin)" + ) + + args = parser.parse_args() + + db = SessionLocal() + try: + generate_smart_agent(db, username=args.username) + except Exception as e: + print(f"❌ 发生错误: {e}") + import traceback + traceback.print_exc() + finally: + db.close() diff --git a/frontend/src/components/AgentChatPreview.vue b/frontend/src/components/AgentChatPreview.vue index d34fb64..57b3085 100644 --- a/frontend/src/components/AgentChatPreview.vue +++ b/frontend/src/components/AgentChatPreview.vue @@ -143,7 +143,7 @@