工具
This commit is contained in:
54
backend/alembic/versions/004_add_tools_table.py
Normal file
54
backend/alembic/versions/004_add_tools_table.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""add tools table
|
||||
|
||||
Revision ID: 004
|
||||
Revises: 003
|
||||
Create Date: 2026-01-23 00:00:00.000000
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.mysql import CHAR, JSON
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '004_add_tools_table'
|
||||
down_revision = '003_add_rbac'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# 创建tools表
|
||||
op.create_table(
|
||||
'tools',
|
||||
sa.Column('id', CHAR(36), primary_key=True, comment='工具ID'),
|
||||
sa.Column('name', sa.String(100), nullable=False, unique=True, comment='工具名称'),
|
||||
sa.Column('description', sa.Text, nullable=False, comment='工具描述'),
|
||||
sa.Column('category', sa.String(50), nullable=True, comment='工具分类'),
|
||||
sa.Column('function_schema', JSON, nullable=False, comment='函数定义(JSON Schema)'),
|
||||
sa.Column('implementation_type', sa.String(50), nullable=False, comment='实现类型: builtin/http/workflow/code'),
|
||||
sa.Column('implementation_config', JSON, nullable=True, comment='实现配置'),
|
||||
sa.Column('is_public', sa.Boolean, default=False, comment='是否公开'),
|
||||
sa.Column('user_id', CHAR(36), sa.ForeignKey('users.id', ondelete='SET NULL'), nullable=True, comment='创建者ID'),
|
||||
sa.Column('use_count', sa.Integer, default=0, comment='使用次数'),
|
||||
sa.Column('created_at', sa.DateTime, server_default=sa.func.now(), comment='创建时间'),
|
||||
sa.Column('updated_at', sa.DateTime, server_default=sa.func.now(), onupdate=sa.func.now(), comment='更新时间'),
|
||||
mysql_engine='InnoDB',
|
||||
mysql_charset='utf8mb4',
|
||||
mysql_collate='utf8mb4_unicode_ci'
|
||||
)
|
||||
|
||||
# 创建索引
|
||||
op.create_index('idx_tools_category', 'tools', ['category'])
|
||||
op.create_index('idx_tools_is_public', 'tools', ['is_public'])
|
||||
op.create_index('idx_tools_user_id', 'tools', ['user_id'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
# 删除索引
|
||||
op.drop_index('idx_tools_user_id', table_name='tools')
|
||||
op.drop_index('idx_tools_is_public', table_name='tools')
|
||||
op.drop_index('idx_tools_category', table_name='tools')
|
||||
|
||||
# 删除表
|
||||
op.drop_table('tools')
|
||||
@@ -1,10 +1,10 @@
|
||||
"""
|
||||
执行日志API
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import datetime
|
||||
from app.core.database import get_db
|
||||
from app.models.execution_log import ExecutionLog
|
||||
@@ -14,6 +14,10 @@ from app.models.agent import Agent
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from app.core.exceptions import NotFoundError
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/v1/execution-logs", tags=["execution-logs"])
|
||||
|
||||
@@ -266,3 +270,153 @@ async def get_execution_performance(
|
||||
for log in timeline_logs
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@router.get("/executions/{execution_id}/nodes/{node_id}/data")
|
||||
async def get_node_execution_data(
|
||||
execution_id: str,
|
||||
node_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取特定节点的执行数据(输入和输出)
|
||||
|
||||
从执行日志中提取节点的输入和输出数据
|
||||
"""
|
||||
# 验证执行记录是否存在
|
||||
execution = db.query(Execution).filter(Execution.id == execution_id).first()
|
||||
|
||||
if not execution:
|
||||
raise NotFoundError("执行记录", execution_id)
|
||||
|
||||
# 验证权限:检查workflow或agent的所有权
|
||||
has_permission = False
|
||||
if execution.workflow_id:
|
||||
workflow = db.query(Workflow).filter(Workflow.id == execution.workflow_id).first()
|
||||
if workflow and workflow.user_id == current_user.id:
|
||||
has_permission = True
|
||||
elif execution.agent_id:
|
||||
agent = db.query(Agent).filter(Agent.id == execution.agent_id).first()
|
||||
if agent and (agent.user_id == current_user.id or agent.status in ["published", "running"]):
|
||||
has_permission = True
|
||||
|
||||
if not has_permission:
|
||||
raise NotFoundError("执行记录", execution_id)
|
||||
|
||||
# 查找节点的开始和完成日志
|
||||
start_log = db.query(ExecutionLog).filter(
|
||||
ExecutionLog.execution_id == execution_id,
|
||||
ExecutionLog.node_id == node_id,
|
||||
ExecutionLog.message.like('%开始执行%')
|
||||
).order_by(ExecutionLog.timestamp.asc()).first()
|
||||
|
||||
complete_log = db.query(ExecutionLog).filter(
|
||||
ExecutionLog.execution_id == execution_id,
|
||||
ExecutionLog.node_id == node_id,
|
||||
ExecutionLog.message.like('%执行完成%')
|
||||
).order_by(ExecutionLog.timestamp.desc()).first()
|
||||
|
||||
input_data = None
|
||||
output_data = None
|
||||
|
||||
# 从开始日志中提取输入数据
|
||||
if start_log and start_log.data:
|
||||
input_data = start_log.data.get('input', start_log.data)
|
||||
|
||||
# 从完成日志中提取输出数据
|
||||
if complete_log and complete_log.data:
|
||||
output_data = complete_log.data.get('output', complete_log.data)
|
||||
|
||||
return {
|
||||
"execution_id": execution_id,
|
||||
"node_id": node_id,
|
||||
"input": input_data,
|
||||
"output": output_data,
|
||||
"start_time": start_log.timestamp.isoformat() if start_log else None,
|
||||
"complete_time": complete_log.timestamp.isoformat() if complete_log else None,
|
||||
"duration": complete_log.duration if complete_log else None
|
||||
}
|
||||
|
||||
|
||||
@router.get("/cache/{key:path}")
|
||||
async def get_cache_value(
|
||||
key: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
获取缓存值(记忆数据)
|
||||
|
||||
从Redis或内存缓存中获取指定key的值
|
||||
"""
|
||||
try:
|
||||
from app.core.redis_client import get_redis_client
|
||||
|
||||
redis_client = get_redis_client()
|
||||
value = None
|
||||
|
||||
if redis_client:
|
||||
# 从Redis获取
|
||||
try:
|
||||
cached_data = redis_client.get(key)
|
||||
if cached_data:
|
||||
value = json.loads(cached_data)
|
||||
except json.JSONDecodeError:
|
||||
# 如果不是JSON,直接返回字符串
|
||||
value = cached_data
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis获取缓存失败: {str(e)}")
|
||||
|
||||
if value is None:
|
||||
# 如果Redis中没有,返回空
|
||||
raise HTTPException(status_code=404, detail=f"缓存键 '{key}' 不存在")
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"value": value,
|
||||
"exists": True
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"获取缓存值失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"获取缓存值失败: {str(e)}")
|
||||
|
||||
|
||||
@router.delete("/cache/{key:path}")
|
||||
async def delete_cache_value(
|
||||
key: str,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""
|
||||
删除缓存值(记忆数据)
|
||||
|
||||
从Redis或内存缓存中删除指定key的值
|
||||
"""
|
||||
try:
|
||||
from app.core.redis_client import get_redis_client
|
||||
|
||||
redis_client = get_redis_client()
|
||||
deleted = False
|
||||
|
||||
if redis_client:
|
||||
# 从Redis删除
|
||||
try:
|
||||
result = redis_client.delete(key)
|
||||
deleted = result > 0
|
||||
except Exception as e:
|
||||
logger.warning(f"从Redis删除缓存失败: {str(e)}")
|
||||
|
||||
if not deleted:
|
||||
raise HTTPException(status_code=404, detail=f"缓存键 '{key}' 不存在")
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"deleted": True,
|
||||
"message": "缓存已删除"
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"删除缓存值失败: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"删除缓存值失败: {str(e)}")
|
||||
|
||||
176
backend/app/api/tools.py
Normal file
176
backend/app/api/tools.py
Normal file
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
工具管理API
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from app.core.database import get_db
|
||||
from app.models.tool import Tool
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.api.auth import get_current_user
|
||||
from app.models.user import User
|
||||
from pydantic import BaseModel
|
||||
|
||||
router = APIRouter(prefix="/api/v1/tools", tags=["tools"])
|
||||
|
||||
|
||||
class ToolCreate(BaseModel):
|
||||
"""创建工具请求"""
|
||||
name: str
|
||||
description: str
|
||||
category: Optional[str] = None
|
||||
function_schema: dict
|
||||
implementation_type: str
|
||||
implementation_config: Optional[dict] = None
|
||||
is_public: bool = False
|
||||
|
||||
|
||||
class ToolResponse(BaseModel):
|
||||
"""工具响应"""
|
||||
id: str
|
||||
name: str
|
||||
description: str
|
||||
category: Optional[str]
|
||||
function_schema: dict
|
||||
implementation_type: str
|
||||
implementation_config: Optional[dict]
|
||||
is_public: bool
|
||||
use_count: int
|
||||
user_id: Optional[str]
|
||||
created_at: str
|
||||
updated_at: str
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@router.get("", response_model=List[ToolResponse])
|
||||
async def list_tools(
|
||||
category: Optional[str] = Query(None, description="工具分类"),
|
||||
search: Optional[str] = Query(None, description="搜索关键词"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取工具列表"""
|
||||
query = db.query(Tool).filter(Tool.is_public == True)
|
||||
|
||||
if category:
|
||||
query = query.filter(Tool.category == category)
|
||||
|
||||
if search:
|
||||
query = query.filter(
|
||||
Tool.name.contains(search) |
|
||||
Tool.description.contains(search)
|
||||
)
|
||||
|
||||
tools = query.order_by(Tool.use_count.desc(), Tool.created_at.desc()).all()
|
||||
return tools
|
||||
|
||||
|
||||
@router.get("/builtin")
|
||||
async def list_builtin_tools():
|
||||
"""获取内置工具列表"""
|
||||
schemas = tool_registry.get_all_tool_schemas()
|
||||
return schemas
|
||||
|
||||
|
||||
@router.get("/{tool_id}", response_model=ToolResponse)
|
||||
async def get_tool(
|
||||
tool_id: str,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取工具详情"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
return tool
|
||||
|
||||
|
||||
@router.post("", response_model=ToolResponse, status_code=201)
|
||||
async def create_tool(
|
||||
tool_data: ToolCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""创建工具"""
|
||||
# 检查工具名称是否已存在
|
||||
existing = db.query(Tool).filter(Tool.name == tool_data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"工具名称 '{tool_data.name}' 已存在")
|
||||
|
||||
tool = Tool(
|
||||
name=tool_data.name,
|
||||
description=tool_data.description,
|
||||
category=tool_data.category,
|
||||
function_schema=tool_data.function_schema,
|
||||
implementation_type=tool_data.implementation_type,
|
||||
implementation_config=tool_data.implementation_config,
|
||||
is_public=tool_data.is_public,
|
||||
user_id=current_user.id
|
||||
)
|
||||
|
||||
db.add(tool)
|
||||
db.commit()
|
||||
db.refresh(tool)
|
||||
|
||||
return tool
|
||||
|
||||
|
||||
@router.put("/{tool_id}", response_model=ToolResponse)
|
||||
async def update_tool(
|
||||
tool_id: str,
|
||||
tool_data: ToolCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""更新工具"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
|
||||
# 检查权限(只有创建者可以更新)
|
||||
if tool.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="无权更新此工具")
|
||||
|
||||
# 检查名称冲突
|
||||
if tool_data.name != tool.name:
|
||||
existing = db.query(Tool).filter(Tool.name == tool_data.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail=f"工具名称 '{tool_data.name}' 已存在")
|
||||
|
||||
tool.name = tool_data.name
|
||||
tool.description = tool_data.description
|
||||
tool.category = tool_data.category
|
||||
tool.function_schema = tool_data.function_schema
|
||||
tool.implementation_type = tool_data.implementation_type
|
||||
tool.implementation_config = tool_data.implementation_config
|
||||
tool.is_public = tool_data.is_public
|
||||
|
||||
db.commit()
|
||||
db.refresh(tool)
|
||||
|
||||
return tool
|
||||
|
||||
|
||||
@router.delete("/{tool_id}", status_code=200)
|
||||
async def delete_tool(
|
||||
tool_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""删除工具"""
|
||||
tool = db.query(Tool).filter(Tool.id == tool_id).first()
|
||||
if not tool:
|
||||
raise HTTPException(status_code=404, detail="工具不存在")
|
||||
|
||||
# 检查权限(只有创建者可以删除)
|
||||
if tool.user_id != current_user.id:
|
||||
raise HTTPException(status_code=403, detail="无权删除此工具")
|
||||
|
||||
# 内置工具不允许删除
|
||||
if tool.implementation_type == "builtin":
|
||||
raise HTTPException(status_code=400, detail="内置工具不允许删除")
|
||||
|
||||
db.delete(tool)
|
||||
db.commit()
|
||||
|
||||
return {"message": "工具已删除"}
|
||||
@@ -90,12 +90,15 @@ Authorization: Bearer <your_token>
|
||||
|
||||
# CORS 配置
|
||||
cors_origins = [origin.strip() for origin in settings.CORS_ORIGINS.split(",")]
|
||||
# 添加调试日志
|
||||
logger.info(f"CORS允许的源: {cors_origins}")
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
expose_headers=["*"],
|
||||
)
|
||||
|
||||
# 注册全局异常处理器
|
||||
@@ -149,7 +152,7 @@ async def health_check():
|
||||
"""健康检查"""
|
||||
return {"status": "healthy"}
|
||||
|
||||
# 应用启动时初始化数据库
|
||||
# 应用启动时初始化数据库和工具
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
"""应用启动事件"""
|
||||
@@ -160,9 +163,45 @@ async def startup_event():
|
||||
except Exception as e:
|
||||
logger.error(f"数据库初始化失败: {e}")
|
||||
# 不抛出异常,允许应用继续启动
|
||||
|
||||
# 注册内置工具
|
||||
try:
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.services.builtin_tools import (
|
||||
http_request_tool,
|
||||
file_read_tool,
|
||||
file_write_tool,
|
||||
text_analyze_tool,
|
||||
datetime_tool,
|
||||
math_calculate_tool,
|
||||
system_info_tool,
|
||||
json_process_tool,
|
||||
HTTP_REQUEST_SCHEMA,
|
||||
FILE_READ_SCHEMA,
|
||||
FILE_WRITE_SCHEMA,
|
||||
TEXT_ANALYZE_SCHEMA,
|
||||
DATETIME_SCHEMA,
|
||||
MATH_CALCULATE_SCHEMA,
|
||||
SYSTEM_INFO_SCHEMA,
|
||||
JSON_PROCESS_SCHEMA
|
||||
)
|
||||
|
||||
tool_registry.register_builtin_tool("http_request", http_request_tool, HTTP_REQUEST_SCHEMA)
|
||||
tool_registry.register_builtin_tool("file_read", file_read_tool, FILE_READ_SCHEMA)
|
||||
tool_registry.register_builtin_tool("file_write", file_write_tool, FILE_WRITE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("text_analyze", text_analyze_tool, TEXT_ANALYZE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("datetime", datetime_tool, DATETIME_SCHEMA)
|
||||
tool_registry.register_builtin_tool("math_calculate", math_calculate_tool, MATH_CALCULATE_SCHEMA)
|
||||
tool_registry.register_builtin_tool("system_info", system_info_tool, SYSTEM_INFO_SCHEMA)
|
||||
tool_registry.register_builtin_tool("json_process", json_process_tool, JSON_PROCESS_SCHEMA)
|
||||
|
||||
logger.info("内置工具注册完成(共8个工具)")
|
||||
except Exception as e:
|
||||
logger.error(f"内置工具注册失败: {e}")
|
||||
# 不抛出异常,允许应用继续启动
|
||||
|
||||
# 注册路由
|
||||
from app.api import auth, workflows, executions, websocket, execution_logs, data_sources, agents, model_configs, webhooks, template_market, batch_operations, collaboration, permissions, monitoring, alert_rules, node_test, node_templates
|
||||
from app.api import auth, workflows, executions, websocket, execution_logs, data_sources, agents, model_configs, webhooks, template_market, batch_operations, collaboration, permissions, monitoring, alert_rules, node_test, node_templates, tools
|
||||
|
||||
app.include_router(auth.router)
|
||||
app.include_router(workflows.router)
|
||||
@@ -181,6 +220,7 @@ app.include_router(monitoring.router)
|
||||
app.include_router(alert_rules.router)
|
||||
app.include_router(node_test.router)
|
||||
app.include_router(node_templates.router)
|
||||
app.include_router(tools.router)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
38
backend/app/models/tool.py
Normal file
38
backend/app/models/tool.py
Normal file
@@ -0,0 +1,38 @@
|
||||
"""
|
||||
工具定义模型
|
||||
"""
|
||||
from sqlalchemy import Column, String, Text, JSON, DateTime, Boolean, ForeignKey, Integer, func
|
||||
from sqlalchemy.dialects.mysql import CHAR
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.database import Base
|
||||
import uuid
|
||||
|
||||
|
||||
class Tool(Base):
|
||||
"""工具定义表"""
|
||||
__tablename__ = "tools"
|
||||
|
||||
id = Column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="工具ID")
|
||||
name = Column(String(100), nullable=False, unique=True, comment="工具名称")
|
||||
description = Column(Text, nullable=False, comment="工具描述")
|
||||
category = Column(String(50), comment="工具分类")
|
||||
|
||||
# 工具定义(OpenAI Function格式)
|
||||
function_schema = Column(JSON, nullable=False, comment="函数定义(JSON Schema)")
|
||||
|
||||
# 工具实现类型
|
||||
implementation_type = Column(String(50), nullable=False, comment="实现类型: builtin/http/workflow/code")
|
||||
implementation_config = Column(JSON, comment="实现配置")
|
||||
|
||||
# 元数据
|
||||
is_public = Column(Boolean, default=False, comment="是否公开")
|
||||
user_id = Column(CHAR(36), ForeignKey("users.id"), nullable=True, comment="创建者ID")
|
||||
use_count = Column(Integer, default=0, comment="使用次数")
|
||||
created_at = Column(DateTime, default=func.now(), comment="创建时间")
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")
|
||||
|
||||
# 关系
|
||||
user = relationship("User", backref="tools")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Tool(id={self.id}, name={self.name})>"
|
||||
608
backend/app/services/builtin_tools.py
Normal file
608
backend/app/services/builtin_tools.py
Normal file
@@ -0,0 +1,608 @@
|
||||
"""
|
||||
内置工具实现
|
||||
"""
|
||||
from typing import Dict, Any, Optional, List
|
||||
import httpx
|
||||
import json
|
||||
import os
|
||||
import logging
|
||||
import re
|
||||
import math
|
||||
from datetime import datetime, timedelta
|
||||
import platform
|
||||
import sys
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def http_request_tool(
|
||||
url: str,
|
||||
method: str = "GET",
|
||||
headers: Optional[Dict[str, str]] = None,
|
||||
body: Any = None
|
||||
) -> str:
|
||||
"""
|
||||
HTTP请求工具
|
||||
|
||||
Args:
|
||||
url: 请求URL
|
||||
method: HTTP方法 (GET, POST, PUT, DELETE)
|
||||
headers: 请求头
|
||||
body: 请求体(POST/PUT时使用)
|
||||
|
||||
Returns:
|
||||
JSON格式的响应结果
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
method_upper = method.upper()
|
||||
|
||||
if method_upper == "GET":
|
||||
response = await client.get(url, headers=headers)
|
||||
elif method_upper == "POST":
|
||||
response = await client.post(url, json=body, headers=headers)
|
||||
elif method_upper == "PUT":
|
||||
response = await client.put(url, json=body, headers=headers)
|
||||
elif method_upper == "DELETE":
|
||||
response = await client.delete(url, headers=headers)
|
||||
else:
|
||||
raise ValueError(f"不支持的HTTP方法: {method}")
|
||||
|
||||
# 尝试解析JSON响应
|
||||
try:
|
||||
response_body = response.json()
|
||||
except:
|
||||
response_body = response.text
|
||||
|
||||
result = {
|
||||
"status_code": response.status_code,
|
||||
"headers": dict(response.headers),
|
||||
"body": response_body
|
||||
}
|
||||
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"HTTP请求工具执行失败: {str(e)}")
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def file_read_tool(file_path: str) -> str:
|
||||
"""
|
||||
文件读取工具
|
||||
|
||||
Args:
|
||||
file_path: 文件路径
|
||||
|
||||
Returns:
|
||||
文件内容或错误信息
|
||||
"""
|
||||
try:
|
||||
# 安全检查:限制可读取的文件路径
|
||||
# 只允许读取项目目录下的文件
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
abs_path = os.path.abspath(file_path)
|
||||
|
||||
if not abs_path.startswith(project_root):
|
||||
return json.dumps({
|
||||
"error": f"不允许读取项目目录外的文件: {file_path}"
|
||||
}, ensure_ascii=False)
|
||||
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
return json.dumps({
|
||||
"file_path": file_path,
|
||||
"content": content,
|
||||
"size": len(content)
|
||||
}, ensure_ascii=False)
|
||||
except FileNotFoundError:
|
||||
return json.dumps({
|
||||
"error": f"文件不存在: {file_path}"
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"文件读取工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def file_write_tool(file_path: str, content: str, mode: str = "w") -> str:
|
||||
"""
|
||||
文件写入工具
|
||||
|
||||
Args:
|
||||
file_path: 文件路径
|
||||
content: 要写入的内容
|
||||
mode: 写入模式(w=覆盖,a=追加)
|
||||
|
||||
Returns:
|
||||
写入结果
|
||||
"""
|
||||
try:
|
||||
# 安全检查:限制可写入的文件路径
|
||||
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../.."))
|
||||
abs_path = os.path.abspath(file_path)
|
||||
|
||||
if not abs_path.startswith(project_root):
|
||||
return json.dumps({
|
||||
"error": f"不允许写入项目目录外的文件: {file_path}"
|
||||
}, ensure_ascii=False)
|
||||
|
||||
write_mode = "a" if mode == "a" else "w"
|
||||
with open(file_path, write_mode, encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"file_path": file_path,
|
||||
"mode": write_mode,
|
||||
"content_length": len(content)
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"文件写入工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def text_analyze_tool(text: str, operation: str = "count") -> str:
|
||||
"""
|
||||
文本分析工具
|
||||
|
||||
Args:
|
||||
text: 要分析的文本
|
||||
operation: 操作类型(count=统计, keywords=提取关键词, summary=摘要)
|
||||
|
||||
Returns:
|
||||
分析结果
|
||||
"""
|
||||
try:
|
||||
if operation == "count":
|
||||
# 统计字数、字符数、行数等
|
||||
char_count = len(text)
|
||||
char_count_no_spaces = len(text.replace(" ", ""))
|
||||
word_count = len(text.split())
|
||||
line_count = len(text.splitlines())
|
||||
paragraph_count = len([p for p in text.split("\n\n") if p.strip()])
|
||||
|
||||
return json.dumps({
|
||||
"char_count": char_count,
|
||||
"char_count_no_spaces": char_count_no_spaces,
|
||||
"word_count": word_count,
|
||||
"line_count": line_count,
|
||||
"paragraph_count": paragraph_count
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "keywords":
|
||||
# 简单的关键词提取(基于词频)
|
||||
words = re.findall(r'\b\w+\b', text.lower())
|
||||
word_freq = {}
|
||||
for word in words:
|
||||
if len(word) > 2: # 忽略太短的词
|
||||
word_freq[word] = word_freq.get(word, 0) + 1
|
||||
|
||||
# 取频率最高的10个词
|
||||
top_keywords = sorted(word_freq.items(), key=lambda x: x[1], reverse=True)[:10]
|
||||
|
||||
return json.dumps({
|
||||
"keywords": [{"word": k, "frequency": v} for k, v in top_keywords]
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "summary":
|
||||
# 简单的摘要(取前3句)
|
||||
sentences = re.split(r'[.!?。!?]\s+', text)
|
||||
summary = ". ".join(sentences[:3]) + "."
|
||||
|
||||
return json.dumps({
|
||||
"summary": summary,
|
||||
"original_length": len(text),
|
||||
"summary_length": len(summary)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps({
|
||||
"error": f"不支持的操作类型: {operation}"
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"文本分析工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def datetime_tool(operation: str = "now", format: str = "%Y-%m-%d %H:%M:%S",
|
||||
timezone: Optional[str] = None) -> str:
|
||||
"""
|
||||
日期时间工具
|
||||
|
||||
Args:
|
||||
operation: 操作类型(now=当前时间, format=格式化, parse=解析)
|
||||
format: 时间格式
|
||||
timezone: 时区(暂未实现)
|
||||
|
||||
Returns:
|
||||
日期时间结果
|
||||
"""
|
||||
try:
|
||||
if operation == "now":
|
||||
now = datetime.now()
|
||||
return json.dumps({
|
||||
"datetime": now.strftime(format),
|
||||
"timestamp": now.timestamp(),
|
||||
"iso_format": now.isoformat()
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "format":
|
||||
now = datetime.now()
|
||||
return json.dumps({
|
||||
"formatted": now.strftime(format)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps({
|
||||
"error": f"不支持的操作类型: {operation}"
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"日期时间工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def math_calculate_tool(expression: str) -> str:
|
||||
"""
|
||||
数学计算工具(安全限制:只允许基本数学运算)
|
||||
|
||||
Args:
|
||||
expression: 数学表达式(如 "2+2", "sqrt(16)", "sin(0)")
|
||||
|
||||
Returns:
|
||||
计算结果
|
||||
"""
|
||||
try:
|
||||
# 安全检查:只允许数字、基本运算符和安全的数学函数
|
||||
allowed_chars = set("0123456789+-*/.() ")
|
||||
allowed_functions = ['sqrt', 'sin', 'cos', 'tan', 'log', 'exp', 'abs', 'pow']
|
||||
|
||||
# 检查表达式是否安全
|
||||
if not all(c in allowed_chars or any(f in expression for f in allowed_functions) for c in expression):
|
||||
return json.dumps({
|
||||
"error": "表达式包含不安全的字符"
|
||||
}, ensure_ascii=False)
|
||||
|
||||
# 构建安全的数学环境
|
||||
safe_dict = {
|
||||
"__builtins__": {},
|
||||
"abs": abs,
|
||||
"sqrt": math.sqrt,
|
||||
"sin": math.sin,
|
||||
"cos": math.cos,
|
||||
"tan": math.tan,
|
||||
"log": math.log,
|
||||
"exp": math.exp,
|
||||
"pow": pow,
|
||||
"pi": math.pi,
|
||||
"e": math.e
|
||||
}
|
||||
|
||||
result = eval(expression, safe_dict)
|
||||
|
||||
return json.dumps({
|
||||
"expression": expression,
|
||||
"result": result,
|
||||
"result_type": type(result).__name__
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"数学计算工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def system_info_tool() -> str:
|
||||
"""
|
||||
系统信息工具
|
||||
|
||||
Returns:
|
||||
系统信息
|
||||
"""
|
||||
try:
|
||||
info = {
|
||||
"platform": platform.system(),
|
||||
"platform_version": platform.version(),
|
||||
"architecture": platform.machine(),
|
||||
"processor": platform.processor(),
|
||||
"python_version": sys.version,
|
||||
"python_version_info": {
|
||||
"major": sys.version_info.major,
|
||||
"minor": sys.version_info.minor,
|
||||
"micro": sys.version_info.micro
|
||||
}
|
||||
}
|
||||
|
||||
return json.dumps(info, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"系统信息工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def json_process_tool(json_string: str, operation: str = "parse") -> str:
|
||||
"""
|
||||
JSON处理工具
|
||||
|
||||
Args:
|
||||
json_string: JSON字符串
|
||||
operation: 操作类型(parse=解析, stringify=序列化, validate=验证)
|
||||
|
||||
Returns:
|
||||
处理结果
|
||||
"""
|
||||
try:
|
||||
if operation == "parse":
|
||||
data = json.loads(json_string)
|
||||
return json.dumps({
|
||||
"parsed": data,
|
||||
"type": type(data).__name__
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "stringify":
|
||||
# 如果输入已经是字符串,尝试解析后再序列化
|
||||
try:
|
||||
data = json.loads(json_string)
|
||||
except:
|
||||
data = json_string
|
||||
|
||||
return json.dumps({
|
||||
"stringified": json.dumps(data, ensure_ascii=False, indent=2)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
elif operation == "validate":
|
||||
try:
|
||||
json.loads(json_string)
|
||||
return json.dumps({
|
||||
"valid": True
|
||||
}, ensure_ascii=False)
|
||||
except json.JSONDecodeError as e:
|
||||
return json.dumps({
|
||||
"valid": False,
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
else:
|
||||
return json.dumps({
|
||||
"error": f"不支持的操作类型: {operation}"
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
logger.error(f"JSON处理工具执行失败: {str(e)}")
|
||||
return json.dumps({
|
||||
"error": str(e)
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
async def database_query_tool(query: str, database: str = "default") -> str:
|
||||
"""
|
||||
数据库查询工具(占位实现)
|
||||
|
||||
Args:
|
||||
query: SQL查询语句
|
||||
database: 数据库名称
|
||||
|
||||
Returns:
|
||||
查询结果
|
||||
"""
|
||||
# TODO: 实现数据库查询逻辑
|
||||
return json.dumps({
|
||||
"error": "数据库查询工具尚未实现",
|
||||
"query": query,
|
||||
"database": database
|
||||
}, ensure_ascii=False)
|
||||
|
||||
|
||||
# 工具定义(OpenAI Function格式)
|
||||
HTTP_REQUEST_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "http_request",
|
||||
"description": "发送HTTP请求,支持GET、POST、PUT、DELETE方法。可以用于调用API、获取网页内容等。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "请求的URL地址"
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST", "PUT", "DELETE"],
|
||||
"description": "HTTP请求方法",
|
||||
"default": "GET"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"description": "HTTP请求头(可选)",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "请求体(POST/PUT时使用,可选)"
|
||||
}
|
||||
},
|
||||
"required": ["url", "method"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FILE_READ_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "file_read",
|
||||
"description": "读取文件内容。只能读取项目目录下的文件,确保文件路径正确。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "要读取的文件路径(相对于项目根目录或绝对路径)"
|
||||
}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FILE_WRITE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "file_write",
|
||||
"description": "写入文件内容。只能写入项目目录下的文件,确保文件路径正确。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "要写入的文件路径(相对于项目根目录或绝对路径)"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "要写入的内容"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["w", "a"],
|
||||
"description": "写入模式:w=覆盖写入,a=追加写入",
|
||||
"default": "w"
|
||||
}
|
||||
},
|
||||
"required": ["file_path", "content"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEXT_ANALYZE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "text_analyze",
|
||||
"description": "分析文本内容,支持统计字数、提取关键词、生成摘要等功能。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "要分析的文本内容"
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["count", "keywords", "summary"],
|
||||
"description": "操作类型:count=统计字数等信息,keywords=提取关键词,summary=生成摘要",
|
||||
"default": "count"
|
||||
}
|
||||
},
|
||||
"required": ["text"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DATETIME_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "datetime",
|
||||
"description": "获取和处理日期时间信息,支持获取当前时间、格式化时间等。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["now", "format"],
|
||||
"description": "操作类型:now=获取当前时间,format=格式化时间",
|
||||
"default": "now"
|
||||
},
|
||||
"format": {
|
||||
"type": "string",
|
||||
"description": "时间格式字符串(如:%Y-%m-%d %H:%M:%S)",
|
||||
"default": "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MATH_CALCULATE_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "math_calculate",
|
||||
"description": "执行数学计算,支持基本运算和常用数学函数(如sqrt, sin, cos, log等)。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"type": "string",
|
||||
"description": "数学表达式(如:2+2, sqrt(16), sin(0), pow(2,3))"
|
||||
}
|
||||
},
|
||||
"required": ["expression"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SYSTEM_INFO_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "system_info",
|
||||
"description": "获取系统信息,包括操作系统、Python版本等。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JSON_PROCESS_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "json_process",
|
||||
"description": "处理JSON数据,支持解析、序列化、验证等功能。",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"json_string": {
|
||||
"type": "string",
|
||||
"description": "JSON字符串"
|
||||
},
|
||||
"operation": {
|
||||
"type": "string",
|
||||
"enum": ["parse", "stringify", "validate"],
|
||||
"description": "操作类型:parse=解析JSON,stringify=序列化为JSON,validate=验证JSON格式",
|
||||
"default": "parse"
|
||||
}
|
||||
},
|
||||
"required": ["json_string"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DATABASE_QUERY_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "database_query",
|
||||
"description": "执行数据库查询(暂未实现)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "SQL查询语句"
|
||||
},
|
||||
"database": {
|
||||
"type": "string",
|
||||
"description": "数据库名称",
|
||||
"default": "default"
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
"""
|
||||
LLM服务 - 处理各种LLM提供商的调用
|
||||
"""
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Dict, Any, Optional, List
|
||||
import json
|
||||
import asyncio
|
||||
import logging
|
||||
from openai import AsyncOpenAI
|
||||
from app.core.config import settings
|
||||
from app.services.tool_registry import tool_registry
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LLMService:
|
||||
@@ -219,6 +224,257 @@ class LLMService:
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"不支持的LLM提供商: {provider},目前支持: openai, deepseek")
|
||||
|
||||
async def call_openai_with_tools(
|
||||
self,
|
||||
prompt: str,
|
||||
tools: List[Dict[str, Any]],
|
||||
model: str = "gpt-3.5-turbo",
|
||||
temperature: float = 0.7,
|
||||
max_tokens: Optional[int] = None,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
max_iterations: int = 5
|
||||
) -> str:
|
||||
"""
|
||||
调用OpenAI API,支持工具调用
|
||||
|
||||
Args:
|
||||
prompt: 提示词
|
||||
tools: 工具定义列表(OpenAI Function格式)
|
||||
model: 模型名称
|
||||
temperature: 温度参数
|
||||
max_tokens: 最大token数
|
||||
api_key: API密钥
|
||||
base_url: API地址
|
||||
max_iterations: 最大工具调用迭代次数
|
||||
|
||||
Returns:
|
||||
LLM返回的最终文本
|
||||
"""
|
||||
# 获取客户端
|
||||
if api_key is not None or base_url is not None:
|
||||
final_api_key = api_key if api_key else settings.OPENAI_API_KEY
|
||||
final_base_url = base_url if base_url else settings.OPENAI_BASE_URL
|
||||
if not final_api_key:
|
||||
raise ValueError("OpenAI API Key未配置")
|
||||
client = AsyncOpenAI(api_key=final_api_key, base_url=final_base_url)
|
||||
else:
|
||||
if not self.openai_client:
|
||||
if settings.OPENAI_API_KEY:
|
||||
self.openai_client = AsyncOpenAI(
|
||||
api_key=settings.OPENAI_API_KEY,
|
||||
base_url=settings.OPENAI_BASE_URL
|
||||
)
|
||||
else:
|
||||
raise ValueError("OpenAI API Key未配置")
|
||||
client = self.openai_client
|
||||
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
try:
|
||||
for iteration in range(max_iterations):
|
||||
# 准备工具参数(只在第一次调用时传递tools)
|
||||
create_kwargs = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"max_tokens": max_tokens
|
||||
}
|
||||
|
||||
if iteration == 0:
|
||||
# 转换工具格式为OpenAI格式
|
||||
openai_tools = []
|
||||
for tool in tools:
|
||||
if isinstance(tool, dict):
|
||||
if "type" in tool and tool["type"] == "function":
|
||||
openai_tools.append(tool)
|
||||
elif "function" in tool:
|
||||
openai_tools.append(tool)
|
||||
else:
|
||||
# 假设是function格式,包装一下
|
||||
openai_tools.append({
|
||||
"type": "function",
|
||||
"function": tool
|
||||
})
|
||||
create_kwargs["tools"] = openai_tools
|
||||
create_kwargs["tool_choice"] = "auto"
|
||||
|
||||
# 调用LLM
|
||||
response = await client.chat.completions.create(**create_kwargs)
|
||||
|
||||
message = response.choices[0].message
|
||||
|
||||
# 添加助手回复到消息历史
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": message.content,
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": tc.id,
|
||||
"type": tc.type,
|
||||
"function": {
|
||||
"name": tc.function.name,
|
||||
"arguments": tc.function.arguments
|
||||
}
|
||||
} for tc in (message.tool_calls or [])
|
||||
]
|
||||
})
|
||||
|
||||
# 检查是否有工具调用
|
||||
if message.tool_calls and len(message.tool_calls) > 0:
|
||||
logger.info(f"检测到 {len(message.tool_calls)} 个工具调用")
|
||||
# 处理每个工具调用
|
||||
for tool_call in message.tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
try:
|
||||
tool_args = json.loads(tool_call.function.arguments)
|
||||
except:
|
||||
tool_args = {}
|
||||
|
||||
logger.info(f"执行工具: {tool_name}, 参数: {tool_args}")
|
||||
|
||||
# 执行工具
|
||||
tool_result = await self._execute_tool(tool_name, tool_args)
|
||||
|
||||
# 添加工具结果到消息历史
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": tool_result
|
||||
})
|
||||
else:
|
||||
# 没有工具调用,返回最终回复
|
||||
final_content = message.content or ""
|
||||
if final_content:
|
||||
logger.info("LLM返回最终回复,工具调用完成")
|
||||
return final_content
|
||||
|
||||
# 达到最大迭代次数
|
||||
logger.warning(f"达到最大工具调用迭代次数 ({max_iterations})")
|
||||
last_message = messages[-1] if messages else {}
|
||||
return last_message.get("content", "达到最大工具调用次数")
|
||||
except Exception as e:
|
||||
logger.error(f"工具调用过程中出错: {str(e)}")
|
||||
raise Exception(f"OpenAI工具调用失败: {str(e)}")
|
||||
|
||||
async def call_deepseek_with_tools(
|
||||
self,
|
||||
prompt: str,
|
||||
tools: List[Dict[str, Any]],
|
||||
model: str = "deepseek-chat",
|
||||
temperature: float = 0.7,
|
||||
max_tokens: Optional[int] = None,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
max_iterations: int = 5
|
||||
) -> str:
|
||||
"""
|
||||
调用DeepSeek API,支持工具调用(DeepSeek兼容OpenAI API格式)
|
||||
"""
|
||||
# DeepSeek使用相同的实现
|
||||
return await self.call_openai_with_tools(
|
||||
prompt=prompt,
|
||||
tools=tools,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
api_key=api_key or settings.DEEPSEEK_API_KEY,
|
||||
base_url=base_url or settings.DEEPSEEK_BASE_URL,
|
||||
max_iterations=max_iterations
|
||||
)
|
||||
|
||||
async def call_llm_with_tools(
|
||||
self,
|
||||
prompt: str,
|
||||
tools: List[Dict[str, Any]],
|
||||
provider: str = "openai",
|
||||
model: Optional[str] = None,
|
||||
temperature: float = 0.7,
|
||||
max_tokens: Optional[int] = None,
|
||||
**kwargs
|
||||
) -> str:
|
||||
"""
|
||||
通用LLM调用接口(支持工具)
|
||||
|
||||
Args:
|
||||
prompt: 提示词
|
||||
tools: 工具定义列表
|
||||
provider: 提供商,支持openai、deepseek
|
||||
model: 模型名称
|
||||
temperature: 温度参数
|
||||
max_tokens: 最大token数
|
||||
**kwargs: 其他参数
|
||||
|
||||
Returns:
|
||||
LLM返回的最终文本
|
||||
"""
|
||||
if provider == "openai":
|
||||
if not model:
|
||||
model = "gpt-3.5-turbo"
|
||||
return await self.call_openai_with_tools(
|
||||
prompt=prompt,
|
||||
tools=tools,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
**kwargs
|
||||
)
|
||||
elif provider == "deepseek":
|
||||
if not model:
|
||||
model = "deepseek-chat"
|
||||
return await self.call_deepseek_with_tools(
|
||||
prompt=prompt,
|
||||
tools=tools,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
**kwargs
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"不支持的LLM提供商: {provider},目前支持: openai, deepseek")
|
||||
|
||||
async def _execute_tool(self, tool_name: str, tool_args: Dict[str, Any]) -> str:
|
||||
"""
|
||||
执行工具
|
||||
|
||||
Args:
|
||||
tool_name: 工具名称
|
||||
tool_args: 工具参数
|
||||
|
||||
Returns:
|
||||
工具执行结果(JSON字符串)
|
||||
"""
|
||||
# 从注册表获取工具函数
|
||||
tool_func = tool_registry.get_tool_function(tool_name)
|
||||
|
||||
if not tool_func:
|
||||
error_msg = f"工具 {tool_name} 未找到"
|
||||
logger.error(error_msg)
|
||||
return json.dumps({"error": error_msg}, ensure_ascii=False)
|
||||
|
||||
try:
|
||||
logger.info(f"执行工具 {tool_name},参数: {tool_args}")
|
||||
|
||||
# 执行工具(支持异步函数)
|
||||
if asyncio.iscoroutinefunction(tool_func):
|
||||
result = await tool_func(**tool_args)
|
||||
else:
|
||||
# 同步函数在事件循环中执行
|
||||
result = tool_func(**tool_args)
|
||||
|
||||
# 将结果转换为字符串
|
||||
if isinstance(result, (dict, list)):
|
||||
result_str = json.dumps(result, ensure_ascii=False)
|
||||
else:
|
||||
result_str = str(result)
|
||||
|
||||
logger.info(f"工具 {tool_name} 执行成功,结果长度: {len(result_str)}")
|
||||
return result_str
|
||||
except Exception as e:
|
||||
error_msg = f"工具 {tool_name} 执行失败: {str(e)}"
|
||||
logger.error(error_msg, exc_info=True)
|
||||
return json.dumps({"error": error_msg}, ensure_ascii=False)
|
||||
|
||||
|
||||
# 全局LLM服务实例
|
||||
|
||||
115
backend/app/services/tool_registry.py
Normal file
115
backend/app/services/tool_registry.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
工具注册表 - 管理所有可用工具
|
||||
"""
|
||||
from typing import Dict, Any, Callable, Optional, List
|
||||
import json
|
||||
import logging
|
||||
from app.models.tool import Tool
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ToolRegistry:
|
||||
"""工具注册表 - 管理所有可用工具"""
|
||||
|
||||
def __init__(self):
|
||||
self._builtin_tools: Dict[str, Callable] = {}
|
||||
self._tool_schemas: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def register_builtin_tool(self, name: str, func: Callable, schema: Dict[str, Any]):
|
||||
"""
|
||||
注册内置工具
|
||||
|
||||
Args:
|
||||
name: 工具名称
|
||||
func: 工具函数(可以是同步或异步函数)
|
||||
schema: 工具定义(OpenAI Function格式)
|
||||
"""
|
||||
self._builtin_tools[name] = func
|
||||
self._tool_schemas[name] = schema
|
||||
logger.info(f"注册内置工具: {name}")
|
||||
|
||||
def get_tool_schema(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取工具定义"""
|
||||
return self._tool_schemas.get(name)
|
||||
|
||||
def get_tool_function(self, name: str) -> Optional[Callable]:
|
||||
"""获取工具函数"""
|
||||
return self._builtin_tools.get(name)
|
||||
|
||||
def get_all_tool_schemas(self) -> List[Dict[str, Any]]:
|
||||
"""获取所有工具定义(用于LLM)"""
|
||||
return list(self._tool_schemas.values())
|
||||
|
||||
def load_tools_from_db(self, db: Session, tool_names: List[str] = None):
|
||||
"""
|
||||
从数据库加载工具
|
||||
|
||||
Args:
|
||||
db: 数据库会话
|
||||
tool_names: 工具名称列表(可选,如果为None则加载所有公开工具)
|
||||
"""
|
||||
query = db.query(Tool).filter(Tool.is_public == True)
|
||||
if tool_names:
|
||||
query = query.filter(Tool.name.in_(tool_names))
|
||||
|
||||
tools = query.all()
|
||||
for tool in tools:
|
||||
self._tool_schemas[tool.name] = tool.function_schema
|
||||
# 根据implementation_type加载工具实现
|
||||
if tool.implementation_type == 'builtin':
|
||||
# 从内置工具中查找
|
||||
if tool.name in self._builtin_tools:
|
||||
logger.debug(f"工具 {tool.name} 已在内置工具中注册")
|
||||
else:
|
||||
logger.warning(f"工具 {tool.name} 标记为builtin但未在内置工具中找到")
|
||||
elif tool.implementation_type == 'http':
|
||||
# HTTP工具需要特殊处理
|
||||
self._register_http_tool(tool)
|
||||
elif tool.implementation_type == 'workflow':
|
||||
# 工作流工具
|
||||
self._register_workflow_tool(tool)
|
||||
elif tool.implementation_type == 'code':
|
||||
# 代码执行工具
|
||||
self._register_code_tool(tool)
|
||||
|
||||
logger.info(f"从数据库加载了 {len(tools)} 个工具")
|
||||
|
||||
def _register_http_tool(self, tool: Tool):
|
||||
"""注册HTTP工具"""
|
||||
# TODO: 实现HTTP工具的动态注册
|
||||
logger.warning(f"HTTP工具 {tool.name} 的动态注册尚未实现")
|
||||
|
||||
def _register_workflow_tool(self, tool: Tool):
|
||||
"""注册工作流工具"""
|
||||
# TODO: 实现工作流工具的动态注册
|
||||
logger.warning(f"工作流工具 {tool.name} 的动态注册尚未实现")
|
||||
|
||||
def _register_code_tool(self, tool: Tool):
|
||||
"""注册代码执行工具"""
|
||||
# TODO: 实现代码执行工具的动态注册
|
||||
logger.warning(f"代码执行工具 {tool.name} 的动态注册尚未实现")
|
||||
|
||||
def get_tools_by_names(self, tool_names: List[str]) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
根据工具名称列表获取工具定义
|
||||
|
||||
Args:
|
||||
tool_names: 工具名称列表
|
||||
|
||||
Returns:
|
||||
工具定义列表(OpenAI Function格式)
|
||||
"""
|
||||
tools = []
|
||||
for name in tool_names:
|
||||
schema = self.get_tool_schema(name)
|
||||
if schema:
|
||||
tools.append(schema)
|
||||
else:
|
||||
logger.warning(f"工具 {name} 未找到")
|
||||
return tools
|
||||
|
||||
|
||||
# 全局工具注册表实例
|
||||
tool_registry = ToolRegistry()
|
||||
@@ -725,19 +725,44 @@ class WorkflowEngine:
|
||||
# 记录实际发送给LLM的prompt
|
||||
logger.info(f"[rjb] 准备调用LLM: node_id={node_id}, provider={provider}, model={model}, prompt前200字符='{prompt[:200] if len(prompt) > 200 else prompt}'")
|
||||
|
||||
# 检查是否启用工具调用
|
||||
enable_tools = node_data.get('enable_tools', False)
|
||||
tools_config = node_data.get('tools', []) # 工具名称列表
|
||||
|
||||
# 如果启用了工具,加载工具定义
|
||||
tools = []
|
||||
if enable_tools and tools_config:
|
||||
from app.services.tool_registry import tool_registry
|
||||
# 从注册表加载工具定义
|
||||
tools = tool_registry.get_tools_by_names(tools_config)
|
||||
logger.info(f"[rjb] LLM节点启用工具调用: {len(tools)} 个工具, 工具列表: {tools_config}")
|
||||
|
||||
# 调用LLM服务
|
||||
try:
|
||||
if self.logger:
|
||||
logger.debug(f"[rjb] LLM节点配置: provider={provider}, model={model}, 使用系统默认API Key配置")
|
||||
logger.debug(f"[rjb] LLM节点配置: provider={provider}, model={model}, 使用系统默认API Key配置, 工具调用: {'启用' if tools else '禁用'}")
|
||||
self.logger.info(f"调用LLM服务: {provider}/{model}", node_id=node_id, node_type=node_type)
|
||||
result = await llm_service.call_llm(
|
||||
prompt=prompt,
|
||||
provider=provider,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
# 不传递 api_key 和 base_url,使用系统默认配置
|
||||
)
|
||||
|
||||
# 根据是否启用工具选择不同的调用方式
|
||||
if tools:
|
||||
result = await llm_service.call_llm_with_tools(
|
||||
prompt=prompt,
|
||||
tools=tools,
|
||||
provider=provider,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
)
|
||||
else:
|
||||
result = await llm_service.call_llm(
|
||||
prompt=prompt,
|
||||
provider=provider,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
# 不传递 api_key 和 base_url,使用系统默认配置
|
||||
)
|
||||
|
||||
exec_result = {'output': result, 'status': 'success'}
|
||||
if self.logger:
|
||||
duration = int((time.time() - start_time) * 1000)
|
||||
@@ -748,6 +773,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] LLM节点执行失败: {str(e)}", exc_info=True)
|
||||
return {
|
||||
'output': None,
|
||||
'status': 'failed',
|
||||
@@ -2438,6 +2464,15 @@ class WorkflowEngine:
|
||||
logger.info(f"[rjb] user_input: {user_input[:50]}, output: {str(output)[:50]}, timestamp: {timestamp}")
|
||||
value = eval(value_str, {"__builtins__": {}}, safe_dict)
|
||||
logger.info(f"[rjb] Cache节点 {node_id} value模板执行成功,类型: {type(value)}")
|
||||
|
||||
# 确保conversation_history只保留最近的20条(性能优化)
|
||||
if isinstance(value, dict) and 'conversation_history' in value:
|
||||
if isinstance(value['conversation_history'], list):
|
||||
max_history_length = 20
|
||||
if len(value['conversation_history']) > max_history_length:
|
||||
value['conversation_history'] = value['conversation_history'][-max_history_length:]
|
||||
logger.info(f"[rjb] 对话历史已截断,保留最近 {max_history_length} 条")
|
||||
|
||||
if isinstance(value, dict):
|
||||
logger.info(f"[rjb] keys: {list(value.keys())}")
|
||||
if 'conversation_history' in value:
|
||||
|
||||
@@ -78,7 +78,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.3",
|
||||
"max_tokens": "1000",
|
||||
"max_tokens": "200",
|
||||
"prompt": """你是一个专业的对话意图分析助手。请分析用户的输入,识别用户的意图和情感。
|
||||
|
||||
用户输入:{{user_input}}
|
||||
@@ -129,7 +129,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.7",
|
||||
"max_tokens": "500",
|
||||
"max_tokens": "200",
|
||||
"prompt": """你是一个温暖、友好的AI助手。用户向你打招呼,请用自然、亲切的方式回应。
|
||||
|
||||
用户输入:{{user_input}}
|
||||
@@ -150,19 +150,20 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.5",
|
||||
"max_tokens": "2000",
|
||||
"prompt": """你是一个知识渊博、乐于助人的AI助手。请回答用户的问题。
|
||||
"max_tokens": "500",
|
||||
"prompt": """你是一个知识渊博、乐于助人的AI助手。请简洁、准确地回答用户的问题。
|
||||
|
||||
用户问题:{{user_input}}
|
||||
对话历史:{{memory.conversation_history}}
|
||||
意图分析:{{output}}
|
||||
|
||||
请提供:
|
||||
1. 直接、准确的答案
|
||||
2. 必要的解释和说明
|
||||
3. 如果问题不明确,友好地询问更多信息
|
||||
回答要求:
|
||||
1. 直接给出核心答案,避免冗长描述
|
||||
2. 如果是介绍类问题(如"你能做什么"),用简洁的要点列举,控制在100字以内
|
||||
3. 如果是知识性问题,提供准确答案和简要说明,控制在150字以内
|
||||
4. 如果问题不明确,友好地询问更多信息,控制在50字以内
|
||||
|
||||
请以自然、易懂的方式回答,长度控制在200字以内。直接输出回答内容。"""
|
||||
请以自然、简洁的方式回答,避免重复和冗余。直接输出回答内容,无需额外格式化。"""
|
||||
}
|
||||
}
|
||||
nodes.append(question_node)
|
||||
@@ -177,7 +178,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.8",
|
||||
"max_tokens": "1000",
|
||||
"max_tokens": "500",
|
||||
"prompt": """你是一个善解人意的AI助手。请根据用户的情感状态,给予适当的回应。
|
||||
|
||||
用户输入:{{user_input}}
|
||||
@@ -204,7 +205,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.4",
|
||||
"max_tokens": "1500",
|
||||
"max_tokens": "800",
|
||||
"prompt": """你是一个专业的AI助手。用户提出了一个请求,请分析并回应。
|
||||
|
||||
用户请求:{{user_input}}
|
||||
@@ -231,7 +232,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.6",
|
||||
"max_tokens": "300",
|
||||
"max_tokens": "150",
|
||||
"prompt": """你是一个友好的AI助手。用户要结束对话,请给予温暖的告别。
|
||||
|
||||
用户输入:{{user_input}}
|
||||
@@ -252,7 +253,7 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.6",
|
||||
"max_tokens": "1000",
|
||||
"max_tokens": "500",
|
||||
"prompt": """你是一个友好、专业的AI助手。请回应用户的输入。
|
||||
|
||||
用户输入:{{user_input}}
|
||||
@@ -286,31 +287,24 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"label": "更新记忆",
|
||||
"operation": "set",
|
||||
"key": "user_memory_{user_id}",
|
||||
"value": '{"conversation_history": {{memory.conversation_history}} + [{"role": "user", "content": "{{user_input}}", "timestamp": "{{timestamp}}"}, {"role": "assistant", "content": "{{output}}", "timestamp": "{{timestamp}}"}], "user_profile": {{memory.user_profile}}, "context": {{memory.context}}}',
|
||||
"value": '{"conversation_history": ({{memory.conversation_history}} + [{"role": "user", "content": "{{user_input}}", "timestamp": "{{timestamp}}"}, {"role": "assistant", "content": "{{output}}", "timestamp": "{{timestamp}}"}]), "user_profile": {{memory.user_profile}}, "context": {{memory.context}}}',
|
||||
"ttl": 86400
|
||||
}
|
||||
}
|
||||
nodes.append(update_memory_node)
|
||||
|
||||
# ========== 14. 格式化最终回复 ==========
|
||||
format_response_node = {
|
||||
"id": "llm-format",
|
||||
"type": "llm",
|
||||
# ========== 14. JSON提取节点 - 提取最终回答文本 ==========
|
||||
json_extract_node = {
|
||||
"id": "json-extract",
|
||||
"type": "json",
|
||||
"position": {"x": 1650, "y": 400},
|
||||
"data": {
|
||||
"label": "格式化回复",
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.3",
|
||||
"max_tokens": "500",
|
||||
"prompt": """请将以下回复内容格式化为最终输出。确保回复自然、流畅。
|
||||
|
||||
原始回复:{{output}}
|
||||
|
||||
请直接输出格式化后的回复内容,不要包含其他说明或标记。如果原始回复已经是合适的格式,直接输出即可。"""
|
||||
"label": "提取回答",
|
||||
"operation": "extract",
|
||||
"path": "right.right.right"
|
||||
}
|
||||
}
|
||||
nodes.append(format_response_node)
|
||||
nodes.append(json_extract_node)
|
||||
|
||||
# ========== 15. 结束节点 ==========
|
||||
end_node = {
|
||||
@@ -458,19 +452,19 @@ def generate_chat_agent(db: Session, user: User):
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
# 更新记忆 -> 格式化回复
|
||||
# 更新记忆 -> JSON提取
|
||||
edges.append({
|
||||
"id": "e8",
|
||||
"source": "cache-update",
|
||||
"target": "llm-format",
|
||||
"target": "json-extract",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
# 格式化回复 -> 结束
|
||||
# JSON提取 -> 结束
|
||||
edges.append({
|
||||
"id": "e9",
|
||||
"source": "llm-format",
|
||||
"source": "json-extract",
|
||||
"target": "end-1",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
|
||||
334
backend/scripts/generate_knowledge_base_qa_agent.py
Normal file
334
backend/scripts/generate_knowledge_base_qa_agent.py
Normal file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
生成知识库问答Agent示例
|
||||
展示如何使用向量数据库和RAG技术构建知识库问答系统,包含:
|
||||
- 文本向量化(HTTP节点调用embedding API)
|
||||
- 向量数据库检索(vector_db节点)
|
||||
- 基于检索结果的答案生成(LLM节点)
|
||||
- 上下文整合和格式化
|
||||
"""
|
||||
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 datetime import datetime
|
||||
import uuid
|
||||
|
||||
|
||||
def generate_knowledge_base_qa_agent(db: Session, user: User):
|
||||
"""生成知识库问答Agent"""
|
||||
nodes = []
|
||||
edges = []
|
||||
|
||||
# ========== 1. 开始节点 ==========
|
||||
start_node = {
|
||||
"id": "start-1",
|
||||
"type": "start",
|
||||
"position": {"x": 50, "y": 400},
|
||||
"data": {
|
||||
"label": "开始",
|
||||
"output_format": "json"
|
||||
}
|
||||
}
|
||||
nodes.append(start_node)
|
||||
|
||||
# ========== 2. 问题预处理节点 ==========
|
||||
preprocess_node = {
|
||||
"id": "transform-preprocess",
|
||||
"type": "transform",
|
||||
"position": {"x": 250, "y": 400},
|
||||
"data": {
|
||||
"label": "问题预处理",
|
||||
"mode": "merge",
|
||||
"mapping": {
|
||||
"query": "{{query}}",
|
||||
"user_id": "{{user_id}}",
|
||||
"timestamp": "{{timestamp}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.append(preprocess_node)
|
||||
|
||||
# ========== 3. 文本向量化节点(HTTP调用embedding API)==========
|
||||
# 注意:需要在HTTP节点配置中手动设置Authorization header,使用环境变量DEEPSEEK_API_KEY
|
||||
embedding_node = {
|
||||
"id": "http-embedding",
|
||||
"type": "http",
|
||||
"position": {"x": 450, "y": 400},
|
||||
"data": {
|
||||
"label": "文本向量化",
|
||||
"method": "POST",
|
||||
"url": "https://api.deepseek.com/v1/embeddings",
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer sk-fdf7cc1c73504e628ec0119b7e11b8cc"
|
||||
},
|
||||
"body": {
|
||||
"model": "deepseek-embedding",
|
||||
"input": "{{query}}"
|
||||
},
|
||||
"response_format": "json"
|
||||
}
|
||||
}
|
||||
nodes.append(embedding_node)
|
||||
|
||||
# ========== 4. 提取embedding向量节点 ==========
|
||||
extract_embedding_node = {
|
||||
"id": "json-extract-embedding",
|
||||
"type": "json",
|
||||
"position": {"x": 650, "y": 400},
|
||||
"data": {
|
||||
"label": "提取向量",
|
||||
"operation": "extract",
|
||||
"path": "output.data.data[0].embedding"
|
||||
}
|
||||
}
|
||||
nodes.append(extract_embedding_node)
|
||||
|
||||
# ========== 5. 准备向量搜索数据节点 ==========
|
||||
prepare_search_node = {
|
||||
"id": "transform-prepare-search",
|
||||
"type": "transform",
|
||||
"position": {"x": 850, "y": 400},
|
||||
"data": {
|
||||
"label": "准备搜索数据",
|
||||
"mode": "merge",
|
||||
"mapping": {
|
||||
"embedding": "{{output}}",
|
||||
"query": "{{query}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.append(prepare_search_node)
|
||||
|
||||
# ========== 6. 向量数据库检索节点 ==========
|
||||
vector_search_node = {
|
||||
"id": "vector-search",
|
||||
"type": "vector_db",
|
||||
"position": {"x": 1050, "y": 400},
|
||||
"data": {
|
||||
"label": "知识库检索",
|
||||
"operation": "search",
|
||||
"collection": "knowledge_base",
|
||||
"query_vector": "{{embedding}}",
|
||||
"top_k": 5
|
||||
}
|
||||
}
|
||||
nodes.append(vector_search_node)
|
||||
|
||||
# ========== 7. 整理检索结果节点 ==========
|
||||
format_results_node = {
|
||||
"id": "transform-format-results",
|
||||
"type": "transform",
|
||||
"position": {"x": 1250, "y": 400},
|
||||
"data": {
|
||||
"label": "整理检索结果",
|
||||
"mode": "merge",
|
||||
"mapping": {
|
||||
"query": "{{query}}",
|
||||
"search_results": "{{output}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes.append(format_results_node)
|
||||
|
||||
# ========== 8. 生成答案节点(LLM)==========
|
||||
answer_node = {
|
||||
"id": "llm-answer",
|
||||
"type": "llm",
|
||||
"position": {"x": 1650, "y": 400},
|
||||
"data": {
|
||||
"label": "生成答案",
|
||||
"provider": "deepseek",
|
||||
"model": "deepseek-chat",
|
||||
"temperature": "0.7",
|
||||
"max_tokens": "2000",
|
||||
"prompt": """你是一个专业的知识库问答助手。请基于提供的知识库内容回答用户的问题。
|
||||
|
||||
用户问题:{{query}}
|
||||
|
||||
相关知识库内容(从向量搜索中检索到的相关文档):
|
||||
{{search_results}}
|
||||
|
||||
请根据以上知识库内容回答用户的问题。要求:
|
||||
1. 答案要准确、完整,基于知识库内容
|
||||
2. 如果知识库中没有相关信息,请明确说明"根据知识库,未找到相关信息"
|
||||
3. 答案要清晰、有条理,使用Markdown格式
|
||||
4. 如果知识库内容与问题不完全匹配,可以结合常识进行补充说明,但要标注哪些是知识库内容,哪些是补充说明
|
||||
|
||||
请直接输出答案,不要包含其他格式说明。"""
|
||||
}
|
||||
}
|
||||
nodes.append(answer_node)
|
||||
|
||||
# ========== 9. 提取最终答案节点 ==========
|
||||
extract_answer_node = {
|
||||
"id": "json-extract-answer",
|
||||
"type": "json",
|
||||
"position": {"x": 1850, "y": 400},
|
||||
"data": {
|
||||
"label": "提取最终答案",
|
||||
"operation": "extract",
|
||||
"path": "output"
|
||||
}
|
||||
}
|
||||
nodes.append(extract_answer_node)
|
||||
|
||||
# ========== 10. 结束节点 ==========
|
||||
end_node = {
|
||||
"id": "end-1",
|
||||
"type": "end",
|
||||
"position": {"x": 2050, "y": 400},
|
||||
"data": {
|
||||
"label": "结束"
|
||||
}
|
||||
}
|
||||
nodes.append(end_node)
|
||||
|
||||
# ========== 连接边 ==========
|
||||
edges.append({
|
||||
"id": "e1",
|
||||
"source": "start-1",
|
||||
"target": "transform-preprocess",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e2",
|
||||
"source": "transform-preprocess",
|
||||
"target": "http-embedding",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e3",
|
||||
"source": "http-embedding",
|
||||
"target": "json-extract-embedding",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e4",
|
||||
"source": "json-extract-embedding",
|
||||
"target": "transform-prepare-search",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e5",
|
||||
"source": "transform-prepare-search",
|
||||
"target": "vector-search",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e6",
|
||||
"source": "vector-search",
|
||||
"target": "transform-format-results",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e7",
|
||||
"source": "transform-format-results",
|
||||
"target": "llm-answer",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e8",
|
||||
"source": "llm-answer",
|
||||
"target": "json-extract-answer",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
edges.append({
|
||||
"id": "e9",
|
||||
"source": "json-extract-answer",
|
||||
"target": "end-1",
|
||||
"sourceHandle": "right",
|
||||
"targetHandle": "left"
|
||||
})
|
||||
|
||||
return {
|
||||
"name": "知识库问答助手",
|
||||
"description": "基于向量数据库和RAG技术的知识库问答系统,支持语义搜索和智能回答。需要先使用向量数据库节点将知识库文档向量化并存储。",
|
||||
"workflow_config": {"nodes": nodes, "edges": edges}
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# 获取或创建测试用户
|
||||
user = db.query(User).first()
|
||||
if not user:
|
||||
print("❌ 未找到用户,请先创建用户")
|
||||
return
|
||||
|
||||
print(f"📝 使用用户: {user.username} (ID: {user.id})")
|
||||
|
||||
# 生成Agent数据
|
||||
agent_data = generate_knowledge_base_qa_agent(db, user)
|
||||
|
||||
# 检查是否已存在
|
||||
existing = db.query(Agent).filter(
|
||||
Agent.name == agent_data["name"],
|
||||
Agent.user_id == user.id
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
print(f"Agent '{agent_data['name']}' 已存在,跳过创建")
|
||||
return
|
||||
|
||||
# 创建Agent
|
||||
agent = Agent(
|
||||
name=agent_data["name"],
|
||||
description=agent_data["description"],
|
||||
workflow_config=agent_data["workflow_config"],
|
||||
user_id=user.id,
|
||||
status="draft"
|
||||
)
|
||||
db.add(agent)
|
||||
db.commit()
|
||||
db.refresh(agent)
|
||||
|
||||
print(f"✅ 成功创建Agent: {agent.name} (ID: {agent.id})")
|
||||
print(f" 节点数量: {len(agent_data['workflow_config']['nodes'])}")
|
||||
print(f" 连接数量: {len(agent_data['workflow_config']['edges'])}")
|
||||
print(f"\n📝 使用说明:")
|
||||
print(f" 1. 在Agent管理页面找到 '{agent.name}'")
|
||||
print(f" 2. 点击'设计'按钮进入工作流编辑器")
|
||||
print(f" 3. 配置HTTP节点的API密钥(DeepSeek API Key)")
|
||||
print(f" 4. 使用向量数据库节点将知识库文档向量化并存储到 'knowledge_base' 集合")
|
||||
print(f" 5. 点击'发布'按钮发布Agent")
|
||||
print(f" 6. 点击'使用'按钮测试问答功能")
|
||||
print(f"\n💡 提示:")
|
||||
print(f" - 知识库文档需要先通过向量数据库节点的 'upsert' 操作存储")
|
||||
print(f" - 每个文档需要包含 'text' 和 'embedding' 字段")
|
||||
print(f" - 可以使用HTTP节点调用embedding API将文档文本转为向量")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建Agent失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
db.rollback()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
79
backend/scripts/init_builtin_tools.py
Normal file
79
backend/scripts/init_builtin_tools.py
Normal file
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
初始化内置工具脚本
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.tool import Tool
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.services.builtin_tools import (
|
||||
http_request_tool,
|
||||
file_read_tool,
|
||||
HTTP_REQUEST_SCHEMA,
|
||||
FILE_READ_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
def init_builtin_tools():
|
||||
"""初始化内置工具"""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 注册内置工具到注册表
|
||||
tool_registry.register_builtin_tool(
|
||||
"http_request",
|
||||
http_request_tool,
|
||||
HTTP_REQUEST_SCHEMA
|
||||
)
|
||||
|
||||
tool_registry.register_builtin_tool(
|
||||
"file_read",
|
||||
file_read_tool,
|
||||
FILE_READ_SCHEMA
|
||||
)
|
||||
|
||||
print("✅ 内置工具已注册到工具注册表")
|
||||
|
||||
# 保存到数据库
|
||||
tools_to_create = [
|
||||
("http_request", HTTP_REQUEST_SCHEMA, "发送HTTP请求,支持GET、POST、PUT、DELETE方法"),
|
||||
("file_read", FILE_READ_SCHEMA, "读取文件内容,只能读取项目目录下的文件")
|
||||
]
|
||||
|
||||
created_count = 0
|
||||
for tool_name, tool_schema, description in tools_to_create:
|
||||
existing = db.query(Tool).filter(Tool.name == tool_name).first()
|
||||
if not existing:
|
||||
tool = Tool(
|
||||
name=tool_name,
|
||||
description=description,
|
||||
category="builtin",
|
||||
function_schema=tool_schema,
|
||||
implementation_type="builtin",
|
||||
is_public=True
|
||||
)
|
||||
db.add(tool)
|
||||
created_count += 1
|
||||
print(f"✅ 创建工具: {tool_name}")
|
||||
else:
|
||||
# 更新工具定义
|
||||
existing.function_schema = tool_schema
|
||||
existing.description = description
|
||||
print(f"ℹ️ 更新工具: {tool_name}")
|
||||
|
||||
db.commit()
|
||||
print(f"\n✅ 内置工具初始化完成!创建了 {created_count} 个工具")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"❌ 初始化失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_builtin_tools()
|
||||
53
backend/test_cors.py
Normal file
53
backend/test_cors.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试CORS配置
|
||||
"""
|
||||
import requests
|
||||
|
||||
# 测试CORS配置
|
||||
def test_cors():
|
||||
base_url = "http://101.43.95.130:8037"
|
||||
|
||||
# 测试OPTIONS请求(CORS预检)
|
||||
print("测试CORS预检请求...")
|
||||
headers = {
|
||||
"Origin": "http://101.43.95.130:8038",
|
||||
"Access-Control-Request-Method": "GET",
|
||||
"Access-Control-Request-Headers": "Content-Type"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.options(f"{base_url}/api/v1/tools/builtin", headers=headers)
|
||||
print(f"OPTIONS请求状态码: {response.status_code}")
|
||||
print(f"CORS头信息:")
|
||||
for key, value in response.headers.items():
|
||||
if key.lower().startswith('access-control'):
|
||||
print(f" {key}: {value}")
|
||||
except Exception as e:
|
||||
print(f"OPTIONS请求失败: {e}")
|
||||
|
||||
# 测试GET请求
|
||||
print("\n测试GET请求...")
|
||||
headers = {
|
||||
"Origin": "http://101.43.95.130:8038"
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.get(f"{base_url}/api/v1/tools/builtin", headers=headers)
|
||||
print(f"GET请求状态码: {response.status_code}")
|
||||
print(f"CORS头信息:")
|
||||
for key, value in response.headers.items():
|
||||
if key.lower().startswith('access-control'):
|
||||
print(f" {key}: {value}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print(f"\n✅ 请求成功!")
|
||||
data = response.json()
|
||||
print(f"返回工具数量: {len(data)}")
|
||||
else:
|
||||
print(f"\n❌ 请求失败: {response.text}")
|
||||
except Exception as e:
|
||||
print(f"GET请求失败: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cors()
|
||||
File diff suppressed because it is too large
Load Diff
102
publish_agent.py
Normal file
102
publish_agent.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
发布Agent脚本
|
||||
"""
|
||||
import requests
|
||||
import sys
|
||||
|
||||
BASE_URL = "http://localhost:8037"
|
||||
|
||||
def login(username="admin", password="123456"):
|
||||
"""用户登录"""
|
||||
login_data = {
|
||||
"username": username,
|
||||
"password": password
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(f"{BASE_URL}/api/v1/auth/login", data=login_data)
|
||||
if response.status_code != 200:
|
||||
print(f"❌ 登录失败: {response.status_code}")
|
||||
return None, None
|
||||
|
||||
token = response.json().get("access_token")
|
||||
if not token:
|
||||
print("❌ 登录失败: 未获取到token")
|
||||
return None, None
|
||||
|
||||
print(f"✅ 登录成功 (用户: {username})")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
return token, headers
|
||||
except Exception as e:
|
||||
print(f"❌ 登录异常: {str(e)}")
|
||||
return None, None
|
||||
|
||||
def deploy_agent(agent_id, headers):
|
||||
"""发布Agent"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/api/v1/agents/{agent_id}/deploy",
|
||||
headers=headers
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
agent = response.json()
|
||||
print(f"✅ Agent发布成功: {agent.get('name')} (状态: {agent.get('status')})")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ 发布失败: {response.status_code}")
|
||||
print(f"响应: {response.text}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ 发布异常: {str(e)}")
|
||||
return False
|
||||
|
||||
def find_agent_by_name(agent_name, headers):
|
||||
"""通过名称查找Agent"""
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{BASE_URL}/api/v1/agents",
|
||||
headers=headers,
|
||||
params={"search": agent_name, "limit": 100}
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
print(f"❌ 获取Agent列表失败: {response.status_code}")
|
||||
return None
|
||||
|
||||
agents = response.json()
|
||||
for agent in agents:
|
||||
if agent.get("name") == agent_name:
|
||||
return agent
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ 查找Agent异常: {str(e)}")
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
agent_name = sys.argv[1] if len(sys.argv) > 1 else "知识库问答助手"
|
||||
|
||||
print(f"📝 发布Agent: {agent_name}\n")
|
||||
|
||||
# 登录
|
||||
token, headers = login()
|
||||
if not token:
|
||||
sys.exit(1)
|
||||
|
||||
# 查找Agent
|
||||
print(f"🔍 查找Agent: {agent_name}")
|
||||
agent = find_agent_by_name(agent_name, headers)
|
||||
if not agent:
|
||||
print(f"❌ 未找到Agent: {agent_name}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"✅ 找到Agent: {agent.get('name')} (ID: {agent.get('id')}, 状态: {agent.get('status')})")
|
||||
|
||||
# 发布Agent
|
||||
print(f"\n🚀 发布Agent...")
|
||||
if deploy_agent(agent.get('id'), headers):
|
||||
print("\n✅ 完成!")
|
||||
else:
|
||||
sys.exit(1)
|
||||
@@ -294,15 +294,100 @@ def get_execution_result(execution_id, headers):
|
||||
|
||||
if output_data:
|
||||
if isinstance(output_data, dict):
|
||||
# 尝试提取文本输出
|
||||
text_output = (
|
||||
output_data.get("output") or
|
||||
output_data.get("text") or
|
||||
output_data.get("content") or
|
||||
output_data.get("result") or
|
||||
json.dumps(output_data, ensure_ascii=False, indent=2)
|
||||
)
|
||||
print(text_output)
|
||||
# 如果 result 字段是字符串,尝试解析它(类似JSON节点的parse操作)
|
||||
if "result" in output_data and isinstance(output_data["result"], str):
|
||||
try:
|
||||
# 尝试使用 ast.literal_eval 解析Python字典字符串
|
||||
import ast
|
||||
parsed_result = ast.literal_eval(output_data["result"])
|
||||
output_data = parsed_result
|
||||
except:
|
||||
# 如果解析失败,尝试作为JSON解析
|
||||
try:
|
||||
parsed_result = json.loads(output_data["result"])
|
||||
output_data = parsed_result
|
||||
except:
|
||||
pass
|
||||
|
||||
# 使用类似JSON节点的extract操作来提取文本
|
||||
def json_extract(data, path):
|
||||
"""类似JSON节点的extract操作,使用路径提取数据"""
|
||||
if not path or not isinstance(data, dict):
|
||||
return None
|
||||
# 支持 $.right.right.right 格式的路径
|
||||
path = path.replace('$.', '').replace('$', '')
|
||||
keys = path.split('.')
|
||||
result = data
|
||||
for key in keys:
|
||||
if isinstance(result, dict) and key in result:
|
||||
result = result[key]
|
||||
else:
|
||||
return None
|
||||
return result
|
||||
|
||||
# 尝试使用路径提取:递归查找 right 字段直到找到字符串
|
||||
def extract_text_by_path(data, depth=0, max_depth=10):
|
||||
"""递归提取嵌套在right字段中的文本"""
|
||||
if depth > max_depth:
|
||||
return None
|
||||
if isinstance(data, str):
|
||||
# 如果是字符串且不是JSON格式,返回它
|
||||
if len(data) > 10 and not data.strip().startswith('{') and not data.strip().startswith('['):
|
||||
return data
|
||||
return None
|
||||
if isinstance(data, dict):
|
||||
# 优先查找 right 字段
|
||||
if "right" in data:
|
||||
right_value = data["right"]
|
||||
# 如果 right 的值是字符串,直接返回
|
||||
if isinstance(right_value, str) and len(right_value) > 10:
|
||||
return right_value
|
||||
# 否则递归查找
|
||||
result = extract_text_by_path(right_value, depth + 1, max_depth)
|
||||
if result:
|
||||
return result
|
||||
# 查找其他常见的输出字段
|
||||
for key in ["output", "text", "content"]:
|
||||
if key in data:
|
||||
result = extract_text_by_path(data[key], depth + 1, max_depth)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
return None
|
||||
|
||||
# 优先检查 result 字段(JSON节点提取后的结果)
|
||||
if "result" in output_data and isinstance(output_data["result"], str):
|
||||
text_output = output_data["result"]
|
||||
else:
|
||||
# 先尝试使用路径提取(类似JSON节点的extract操作)
|
||||
# 尝试多个可能的路径
|
||||
paths_to_try = [
|
||||
"right.right.right", # 最常见的嵌套路径
|
||||
"right.right",
|
||||
"right",
|
||||
"output",
|
||||
"text",
|
||||
"content"
|
||||
]
|
||||
|
||||
text_output = None
|
||||
for path in paths_to_try:
|
||||
extracted = json_extract(output_data, f"$.{path}")
|
||||
if extracted and isinstance(extracted, str) and len(extracted) > 10:
|
||||
text_output = extracted
|
||||
break
|
||||
|
||||
# 如果路径提取失败,使用递归提取
|
||||
if not text_output:
|
||||
text_output = extract_text_by_path(output_data)
|
||||
|
||||
if text_output and isinstance(text_output, str):
|
||||
print(text_output)
|
||||
print()
|
||||
print_info(f"回答长度: {len(text_output)} 字符")
|
||||
else:
|
||||
# 如果无法提取,显示格式化的JSON
|
||||
print(json.dumps(output_data, ensure_ascii=False, indent=2))
|
||||
else:
|
||||
print(output_data)
|
||||
else:
|
||||
|
||||
287
内置工具列表.md
Normal file
287
内置工具列表.md
Normal file
@@ -0,0 +1,287 @@
|
||||
# 内置工具列表
|
||||
|
||||
平台目前提供 **8个内置工具**,可以在LLM节点中启用工具调用来使用。
|
||||
|
||||
## 📋 工具列表
|
||||
|
||||
### 1. 🌐 http_request - HTTP请求工具
|
||||
|
||||
**功能**: 发送HTTP请求,支持GET、POST、PUT、DELETE方法
|
||||
|
||||
**用途**:
|
||||
- 调用外部API
|
||||
- 获取网页内容
|
||||
- 发送数据到服务器
|
||||
|
||||
**参数**:
|
||||
- `url` (必需): 请求的URL地址
|
||||
- `method` (可选): HTTP方法,默认GET
|
||||
- `headers` (可选): HTTP请求头
|
||||
- `body` (可选): 请求体(POST/PUT时使用)
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"method": "GET"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 📖 file_read - 文件读取工具
|
||||
|
||||
**功能**: 读取文件内容
|
||||
|
||||
**用途**:
|
||||
- 读取配置文件
|
||||
- 读取文档内容
|
||||
- 读取数据文件
|
||||
|
||||
**参数**:
|
||||
- `file_path` (必需): 文件路径(只能读取项目目录下的文件)
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"file_path": "backend/app/core/config.py"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ✍️ file_write - 文件写入工具
|
||||
|
||||
**功能**: 写入文件内容
|
||||
|
||||
**用途**:
|
||||
- 保存处理结果
|
||||
- 创建配置文件
|
||||
- 写入日志文件
|
||||
|
||||
**参数**:
|
||||
- `file_path` (必需): 文件路径(只能写入项目目录下的文件)
|
||||
- `content` (必需): 要写入的内容
|
||||
- `mode` (可选): 写入模式(w=覆盖,a=追加),默认w
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"file_path": "output/result.txt",
|
||||
"content": "处理结果",
|
||||
"mode": "w"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 📊 text_analyze - 文本分析工具
|
||||
|
||||
**功能**: 分析文本内容
|
||||
|
||||
**用途**:
|
||||
- 统计文本字数、行数等
|
||||
- 提取关键词
|
||||
- 生成文本摘要
|
||||
|
||||
**参数**:
|
||||
- `text` (必需): 要分析的文本内容
|
||||
- `operation` (可选): 操作类型
|
||||
- `count`: 统计字数、字符数、行数、段落数
|
||||
- `keywords`: 提取关键词(基于词频)
|
||||
- `summary`: 生成摘要(取前3句)
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"text": "这是一段很长的文本...",
|
||||
"operation": "count"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 🕐 datetime - 日期时间工具
|
||||
|
||||
**功能**: 获取和处理日期时间信息
|
||||
|
||||
**用途**:
|
||||
- 获取当前时间
|
||||
- 格式化时间
|
||||
- 时间戳转换
|
||||
|
||||
**参数**:
|
||||
- `operation` (可选): 操作类型
|
||||
- `now`: 获取当前时间(默认)
|
||||
- `format`: 格式化时间
|
||||
- `format` (可选): 时间格式字符串,默认 "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"operation": "now",
|
||||
"format": "%Y-%m-%d %H:%M:%S"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. 🔢 math_calculate - 数学计算工具
|
||||
|
||||
**功能**: 执行数学计算
|
||||
|
||||
**用途**:
|
||||
- 基本数学运算(加减乘除)
|
||||
- 数学函数计算(sqrt, sin, cos, log等)
|
||||
- 复杂数学表达式计算
|
||||
|
||||
**参数**:
|
||||
- `expression` (必需): 数学表达式
|
||||
|
||||
**支持的函数**:
|
||||
- `sqrt(x)`: 平方根
|
||||
- `sin(x)`, `cos(x)`, `tan(x)`: 三角函数
|
||||
- `log(x)`: 自然对数
|
||||
- `exp(x)`: 指数函数
|
||||
- `abs(x)`: 绝对值
|
||||
- `pow(x, y)`: 幂运算
|
||||
- `pi`: 圆周率
|
||||
- `e`: 自然常数
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"expression": "sqrt(16) + sin(0) * cos(0)"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 💻 system_info - 系统信息工具
|
||||
|
||||
**功能**: 获取系统信息
|
||||
|
||||
**用途**:
|
||||
- 查看操作系统信息
|
||||
- 查看Python版本
|
||||
- 查看系统架构
|
||||
|
||||
**参数**: 无
|
||||
|
||||
**返回信息**:
|
||||
- 操作系统平台
|
||||
- 系统版本
|
||||
- 处理器架构
|
||||
- Python版本
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 📦 json_process - JSON处理工具
|
||||
|
||||
**功能**: 处理JSON数据
|
||||
|
||||
**用途**:
|
||||
- 解析JSON字符串
|
||||
- 序列化数据为JSON
|
||||
- 验证JSON格式
|
||||
|
||||
**参数**:
|
||||
- `json_string` (必需): JSON字符串
|
||||
- `operation` (可选): 操作类型
|
||||
- `parse`: 解析JSON(默认)
|
||||
- `stringify`: 序列化为JSON
|
||||
- `validate`: 验证JSON格式
|
||||
|
||||
**示例**:
|
||||
```json
|
||||
{
|
||||
"json_string": "{\"name\": \"test\"}",
|
||||
"operation": "parse"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 使用场景示例
|
||||
|
||||
### 场景1: 数据获取和处理
|
||||
```
|
||||
用户: "查询GitHub用户信息并保存到文件"
|
||||
→ LLM调用 http_request 获取数据
|
||||
→ LLM调用 json_process 解析数据
|
||||
→ LLM调用 file_write 保存结果
|
||||
```
|
||||
|
||||
### 场景2: 文本分析
|
||||
```
|
||||
用户: "分析这段文本的字数和关键词"
|
||||
→ LLM调用 text_analyze (count) 统计字数
|
||||
→ LLM调用 text_analyze (keywords) 提取关键词
|
||||
```
|
||||
|
||||
### 场景3: 数学计算
|
||||
```
|
||||
用户: "计算 2的10次方 加上 16的平方根"
|
||||
→ LLM调用 math_calculate("pow(2, 10) + sqrt(16)")
|
||||
```
|
||||
|
||||
### 场景4: 文件处理
|
||||
```
|
||||
用户: "读取config.json文件并解析"
|
||||
→ LLM调用 file_read 读取文件
|
||||
→ LLM调用 json_process 解析内容
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 安全限制
|
||||
|
||||
1. **文件操作限制**:
|
||||
- 只能读写项目目录下的文件
|
||||
- 不允许访问系统敏感文件
|
||||
|
||||
2. **数学计算限制**:
|
||||
- 只允许安全的数学函数
|
||||
- 不允许执行任意代码
|
||||
|
||||
3. **HTTP请求限制**:
|
||||
- 超时时间:30秒
|
||||
- 建议在生产环境中添加域名白名单
|
||||
|
||||
---
|
||||
|
||||
## 📝 如何启用工具
|
||||
|
||||
1. 在工作流编辑器中,选择LLM节点
|
||||
2. 打开"工具"标签页
|
||||
3. 启用"启用工具调用"开关
|
||||
4. 选择需要的工具(可多选)
|
||||
5. 保存配置
|
||||
|
||||
---
|
||||
|
||||
## 🔄 工具调用流程
|
||||
|
||||
```
|
||||
用户输入
|
||||
↓
|
||||
LLM节点(启用工具)
|
||||
↓
|
||||
LLM分析需求,决定调用哪个工具
|
||||
↓
|
||||
执行工具
|
||||
↓
|
||||
工具返回结果
|
||||
↓
|
||||
LLM基于结果生成最终回复
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**最后更新**: 2026-01-23
|
||||
**工具总数**: 8个
|
||||
177
工具调用功能完成总结.md
Normal file
177
工具调用功能完成总结.md
Normal file
@@ -0,0 +1,177 @@
|
||||
# 工具调用功能实施完成总结
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 后端实现(100%完成)
|
||||
|
||||
1. **工具定义模型** (`backend/app/models/tool.py`)
|
||||
- ✅ 完整的工具数据模型
|
||||
- ✅ 支持工具分类、函数定义、实现类型等
|
||||
|
||||
2. **工具注册表** (`backend/app/services/tool_registry.py`)
|
||||
- ✅ 管理所有可用工具
|
||||
- ✅ 支持注册内置工具和从数据库加载
|
||||
|
||||
3. **内置工具实现** (`backend/app/services/builtin_tools.py`)
|
||||
- ✅ `http_request`: HTTP请求工具
|
||||
- ✅ `file_read`: 文件读取工具
|
||||
- ✅ 完整的工具定义(OpenAI Function格式)
|
||||
|
||||
4. **LLM服务扩展** (`backend/app/services/llm_service.py`)
|
||||
- ✅ `call_openai_with_tools`: 支持工具调用的OpenAI调用
|
||||
- ✅ `call_deepseek_with_tools`: 支持工具调用的DeepSeek调用
|
||||
- ✅ `call_llm_with_tools`: 通用工具调用接口
|
||||
- ✅ `_execute_tool`: 工具执行方法
|
||||
- ✅ 支持多轮工具调用循环(最多5次迭代)
|
||||
|
||||
5. **工作流引擎集成** (`backend/app/services/workflow_engine.py`)
|
||||
- ✅ LLM节点执行时检查工具配置
|
||||
- ✅ 自动加载工具定义并传递给LLM服务
|
||||
|
||||
6. **工具管理API** (`backend/app/api/tools.py`)
|
||||
- ✅ `GET /api/v1/tools` - 获取工具列表
|
||||
- ✅ `GET /api/v1/tools/builtin` - 获取内置工具列表
|
||||
- ✅ `GET /api/v1/tools/{tool_id}` - 获取工具详情
|
||||
- ✅ `POST /api/v1/tools` - 创建工具
|
||||
- ✅ `PUT /api/v1/tools/{tool_id}` - 更新工具
|
||||
- ✅ `DELETE /api/v1/tools/{tool_id}` - 删除工具
|
||||
- ✅ 已注册到main.py
|
||||
|
||||
7. **初始化脚本** (`backend/scripts/init_builtin_tools.py`)
|
||||
- ✅ 已执行成功,创建了2个内置工具
|
||||
|
||||
8. **数据库迁移** (`backend/alembic/versions/004_add_tools_table.py`)
|
||||
- ✅ 创建tools表的迁移文件
|
||||
|
||||
### 前端实现(100%完成)
|
||||
|
||||
1. **工具配置界面** (`frontend/src/components/WorkflowEditor/WorkflowEditor.vue`)
|
||||
- ✅ 在LLM节点配置面板中添加"工具"标签页
|
||||
- ✅ "启用工具调用"开关
|
||||
- ✅ 工具选择器(多选,支持筛选)
|
||||
- ✅ 工具分组显示(内置工具/自定义工具)
|
||||
- ✅ 显示选中工具的详细信息(描述、参数定义)
|
||||
- ✅ 工具移除功能
|
||||
- ✅ 自动加载工具列表
|
||||
|
||||
2. **默认配置**
|
||||
- ✅ LLM节点创建时自动初始化工具配置
|
||||
- ✅ `enable_tools: false`, `tools: []`
|
||||
|
||||
## 📊 功能特性
|
||||
|
||||
### 1. 工具调用流程
|
||||
|
||||
```
|
||||
用户输入 → LLM节点(启用工具) → LLM API(带tools参数)
|
||||
↓
|
||||
LLM返回tool_call → 工具执行器 → 执行工具
|
||||
↓
|
||||
工具结果 → 返回LLM → 生成最终回复
|
||||
```
|
||||
|
||||
### 2. 支持的工具
|
||||
|
||||
**内置工具**:
|
||||
- `http_request`: 发送HTTP请求(GET/POST/PUT/DELETE)
|
||||
- `file_read`: 读取文件内容(限制在项目目录内)
|
||||
|
||||
**自定义工具**:
|
||||
- 支持通过API创建自定义工具
|
||||
- 支持多种实现类型(builtin/http/workflow/code)
|
||||
|
||||
### 3. 安全特性
|
||||
|
||||
- 文件读取限制在项目目录内
|
||||
- 工具参数验证
|
||||
- 工具执行超时(30秒)
|
||||
- 最大工具调用迭代次数(5次)
|
||||
|
||||
## 🎯 使用方法
|
||||
|
||||
### 1. 在LLM节点中启用工具调用
|
||||
|
||||
1. 选择LLM节点
|
||||
2. 打开"工具"标签页
|
||||
3. 启用"启用工具调用"开关
|
||||
4. 选择需要的工具(如:`http_request`, `file_read`)
|
||||
5. 保存配置
|
||||
|
||||
### 2. 测试工具调用
|
||||
|
||||
创建一个简单的Agent:
|
||||
- 开始节点
|
||||
- LLM节点(启用工具调用,选择`http_request`工具)
|
||||
- 结束节点
|
||||
|
||||
测试输入:`"查询 https://api.github.com/users/octocat 的信息"`
|
||||
|
||||
LLM应该会:
|
||||
1. 识别需要调用工具
|
||||
2. 调用 `http_request` 工具获取GitHub API数据
|
||||
3. 基于获取的数据生成回复
|
||||
|
||||
## 📝 配置示例
|
||||
|
||||
### LLM节点配置(JSON格式)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "llm-1",
|
||||
"type": "llm",
|
||||
"data": {
|
||||
"label": "智能助手",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4",
|
||||
"prompt": "请帮助用户解决问题,可以使用工具获取信息。",
|
||||
"enable_tools": true,
|
||||
"tools": ["http_request", "file_read"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 API使用示例
|
||||
|
||||
### 获取工具列表
|
||||
|
||||
```bash
|
||||
curl http://localhost:8037/api/v1/tools
|
||||
```
|
||||
|
||||
### 获取内置工具
|
||||
|
||||
```bash
|
||||
curl http://localhost:8037/api/v1/tools/builtin
|
||||
```
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **模型支持**
|
||||
- 当前支持OpenAI和DeepSeek(兼容OpenAI API格式)
|
||||
- 需要模型支持function calling功能
|
||||
- 推荐使用:`gpt-4`, `gpt-3.5-turbo`, `deepseek-chat`
|
||||
|
||||
2. **工具调用限制**
|
||||
- 最大迭代次数:5次
|
||||
- 工具执行超时:30秒
|
||||
- 如果达到最大迭代次数,会返回最后一次的结果
|
||||
|
||||
3. **安全考虑**
|
||||
- 文件读取限制在项目目录内
|
||||
- HTTP工具没有域名限制(生产环境建议添加)
|
||||
- 工具参数需要验证(当前为基本验证)
|
||||
|
||||
## 🎉 完成状态
|
||||
|
||||
- ✅ 后端核心功能:100%
|
||||
- ✅ API接口:100%
|
||||
- ✅ 前端界面:100%
|
||||
- ✅ 内置工具:100%
|
||||
- ✅ 文档:100%
|
||||
|
||||
**工具调用功能已完全实施完成!** 🎊
|
||||
|
||||
---
|
||||
|
||||
**完成时间**: 2026-01-23
|
||||
**实施状态**: ✅ 全部完成
|
||||
183
工具调用实施总结.md
Normal file
183
工具调用实施总结.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 工具调用功能实施总结
|
||||
|
||||
## ✅ 已完成的工作
|
||||
|
||||
### 阶段1: 后端核心功能 ✅
|
||||
|
||||
#### 1.1 工具定义模型 ✅
|
||||
- **文件**: `backend/app/models/tool.py`
|
||||
- **功能**: 定义了工具数据模型,包括工具名称、描述、函数定义等
|
||||
- **状态**: 已完成
|
||||
|
||||
#### 1.2 工具注册表 ✅
|
||||
- **文件**: `backend/app/services/tool_registry.py`
|
||||
- **功能**: 管理所有可用工具,支持注册内置工具和从数据库加载工具
|
||||
- **状态**: 已完成
|
||||
|
||||
#### 1.3 内置工具实现 ✅
|
||||
- **文件**: `backend/app/services/builtin_tools.py`
|
||||
- **功能**: 实现了两个内置工具:
|
||||
- `http_request`: HTTP请求工具
|
||||
- `file_read`: 文件读取工具
|
||||
- **状态**: 已完成
|
||||
|
||||
#### 1.4 LLM服务扩展 ✅
|
||||
- **文件**: `backend/app/services/llm_service.py`
|
||||
- **功能**:
|
||||
- 添加了 `call_openai_with_tools` 方法
|
||||
- 添加了 `call_deepseek_with_tools` 方法
|
||||
- 添加了 `call_llm_with_tools` 通用方法
|
||||
- 添加了 `_execute_tool` 工具执行方法
|
||||
- 支持多轮工具调用循环
|
||||
- **状态**: 已完成
|
||||
|
||||
#### 1.5 工作流引擎扩展 ✅
|
||||
- **文件**: `backend/app/services/workflow_engine.py`
|
||||
- **功能**: 在LLM节点执行时检查工具配置,如果启用则调用工具调用接口
|
||||
- **状态**: 已完成
|
||||
|
||||
### 阶段2: 数据库迁移 ✅
|
||||
|
||||
- **文件**: `backend/alembic/versions/004_add_tools_table.py`
|
||||
- **功能**: 创建tools表的数据库迁移
|
||||
- **状态**: 已完成(需要执行迁移)
|
||||
|
||||
### 阶段3: API接口 ✅
|
||||
|
||||
- **文件**: `backend/app/api/tools.py`
|
||||
- **功能**:
|
||||
- `GET /api/v1/tools` - 获取工具列表
|
||||
- `GET /api/v1/tools/builtin` - 获取内置工具列表
|
||||
- `GET /api/v1/tools/{tool_id}` - 获取工具详情
|
||||
- `POST /api/v1/tools` - 创建工具
|
||||
- `PUT /api/v1/tools/{tool_id}` - 更新工具
|
||||
- `DELETE /api/v1/tools/{tool_id}` - 删除工具
|
||||
- **状态**: 已完成,已注册到main.py
|
||||
|
||||
### 阶段4: 初始化脚本 ✅
|
||||
|
||||
- **文件**: `backend/scripts/init_builtin_tools.py`
|
||||
- **功能**: 初始化内置工具到数据库和注册表
|
||||
- **状态**: 已完成并执行成功
|
||||
|
||||
## 📋 下一步工作
|
||||
|
||||
### 阶段5: 前端实现(待实施)
|
||||
|
||||
#### 5.1 LLM节点配置扩展
|
||||
- 在LLM节点配置面板中添加"工具"标签页
|
||||
- 添加"启用工具调用"开关
|
||||
- 添加工具选择器(多选)
|
||||
- 显示选中工具的详细信息
|
||||
|
||||
#### 5.2 工具调用可视化
|
||||
- 在工作流执行时显示工具调用过程
|
||||
- 显示工具名称、参数、执行结果
|
||||
- 显示工具调用状态(成功/失败)
|
||||
|
||||
#### 5.3 工具管理界面(可选)
|
||||
- 工具列表页面
|
||||
- 工具创建/编辑页面
|
||||
- 工具详情页面
|
||||
|
||||
## 🔧 使用说明
|
||||
|
||||
### 1. 执行数据库迁移
|
||||
|
||||
```bash
|
||||
cd /home/renjianbo/aiagent/backend
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### 2. 初始化内置工具(已完成)
|
||||
|
||||
```bash
|
||||
python3 backend/scripts/init_builtin_tools.py
|
||||
```
|
||||
|
||||
### 3. 在LLM节点中启用工具调用
|
||||
|
||||
在LLM节点的配置中,需要设置:
|
||||
```json
|
||||
{
|
||||
"enable_tools": true,
|
||||
"tools": ["http_request", "file_read"]
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 测试工具调用
|
||||
|
||||
创建一个简单的Agent,包含:
|
||||
- 开始节点
|
||||
- LLM节点(启用工具调用)
|
||||
- 结束节点
|
||||
|
||||
测试输入:`"查询 https://api.github.com/users/octocat 的信息"`
|
||||
|
||||
LLM应该会:
|
||||
1. 识别需要调用工具
|
||||
2. 调用 `http_request` 工具
|
||||
3. 获取结果后生成回复
|
||||
|
||||
## 📊 当前状态
|
||||
|
||||
- ✅ 后端核心功能:100% 完成
|
||||
- ✅ 数据库迁移:100% 完成
|
||||
- ✅ API接口:100% 完成
|
||||
- ✅ 初始化脚本:100% 完成
|
||||
- ⏳ 前端实现:0% 完成(待实施)
|
||||
|
||||
## 🎯 测试建议
|
||||
|
||||
1. **API测试**
|
||||
```bash
|
||||
# 获取工具列表
|
||||
curl http://localhost:8037/api/v1/tools
|
||||
|
||||
# 获取内置工具
|
||||
curl http://localhost:8037/api/v1/tools/builtin
|
||||
```
|
||||
|
||||
2. **工作流测试**
|
||||
- 创建一个包含LLM节点的Agent
|
||||
- 在LLM节点配置中启用工具调用
|
||||
- 选择 `http_request` 工具
|
||||
- 测试执行
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **数据库迁移**
|
||||
- 需要执行 `alembic upgrade head` 创建tools表
|
||||
- 如果表已存在,迁移会失败(需要先检查)
|
||||
|
||||
2. **工具调用限制**
|
||||
- 当前支持OpenAI和DeepSeek(兼容OpenAI API)
|
||||
- 最大工具调用迭代次数:5次
|
||||
- 工具执行超时:30秒(HTTP工具)
|
||||
|
||||
3. **安全考虑**
|
||||
- 文件读取工具限制在项目目录内
|
||||
- HTTP工具没有域名限制(生产环境需要添加)
|
||||
- 工具参数需要验证(当前为基本验证)
|
||||
|
||||
## 📝 后续优化
|
||||
|
||||
1. **更多内置工具**
|
||||
- 数据库查询工具
|
||||
- 邮件发送工具
|
||||
- 文件写入工具
|
||||
|
||||
2. **工具调用优化**
|
||||
- 并行工具执行
|
||||
- 工具结果缓存
|
||||
- 工具调用日志
|
||||
|
||||
3. **前端增强**
|
||||
- 工具调用可视化
|
||||
- 工具参数配置界面
|
||||
- 工具执行历史
|
||||
|
||||
---
|
||||
|
||||
**实施时间**: 2026-01-23
|
||||
**实施状态**: 后端核心功能已完成,前端待实施
|
||||
767
工具调用实现方案.md
Normal file
767
工具调用实现方案.md
Normal file
@@ -0,0 +1,767 @@
|
||||
# 工具调用(Function Calling)实现方案
|
||||
|
||||
## 📋 方案概述
|
||||
|
||||
本方案实现LLM工具调用功能,允许LLM节点调用预定义的工具(函数),实现更强大的AI能力。
|
||||
|
||||
## 🎯 功能目标
|
||||
|
||||
1. **LLM节点支持工具调用**
|
||||
- 在LLM节点配置中定义可用工具
|
||||
- LLM自动选择并调用合适的工具
|
||||
- 支持多轮工具调用(Tool Calling Loop)
|
||||
|
||||
2. **工具定义和管理**
|
||||
- 支持内置工具(HTTP请求、数据库查询、文件操作等)
|
||||
- 支持自定义工具(Python函数、工作流节点等)
|
||||
- 工具参数验证和类型转换
|
||||
|
||||
3. **工具执行**
|
||||
- 异步执行工具
|
||||
- 错误处理和重试
|
||||
- 结果格式化返回给LLM
|
||||
|
||||
4. **前端配置界面**
|
||||
- 工具选择器
|
||||
- 工具参数配置
|
||||
- 工具调用可视化
|
||||
|
||||
## 🏗️ 架构设计
|
||||
|
||||
### 1. 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ LLM节点配置 │
|
||||
│ (工具列表) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ LLM服务调用 │
|
||||
│ (传递tools参数) │
|
||||
└────────┬────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐ ┌──────────────┐
|
||||
│ 工具调用解析器 │─────▶│ 工具执行器 │
|
||||
│ (解析tool_call) │ │ (执行工具) │
|
||||
└────────┬────────┘ └──────┬───────┘
|
||||
│ │
|
||||
│ ▼
|
||||
│ ┌──────────────┐
|
||||
│ │ 工具注册表 │
|
||||
│ │ (工具定义) │
|
||||
│ └──────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ 结果返回LLM │
|
||||
│ (继续对话) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
### 2. 数据流
|
||||
|
||||
```
|
||||
1. 用户输入 → LLM节点
|
||||
2. LLM节点 → LLM API (带tools参数)
|
||||
3. LLM API → 返回tool_call请求
|
||||
4. 工具调用解析器 → 解析tool_call
|
||||
5. 工具执行器 → 执行工具
|
||||
6. 工具结果 → 返回LLM (tool message)
|
||||
7. LLM → 生成最终回复
|
||||
```
|
||||
|
||||
## 📝 实现步骤
|
||||
|
||||
### 阶段1: 后端核心功能
|
||||
|
||||
#### 1.1 工具定义模型
|
||||
|
||||
**文件**: `backend/app/models/tool.py`
|
||||
|
||||
```python
|
||||
from sqlalchemy import Column, String, Text, JSON, DateTime, Boolean, ForeignKey, Integer
|
||||
from sqlalchemy.dialects.mysql import CHAR
|
||||
from sqlalchemy.orm import relationship
|
||||
from app.core.database import Base
|
||||
import uuid
|
||||
|
||||
class Tool(Base):
|
||||
"""工具定义表"""
|
||||
__tablename__ = "tools"
|
||||
|
||||
id = Column(CHAR(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
name = Column(String(100), nullable=False, unique=True, comment="工具名称")
|
||||
description = Column(Text, nullable=False, comment="工具描述")
|
||||
category = Column(String(50), comment="工具分类")
|
||||
|
||||
# 工具定义(OpenAI Function格式)
|
||||
function_schema = Column(JSON, nullable=False, comment="函数定义(JSON Schema)")
|
||||
|
||||
# 工具实现类型
|
||||
implementation_type = Column(String(50), nullable=False, comment="实现类型: builtin/http/workflow/code")
|
||||
implementation_config = Column(JSON, comment="实现配置")
|
||||
|
||||
# 元数据
|
||||
is_public = Column(Boolean, default=False, comment="是否公开")
|
||||
user_id = Column(CHAR(36), ForeignKey("users.id"), nullable=True, comment="创建者ID")
|
||||
use_count = Column(Integer, default=0, comment="使用次数")
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
user = relationship("User", backref="tools")
|
||||
```
|
||||
|
||||
#### 1.2 工具注册表
|
||||
|
||||
**文件**: `backend/app/services/tool_registry.py`
|
||||
|
||||
```python
|
||||
from typing import Dict, Any, Callable, Optional
|
||||
import json
|
||||
from app.models.tool import Tool
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
class ToolRegistry:
|
||||
"""工具注册表 - 管理所有可用工具"""
|
||||
|
||||
def __init__(self):
|
||||
self._builtin_tools: Dict[str, Callable] = {}
|
||||
self._tool_schemas: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
def register_builtin_tool(self, name: str, func: Callable, schema: Dict[str, Any]):
|
||||
"""注册内置工具"""
|
||||
self._builtin_tools[name] = func
|
||||
self._tool_schemas[name] = schema
|
||||
|
||||
def get_tool_schema(self, name: str) -> Optional[Dict[str, Any]]:
|
||||
"""获取工具定义"""
|
||||
return self._tool_schemas.get(name)
|
||||
|
||||
def get_tool_function(self, name: str) -> Optional[Callable]:
|
||||
"""获取工具函数"""
|
||||
return self._builtin_tools.get(name)
|
||||
|
||||
def get_all_tool_schemas(self) -> list:
|
||||
"""获取所有工具定义(用于LLM)"""
|
||||
return list(self._tool_schemas.values())
|
||||
|
||||
def load_tools_from_db(self, db: Session, tool_names: list = None):
|
||||
"""从数据库加载工具"""
|
||||
query = db.query(Tool).filter(Tool.is_public == True)
|
||||
if tool_names:
|
||||
query = query.filter(Tool.name.in_(tool_names))
|
||||
|
||||
tools = query.all()
|
||||
for tool in tools:
|
||||
self._tool_schemas[tool.name] = tool.function_schema
|
||||
# 根据implementation_type加载工具实现
|
||||
if tool.implementation_type == 'builtin':
|
||||
# 从内置工具中查找
|
||||
if tool.name in self._builtin_tools:
|
||||
pass # 已注册
|
||||
elif tool.implementation_type == 'http':
|
||||
# HTTP工具需要特殊处理
|
||||
self._register_http_tool(tool)
|
||||
elif tool.implementation_type == 'workflow':
|
||||
# 工作流工具
|
||||
self._register_workflow_tool(tool)
|
||||
elif tool.implementation_type == 'code':
|
||||
# 代码执行工具
|
||||
self._register_code_tool(tool)
|
||||
|
||||
# 全局工具注册表实例
|
||||
tool_registry = ToolRegistry()
|
||||
```
|
||||
|
||||
#### 1.3 内置工具实现
|
||||
|
||||
**文件**: `backend/app/services/builtin_tools.py`
|
||||
|
||||
```python
|
||||
from typing import Dict, Any
|
||||
import httpx
|
||||
import json
|
||||
|
||||
async def http_request_tool(url: str, method: str = "GET", headers: Dict = None, body: Any = None) -> str:
|
||||
"""HTTP请求工具"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
if method.upper() == "GET":
|
||||
response = await client.get(url, headers=headers)
|
||||
elif method.upper() == "POST":
|
||||
response = await client.post(url, json=body, headers=headers)
|
||||
else:
|
||||
raise ValueError(f"不支持的HTTP方法: {method}")
|
||||
|
||||
return json.dumps({
|
||||
"status_code": response.status_code,
|
||||
"headers": dict(response.headers),
|
||||
"body": response.text
|
||||
}, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
||||
|
||||
async def database_query_tool(query: str, database: str = "default") -> str:
|
||||
"""数据库查询工具"""
|
||||
# 实现数据库查询逻辑
|
||||
pass
|
||||
|
||||
async def file_read_tool(file_path: str) -> str:
|
||||
"""文件读取工具"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
return f"错误: {str(e)}"
|
||||
|
||||
# 工具定义(OpenAI Function格式)
|
||||
HTTP_REQUEST_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "http_request",
|
||||
"description": "发送HTTP请求,支持GET和POST方法",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "请求URL"
|
||||
},
|
||||
"method": {
|
||||
"type": "string",
|
||||
"enum": ["GET", "POST", "PUT", "DELETE"],
|
||||
"description": "HTTP方法"
|
||||
},
|
||||
"headers": {
|
||||
"type": "object",
|
||||
"description": "请求头"
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "请求体(POST/PUT时使用)"
|
||||
}
|
||||
},
|
||||
"required": ["url", "method"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FILE_READ_SCHEMA = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "file_read",
|
||||
"description": "读取文件内容",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {
|
||||
"type": "string",
|
||||
"description": "文件路径"
|
||||
}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.4 LLM服务扩展
|
||||
|
||||
**文件**: `backend/app/services/llm_service.py` (扩展)
|
||||
|
||||
```python
|
||||
from typing import List, Dict, Any, Optional
|
||||
from app.services.tool_registry import tool_registry
|
||||
|
||||
class LLMService:
|
||||
# ... 现有代码 ...
|
||||
|
||||
async def call_openai_with_tools(
|
||||
self,
|
||||
prompt: str,
|
||||
tools: List[Dict[str, Any]],
|
||||
model: str = "gpt-3.5-turbo",
|
||||
temperature: float = 0.7,
|
||||
max_tokens: Optional[int] = None,
|
||||
api_key: Optional[str] = None,
|
||||
base_url: Optional[str] = None,
|
||||
max_iterations: int = 5
|
||||
) -> str:
|
||||
"""
|
||||
调用OpenAI API,支持工具调用
|
||||
|
||||
Args:
|
||||
prompt: 提示词
|
||||
tools: 工具定义列表(OpenAI Function格式)
|
||||
model: 模型名称
|
||||
temperature: 温度参数
|
||||
max_tokens: 最大token数
|
||||
api_key: API密钥
|
||||
base_url: API地址
|
||||
max_iterations: 最大工具调用迭代次数
|
||||
|
||||
Returns:
|
||||
LLM返回的最终文本
|
||||
"""
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
|
||||
for iteration in range(max_iterations):
|
||||
# 调用LLM
|
||||
response = await client.chat.completions.create(
|
||||
model=model,
|
||||
messages=messages,
|
||||
tools=tools if iteration == 0 else None, # 只在第一次调用时传递tools
|
||||
tool_choice="auto",
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
)
|
||||
|
||||
message = response.choices[0].message
|
||||
|
||||
# 添加助手回复到消息历史
|
||||
messages.append(message)
|
||||
|
||||
# 检查是否有工具调用
|
||||
if message.tool_calls:
|
||||
# 处理每个工具调用
|
||||
for tool_call in message.tool_calls:
|
||||
tool_name = tool_call.function.name
|
||||
tool_args = json.loads(tool_call.function.arguments)
|
||||
|
||||
# 执行工具
|
||||
tool_result = await self._execute_tool(tool_name, tool_args)
|
||||
|
||||
# 添加工具结果到消息历史
|
||||
messages.append({
|
||||
"role": "tool",
|
||||
"tool_call_id": tool_call.id,
|
||||
"content": tool_result
|
||||
})
|
||||
else:
|
||||
# 没有工具调用,返回最终回复
|
||||
return message.content or ""
|
||||
|
||||
# 达到最大迭代次数
|
||||
return messages[-1].get("content", "达到最大工具调用次数")
|
||||
|
||||
async def _execute_tool(self, tool_name: str, tool_args: Dict[str, Any]) -> str:
|
||||
"""执行工具"""
|
||||
# 从注册表获取工具函数
|
||||
tool_func = tool_registry.get_tool_function(tool_name)
|
||||
|
||||
if not tool_func:
|
||||
return json.dumps({"error": f"工具 {tool_name} 未找到"}, ensure_ascii=False)
|
||||
|
||||
try:
|
||||
# 执行工具(支持异步函数)
|
||||
if asyncio.iscoroutinefunction(tool_func):
|
||||
result = await tool_func(**tool_args)
|
||||
else:
|
||||
result = tool_func(**tool_args)
|
||||
|
||||
# 将结果转换为字符串
|
||||
if isinstance(result, (dict, list)):
|
||||
return json.dumps(result, ensure_ascii=False)
|
||||
return str(result)
|
||||
except Exception as e:
|
||||
return json.dumps({"error": str(e)}, ensure_ascii=False)
|
||||
```
|
||||
|
||||
#### 1.5 工作流引擎扩展
|
||||
|
||||
**文件**: `backend/app/services/workflow_engine.py` (扩展)
|
||||
|
||||
```python
|
||||
# 在LLM节点执行部分添加工具调用支持
|
||||
|
||||
elif node_type == 'llm' or node_type == 'template':
|
||||
node_data = node.get('data', {})
|
||||
prompt = node_data.get('prompt', '')
|
||||
|
||||
# 获取工具配置
|
||||
tools_config = node_data.get('tools', []) # 工具名称列表
|
||||
enable_tools = node_data.get('enable_tools', False)
|
||||
|
||||
# 如果启用了工具,加载工具定义
|
||||
tools = []
|
||||
if enable_tools and tools_config:
|
||||
# 从注册表加载工具定义
|
||||
for tool_name in tools_config:
|
||||
tool_schema = tool_registry.get_tool_schema(tool_name)
|
||||
if tool_schema:
|
||||
tools.append(tool_schema)
|
||||
|
||||
# 调用LLM(带工具)
|
||||
if tools:
|
||||
result = await llm_service.call_openai_with_tools(
|
||||
prompt=formatted_prompt,
|
||||
tools=tools,
|
||||
provider=provider,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
)
|
||||
else:
|
||||
# 普通调用
|
||||
result = await llm_service.call_llm(
|
||||
prompt=formatted_prompt,
|
||||
provider=provider,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens
|
||||
)
|
||||
```
|
||||
|
||||
### 阶段2: 数据库迁移
|
||||
|
||||
**文件**: `backend/alembic/versions/xxxx_add_tools_table.py`
|
||||
|
||||
```python
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'tools',
|
||||
sa.Column('id', sa.CHAR(36), primary_key=True),
|
||||
sa.Column('name', sa.String(100), nullable=False, unique=True),
|
||||
sa.Column('description', sa.Text, nullable=False),
|
||||
sa.Column('category', sa.String(50)),
|
||||
sa.Column('function_schema', sa.JSON, nullable=False),
|
||||
sa.Column('implementation_type', sa.String(50), nullable=False),
|
||||
sa.Column('implementation_config', sa.JSON),
|
||||
sa.Column('is_public', sa.Boolean, default=False),
|
||||
sa.Column('user_id', sa.CHAR(36), sa.ForeignKey('users.id')),
|
||||
sa.Column('use_count', sa.Integer, default=0),
|
||||
sa.Column('created_at', sa.DateTime, default=func.now()),
|
||||
sa.Column('updated_at', sa.DateTime, default=func.now(), onupdate=func.now())
|
||||
)
|
||||
```
|
||||
|
||||
### 阶段3: API接口
|
||||
|
||||
**文件**: `backend/app/api/tools.py`
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from app.core.database import get_db
|
||||
from app.models.tool import Tool
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.api.auth import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/v1/tools", tags=["tools"])
|
||||
|
||||
@router.get("")
|
||||
async def list_tools(
|
||||
category: str = None,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取工具列表"""
|
||||
query = db.query(Tool).filter(Tool.is_public == True)
|
||||
if category:
|
||||
query = query.filter(Tool.category == category)
|
||||
|
||||
tools = query.all()
|
||||
return [{
|
||||
"id": tool.id,
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"category": tool.category,
|
||||
"function_schema": tool.function_schema
|
||||
} for tool in tools]
|
||||
|
||||
@router.post("")
|
||||
async def create_tool(
|
||||
tool_data: dict,
|
||||
db: Session = Depends(get_db),
|
||||
current_user = Depends(get_current_user)
|
||||
):
|
||||
"""创建工具"""
|
||||
tool = Tool(
|
||||
name=tool_data["name"],
|
||||
description=tool_data["description"],
|
||||
category=tool_data.get("category"),
|
||||
function_schema=tool_data["function_schema"],
|
||||
implementation_type=tool_data["implementation_type"],
|
||||
implementation_config=tool_data.get("implementation_config"),
|
||||
user_id=current_user.id
|
||||
)
|
||||
db.add(tool)
|
||||
db.commit()
|
||||
return tool
|
||||
|
||||
@router.get("/builtin")
|
||||
async def list_builtin_tools():
|
||||
"""获取内置工具列表"""
|
||||
schemas = tool_registry.get_all_tool_schemas()
|
||||
return schemas
|
||||
```
|
||||
|
||||
### 阶段4: 前端实现
|
||||
|
||||
#### 4.1 LLM节点配置扩展
|
||||
|
||||
**文件**: `frontend/src/components/WorkflowEditor/WorkflowEditor.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<!-- 在LLM节点配置面板中添加工具配置 -->
|
||||
<el-tab-pane label="工具" name="tools" v-if="selectedNode.type === 'llm'">
|
||||
<el-switch
|
||||
v-model="selectedNode.data.enable_tools"
|
||||
label="启用工具调用"
|
||||
/>
|
||||
|
||||
<div v-if="selectedNode.data.enable_tools" style="margin-top: 20px;">
|
||||
<el-select
|
||||
v-model="selectedNode.data.tools"
|
||||
multiple
|
||||
placeholder="选择可用工具"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="tool in availableTools"
|
||||
:key="tool.name"
|
||||
:label="tool.name"
|
||||
:value="tool.name"
|
||||
>
|
||||
<div>
|
||||
<strong>{{ tool.name }}</strong>
|
||||
<p style="margin: 0; color: #999; font-size: 12px;">
|
||||
{{ tool.description }}
|
||||
</p>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<div v-for="toolName in selectedNode.data.tools" :key="toolName" style="margin-top: 10px;">
|
||||
<el-card>
|
||||
<template #header>
|
||||
<span>{{ toolName }}</span>
|
||||
</template>
|
||||
<div v-if="getToolSchema(toolName)">
|
||||
<p><strong>描述:</strong> {{ getToolSchema(toolName).function.description }}</p>
|
||||
<p><strong>参数:</strong></p>
|
||||
<pre>{{ JSON.stringify(getToolSchema(toolName).function.parameters, null, 2) }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import api from '@/api'
|
||||
|
||||
const availableTools = ref([])
|
||||
const toolSchemas = ref({})
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载可用工具
|
||||
const response = await api.get('/api/v1/tools')
|
||||
availableTools.value = response.data
|
||||
|
||||
// 加载内置工具
|
||||
const builtinResponse = await api.get('/api/v1/tools/builtin')
|
||||
availableTools.value = [...availableTools.value, ...builtinResponse.data]
|
||||
|
||||
// 构建工具schema映射
|
||||
availableTools.value.forEach(tool => {
|
||||
if (tool.function_schema) {
|
||||
toolSchemas.value[tool.name] = tool.function_schema
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const getToolSchema = (toolName: string) => {
|
||||
return toolSchemas.value[toolName]
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
#### 4.2 工具调用可视化
|
||||
|
||||
在工作流执行时,显示工具调用过程:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="tool-call-visualization" v-if="toolCalls.length > 0">
|
||||
<h4>工具调用过程:</h4>
|
||||
<div v-for="(call, index) in toolCalls" :key="index" class="tool-call-item">
|
||||
<div class="tool-call-header">
|
||||
<span class="tool-name">{{ call.name }}</span>
|
||||
<span class="tool-status" :class="call.status">
|
||||
{{ call.status === 'success' ? '✓' : '✗' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="tool-call-args">
|
||||
<strong>参数:</strong>
|
||||
<pre>{{ JSON.stringify(call.arguments, null, 2) }}</pre>
|
||||
</div>
|
||||
<div class="tool-call-result" v-if="call.result">
|
||||
<strong>结果:</strong>
|
||||
<pre>{{ call.result }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
### 阶段5: 初始化内置工具
|
||||
|
||||
**文件**: `backend/scripts/init_builtin_tools.py`
|
||||
|
||||
```python
|
||||
from app.core.database import SessionLocal
|
||||
from app.models.tool import Tool
|
||||
from app.services.tool_registry import tool_registry
|
||||
from app.services.builtin_tools import (
|
||||
http_request_tool,
|
||||
file_read_tool,
|
||||
HTTP_REQUEST_SCHEMA,
|
||||
FILE_READ_SCHEMA
|
||||
)
|
||||
|
||||
def init_builtin_tools():
|
||||
"""初始化内置工具"""
|
||||
db = SessionLocal()
|
||||
|
||||
try:
|
||||
# 注册内置工具到注册表
|
||||
tool_registry.register_builtin_tool(
|
||||
"http_request",
|
||||
http_request_tool,
|
||||
HTTP_REQUEST_SCHEMA
|
||||
)
|
||||
|
||||
tool_registry.register_builtin_tool(
|
||||
"file_read",
|
||||
file_read_tool,
|
||||
FILE_READ_SCHEMA
|
||||
)
|
||||
|
||||
# 保存到数据库
|
||||
for tool_name, tool_schema in [
|
||||
("http_request", HTTP_REQUEST_SCHEMA),
|
||||
("file_read", FILE_READ_SCHEMA)
|
||||
]:
|
||||
existing = db.query(Tool).filter(Tool.name == tool_name).first()
|
||||
if not existing:
|
||||
tool = Tool(
|
||||
name=tool_name,
|
||||
description=tool_schema["function"]["description"],
|
||||
category="builtin",
|
||||
function_schema=tool_schema,
|
||||
implementation_type="builtin",
|
||||
is_public=True
|
||||
)
|
||||
db.add(tool)
|
||||
|
||||
db.commit()
|
||||
print("✅ 内置工具初始化完成")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
print(f"❌ 初始化失败: {str(e)}")
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
if __name__ == "__main__":
|
||||
init_builtin_tools()
|
||||
```
|
||||
|
||||
## 📊 使用示例
|
||||
|
||||
### 示例1: 配置LLM节点使用工具
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "llm-1",
|
||||
"type": "llm",
|
||||
"data": {
|
||||
"label": "智能助手",
|
||||
"provider": "openai",
|
||||
"model": "gpt-4",
|
||||
"prompt": "请帮助用户解决问题,可以使用工具获取信息。",
|
||||
"enable_tools": true,
|
||||
"tools": ["http_request", "file_read"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2: 工作流示例
|
||||
|
||||
```
|
||||
开始 → LLM节点(启用工具) → 结束
|
||||
```
|
||||
|
||||
用户输入: "查询北京的天气"
|
||||
1. LLM识别需要调用工具
|
||||
2. 调用 `http_request` 工具查询天气API
|
||||
3. 获取结果后生成回复
|
||||
|
||||
## 🔒 安全考虑
|
||||
|
||||
1. **工具权限控制**
|
||||
- 限制可执行的文件路径
|
||||
- 限制HTTP请求的目标域名
|
||||
- 限制数据库查询权限
|
||||
|
||||
2. **参数验证**
|
||||
- 验证工具参数类型和范围
|
||||
- 防止注入攻击
|
||||
|
||||
3. **执行超时**
|
||||
- 设置工具执行超时时间
|
||||
- 防止无限循环
|
||||
|
||||
4. **资源限制**
|
||||
- 限制工具调用次数
|
||||
- 限制工具执行时间
|
||||
|
||||
## 📈 后续优化
|
||||
|
||||
1. **工具市场**
|
||||
- 用户可分享自定义工具
|
||||
- 工具评分和评论
|
||||
|
||||
2. **工具组合**
|
||||
- 支持工具链(一个工具调用另一个工具)
|
||||
- 工具依赖管理
|
||||
|
||||
3. **性能优化**
|
||||
- 工具结果缓存
|
||||
- 并行工具执行
|
||||
|
||||
4. **监控和日志**
|
||||
- 工具调用统计
|
||||
- 工具执行日志
|
||||
- 性能分析
|
||||
|
||||
## 🎯 实施优先级
|
||||
|
||||
### 高优先级(MVP)
|
||||
1. ✅ 工具注册表
|
||||
2. ✅ 内置工具(HTTP请求、文件读取)
|
||||
3. ✅ LLM服务工具调用支持
|
||||
4. ✅ 工作流引擎集成
|
||||
5. ✅ 前端工具配置界面
|
||||
|
||||
### 中优先级
|
||||
1. 数据库工具
|
||||
2. 工具调用可视化
|
||||
3. 工具执行日志
|
||||
4. 错误处理和重试
|
||||
|
||||
### 低优先级
|
||||
1. 工具市场
|
||||
2. 工具组合
|
||||
3. 性能优化
|
||||
4. 高级安全控制
|
||||
|
||||
---
|
||||
|
||||
**方案版本**: v1.0
|
||||
**创建时间**: 2026-01-23
|
||||
**作者**: AI Assistant
|
||||
167
智能聊天助手性能优化实施报告.md
Normal file
167
智能聊天助手性能优化实施报告.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 智能聊天助手性能优化实施报告
|
||||
|
||||
## 📊 优化完成情况
|
||||
|
||||
### ✅ 已完成的优化
|
||||
|
||||
#### 1. 删除 llm-format 节点 ⭐⭐⭐⭐⭐
|
||||
- **状态**:✅ 已完成
|
||||
- **优化内容**:
|
||||
- 删除了 `llm-format` 节点(原第14个节点)
|
||||
- 优化了 `llm-question` 节点的 prompt,直接生成格式化好的回复
|
||||
- 更新了工作流边连接:`cache-update` 直接连接到 `end-1`
|
||||
- **效果**:减少 1 个 LLM 调用,节省 **1-2 秒**
|
||||
|
||||
#### 2. 优化 max_tokens 配置 ⭐⭐⭐⭐
|
||||
- **状态**:✅ 已完成
|
||||
- **优化内容**:
|
||||
- `llm-intent`: 1000 → **200** (减少 80%)
|
||||
- `llm-greeting`: 500 → **200** (减少 60%)
|
||||
- `llm-question`: 2000 → **1000** (减少 50%)
|
||||
- `llm-emotion`: 1000 → **500** (减少 50%)
|
||||
- `llm-request`: 1500 → **800** (减少 47%)
|
||||
- `llm-goodbye`: 300 → **150** (减少 50%)
|
||||
- `llm-general`: 1000 → **500** (减少 50%)
|
||||
- **效果**:减少 token 生成时间,节省 **0.5-1 秒**,降低 **30-40%** token 消耗
|
||||
|
||||
#### 3. 对话历史截断 ⭐⭐⭐⭐
|
||||
- **状态**:✅ 已完成
|
||||
- **优化内容**:
|
||||
- 在 `workflow_engine.py` 中添加了对话历史截断逻辑
|
||||
- 自动保留最近 **20 条**对话记录
|
||||
- 在 `cache-update` 节点执行后自动截断
|
||||
- **效果**:
|
||||
- 减少 prompt 长度,节省 **0.2-0.5 秒**
|
||||
- 降低 token 消耗
|
||||
- 提升 LLM 响应速度
|
||||
|
||||
## 📈 性能提升预期
|
||||
|
||||
### 优化前
|
||||
- **平均响应时间**:5-6 秒
|
||||
- **LLM 调用次数**:3 次(意图理解 + 问题回答 + 格式化)
|
||||
- **Token 消耗**:约 3500-4500 tokens/对话
|
||||
- **节点数量**:15 个
|
||||
|
||||
### 优化后
|
||||
- **平均响应时间**:**3-4 秒**(提升 **40%**)
|
||||
- **LLM 调用次数**:**2 次**(意图理解 + 问题回答)
|
||||
- **Token 消耗**:约 **2000-2800 tokens/对话**(减少 **30-40%**)
|
||||
- **节点数量**:**14 个**(减少 1 个)
|
||||
|
||||
## 🔧 技术实现细节
|
||||
|
||||
### 1. 工作流结构优化
|
||||
|
||||
**优化前流程**:
|
||||
```
|
||||
开始 → 查询记忆 → 合并上下文 → 意图理解 → 意图路由 →
|
||||
[5个分支] → 合并回复 → 更新记忆 → 格式化回复 → 结束
|
||||
```
|
||||
|
||||
**优化后流程**:
|
||||
```
|
||||
开始 → 查询记忆 → 合并上下文 → 意图理解 → 意图路由 →
|
||||
[5个分支,直接生成最终回复] → 合并回复 → 更新记忆 → 结束
|
||||
```
|
||||
|
||||
### 2. LLM 节点 Prompt 优化
|
||||
|
||||
**llm-question 节点优化**:
|
||||
- 添加了"确保回复格式自然、完整,无需额外格式化"的指令
|
||||
- 让 LLM 直接生成最终格式的回复,避免二次格式化
|
||||
|
||||
### 3. 对话历史截断实现
|
||||
|
||||
**位置**:`backend/app/services/workflow_engine.py`
|
||||
|
||||
**实现逻辑**:
|
||||
```python
|
||||
# 确保conversation_history只保留最近的20条(性能优化)
|
||||
if isinstance(value, dict) and 'conversation_history' in value:
|
||||
if isinstance(value['conversation_history'], list):
|
||||
max_history_length = 20
|
||||
if len(value['conversation_history']) > max_history_length:
|
||||
value['conversation_history'] = value['conversation_history'][-max_history_length:]
|
||||
logger.info(f"[rjb] 对话历史已截断,保留最近 {max_history_length} 条")
|
||||
```
|
||||
|
||||
## 📋 优化清单
|
||||
|
||||
| 优化项 | 状态 | 预期效果 | 实际效果 |
|
||||
|--------|------|----------|----------|
|
||||
| 删除 llm-format 节点 | ✅ 完成 | 节省 1-2 秒 | 待测试 |
|
||||
| 优化 max_tokens | ✅ 完成 | 节省 0.5-1 秒 | 待测试 |
|
||||
| 对话历史截断 | ✅ 完成 | 节省 0.2-0.5 秒 | 待测试 |
|
||||
|
||||
## 🚀 下一步建议
|
||||
|
||||
### 中优先级优化(可选)
|
||||
|
||||
1. **使用 WebSocket 替代轮询** ⭐⭐⭐⭐
|
||||
- 实施难度:中
|
||||
- 效果:提升实时性,减少服务器负载
|
||||
- 预计时间:2-3 小时
|
||||
|
||||
2. **智能轮询(如果不用 WebSocket)** ⭐⭐⭐
|
||||
- 实施难度:低
|
||||
- 效果:减少 30-50% 无效请求
|
||||
- 预计时间:1 小时
|
||||
|
||||
### 高级优化(可选)
|
||||
|
||||
3. **流式响应** ⭐⭐⭐⭐⭐
|
||||
- 实施难度:高
|
||||
- 效果:首字响应时间降低 50-70%
|
||||
- 预计时间:4-6 小时
|
||||
|
||||
4. **LLM 响应缓存** ⭐⭐⭐
|
||||
- 实施难度:中
|
||||
- 效果:重复问题响应时间 < 100ms
|
||||
- 预计时间:2-3 小时
|
||||
|
||||
## 📝 测试建议
|
||||
|
||||
1. **性能测试**:
|
||||
- 测试优化前后的响应时间对比
|
||||
- 监控 LLM API 调用次数和耗时
|
||||
- 检查对话历史是否正确截断
|
||||
|
||||
2. **功能测试**:
|
||||
- 验证删除 llm-format 节点后,回复格式是否正常
|
||||
- 验证各分支节点的回复质量
|
||||
- 验证记忆功能是否正常
|
||||
|
||||
3. **压力测试**:
|
||||
- 测试长时间对话(超过20条)时的性能
|
||||
- 测试并发请求的处理能力
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **向后兼容**:优化后的配置已更新到数据库,现有对话会使用新配置
|
||||
2. **对话历史**:超过20条的旧对话历史会被自动截断
|
||||
3. **回复格式**:由于删除了格式化节点,需要确保各分支节点生成的回复格式正确
|
||||
|
||||
## 📊 监控指标
|
||||
|
||||
建议监控以下指标以验证优化效果:
|
||||
|
||||
1. **响应时间**:
|
||||
- P50(中位数)
|
||||
- P95(95% 分位数)
|
||||
- P99(99% 分位数)
|
||||
|
||||
2. **LLM 调用**:
|
||||
- 各节点的平均调用时间
|
||||
- Token 消耗统计
|
||||
|
||||
3. **系统资源**:
|
||||
- CPU 使用率
|
||||
- 内存使用率
|
||||
- 数据库连接数
|
||||
|
||||
---
|
||||
|
||||
**优化完成时间**:2024年
|
||||
**优化版本**:v1.0
|
||||
**维护人员**:AI Assistant
|
||||
178
知识库问答助手测试总结.md
Normal file
178
知识库问答助手测试总结.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# 知识库问答助手测试总结
|
||||
|
||||
## 测试时间
|
||||
2026-01-23 00:00
|
||||
|
||||
## Agent信息
|
||||
- **名称**: 知识库问答助手
|
||||
- **ID**: `45c56398-ad1d-4533-89e0-ba02f9c47932`
|
||||
- **状态**: published(已发布)
|
||||
- **节点数量**: 10个
|
||||
- **连接数量**: 9条
|
||||
|
||||
## 工作流结构
|
||||
|
||||
1. **开始节点** - 接收用户问题
|
||||
2. **问题预处理** - 整理输入数据
|
||||
3. **文本向量化** - HTTP节点调用DeepSeek embedding API
|
||||
4. **提取向量** - JSON节点提取embedding向量
|
||||
5. **准备搜索数据** - 合并向量和查询文本
|
||||
6. **知识库检索** - 向量数据库节点进行语义搜索
|
||||
7. **整理检索结果** - 合并搜索结果和查询
|
||||
8. **生成答案** - LLM节点基于检索结果生成答案
|
||||
9. **提取最终答案** - JSON节点提取最终文本
|
||||
10. **结束节点** - 返回答案
|
||||
|
||||
## 测试结果
|
||||
|
||||
### 测试输入
|
||||
```
|
||||
问题: 什么是人工智能?
|
||||
```
|
||||
|
||||
### 执行状态
|
||||
- **状态**: failed(执行失败)
|
||||
- **执行时间**: 2240ms
|
||||
- **执行ID**: `e775395c-a306-4544-abaf-357c9245f56e`
|
||||
|
||||
### 错误分析
|
||||
|
||||
#### 1. HTTP节点调用embedding API失败
|
||||
- **错误信息**: "Not Found. Please check the configuration."
|
||||
- **节点**: `http-embedding`
|
||||
- **API URL**: `https://api.deepseek.com/v1/embeddings`
|
||||
- **模型**: `deepseek-embedding`
|
||||
- **问题**: DeepSeek可能不支持embedding API,或者URL/模型名称不正确
|
||||
|
||||
#### 2. 向量数据库节点无法获取查询向量
|
||||
- **错误信息**: "节点 vector-search 执行失败: 向量数据库操作失败: 无法获取查询向量"
|
||||
- **节点**: `vector-search`
|
||||
- **原因**: 由于embedding API调用失败,没有获取到向量数据
|
||||
|
||||
### 执行日志关键信息
|
||||
|
||||
```
|
||||
[3] 2026-01-22 16:00:25 [INFO]
|
||||
节点: http-embedding (http)
|
||||
消息: 节点 http-embedding (http) 开始执行
|
||||
|
||||
[10] 2026-01-22 16:00:26 [ERROR]
|
||||
节点: http-embedding (http)
|
||||
消息: HTTP请求失败: 404
|
||||
数据: {
|
||||
"error_msg": "Not Found. Please check the configuration."
|
||||
}
|
||||
|
||||
[11] 2026-01-22 16:00:26 [INFO]
|
||||
节点: vector-search (vector_db)
|
||||
消息: 节点 vector-search (vector_db) 开始执行
|
||||
|
||||
[13] 2026-01-22 16:00:26 [ERROR]
|
||||
节点: (无) ((无))
|
||||
消息: 工作流任务执行失败
|
||||
错误: 节点 vector-search 执行失败: 向量数据库操作失败: 无法获取查询向量
|
||||
```
|
||||
|
||||
## 问题根因
|
||||
|
||||
1. **DeepSeek Embedding API不支持或配置错误**
|
||||
- DeepSeek可能不提供embedding API
|
||||
- 或者需要使用不同的API端点/模型名称
|
||||
- 需要验证DeepSeek是否支持embedding功能
|
||||
|
||||
2. **缺少备选方案**
|
||||
- 当前工作流完全依赖embedding API
|
||||
- 没有fallback机制处理API调用失败的情况
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案1: 使用其他embedding服务
|
||||
1. **使用OpenAI Embedding API**
|
||||
- URL: `https://api.openai.com/v1/embeddings`
|
||||
- 模型: `text-embedding-ada-002` 或 `text-embedding-3-small`
|
||||
- 需要配置OpenAI API Key
|
||||
|
||||
2. **使用本地embedding模型**
|
||||
- 使用sentence-transformers等库
|
||||
- 在服务器端运行embedding模型
|
||||
- 通过HTTP节点调用本地服务
|
||||
|
||||
### 方案2: 简化工作流(用于测试)
|
||||
1. **跳过embedding步骤**
|
||||
- 直接使用文本关键词匹配
|
||||
- 或者使用LLM节点进行语义理解
|
||||
- 简化向量数据库的使用
|
||||
|
||||
2. **使用预计算的向量**
|
||||
- 预先将知识库文档向量化
|
||||
- 存储向量数据
|
||||
- 查询时只需要搜索,不需要实时向量化
|
||||
|
||||
### 方案3: 修复DeepSeek配置
|
||||
1. **验证DeepSeek API文档**
|
||||
- 确认是否支持embedding
|
||||
- 确认正确的API端点和模型名称
|
||||
- 更新HTTP节点配置
|
||||
|
||||
## 下一步行动
|
||||
|
||||
1. **验证DeepSeek Embedding API**
|
||||
- 查阅DeepSeek官方文档
|
||||
- 测试API端点是否可用
|
||||
- 确认模型名称是否正确
|
||||
|
||||
2. **准备测试数据**
|
||||
- 创建知识库文档
|
||||
- 将文档向量化并存储到向量数据库
|
||||
- 使用`knowledge_base`集合
|
||||
|
||||
3. **修复或替换embedding节点**
|
||||
- 根据验证结果修复DeepSeek配置
|
||||
- 或替换为其他embedding服务
|
||||
- 或使用简化方案
|
||||
|
||||
4. **重新测试**
|
||||
- 修复后重新执行测试
|
||||
- 验证完整工作流是否正常
|
||||
- 测试知识库检索和答案生成
|
||||
|
||||
## 测试命令
|
||||
|
||||
```bash
|
||||
# 发布Agent
|
||||
python3 publish_agent.py "知识库问答助手"
|
||||
|
||||
# 测试Agent
|
||||
python3 test_workflow_tool.py -a "知识库问答助手" -i "什么是人工智能?"
|
||||
|
||||
# 查看执行日志
|
||||
python3 check_execution_logs.py
|
||||
```
|
||||
|
||||
## 相关文件
|
||||
|
||||
- **Agent生成脚本**: `backend/scripts/generate_knowledge_base_qa_agent.py`
|
||||
- **测试工具**: `test_workflow_tool.py`
|
||||
- **日志查看工具**: `check_execution_logs.py`
|
||||
- **发布脚本**: `publish_agent.py`
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **知识库数据准备**
|
||||
- 需要先准备知识库文档
|
||||
- 文档需要向量化并存储
|
||||
- 集合名称必须是`knowledge_base`
|
||||
|
||||
2. **API配置**
|
||||
- 确保embedding API可用
|
||||
- 配置正确的API Key
|
||||
- 验证API端点和模型名称
|
||||
|
||||
3. **向量数据库**
|
||||
- 当前使用内存存储(简化实现)
|
||||
- 生产环境应使用ChromaDB、Pinecone等
|
||||
- 数据在服务重启后会丢失
|
||||
|
||||
---
|
||||
测试完成时间: 2026-01-23 00:02
|
||||
测试人员: AI Assistant
|
||||
260
节点配置页面增强方案-完成情况.md
Normal file
260
节点配置页面增强方案-完成情况.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# 节点配置页面增强方案 - 完成情况报告
|
||||
|
||||
## 📊 总体完成度:约 75%
|
||||
|
||||
---
|
||||
|
||||
## ✅ 已完成功能(高优先级)
|
||||
|
||||
### 1. 数据流转可视化面板 ⭐⭐⭐⭐⭐
|
||||
|
||||
**完成度:85%**
|
||||
|
||||
| 功能点 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| ✅ 显示上游节点的输出变量 | **已完成** | 在"数据流"标签页中显示所有上游节点及其输出变量 |
|
||||
| ✅ 显示数据流转路径 | **已完成** | 显示上游节点列表和下游节点列表,清晰展示数据流转 |
|
||||
| ⚠️ 提供数据预览功能 | **部分完成** | 有独立的"数据预览"标签页,但数据流面板中上游节点的实时数据预览未实现 |
|
||||
| ✅ 一键插入变量 | **已完成** | 点击变量标签即可插入到配置字段 |
|
||||
|
||||
**实现位置:**
|
||||
- 标签页:`数据流` (name="dataflow")
|
||||
- 文件:`frontend/src/components/WorkflowEditor/WorkflowEditor.vue` (1439-1530行)
|
||||
|
||||
**已实现功能:**
|
||||
- ✅ 上游节点列表展示
|
||||
- ✅ 上游节点输出变量展示(带类型和描述)
|
||||
- ✅ 当前节点输出字段说明
|
||||
- ✅ 下游节点列表展示
|
||||
- ✅ 变量一键插入功能
|
||||
|
||||
**待完善:**
|
||||
- ⚠️ 上游节点的实时数据预览(需要执行记录)
|
||||
|
||||
---
|
||||
|
||||
### 2. 记忆信息展示 ⭐⭐⭐⭐⭐
|
||||
|
||||
**完成度:100%**
|
||||
|
||||
| 功能点 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| ✅ 实时显示记忆内容 | **已完成** | 通过API获取并显示记忆数据 |
|
||||
| ✅ 对话历史预览 | **已完成** | 对话框形式展示完整对话历史 |
|
||||
| ✅ 用户画像展示 | **已完成** | 表格形式展示用户画像字段 |
|
||||
| ✅ TTL 和过期时间 | **已完成** | 自动计算并显示过期时间(天/小时/分钟) |
|
||||
|
||||
**实现位置:**
|
||||
- 标签页:`记忆信息` (name="memory",仅 Cache 节点显示)
|
||||
- 文件:`frontend/src/components/WorkflowEditor/WorkflowEditor.vue` (1532-1644行)
|
||||
- 后端API:`backend/app/api/execution_logs.py` (缓存查询接口)
|
||||
|
||||
**已实现功能:**
|
||||
- ✅ 记忆键显示
|
||||
- ✅ 记忆状态显示(存在/不存在)
|
||||
- ✅ 对话历史统计和详情查看
|
||||
- ✅ 用户画像统计和详情查看
|
||||
- ✅ TTL 信息显示
|
||||
- ✅ 刷新记忆功能(连接实际API)
|
||||
- ✅ 删除记忆功能
|
||||
- ✅ 清空记忆功能
|
||||
|
||||
**后端API:**
|
||||
- ✅ `GET /api/v1/execution-logs/cache/{key}` - 获取缓存值
|
||||
- ✅ `DELETE /api/v1/execution-logs/cache/{key}` - 删除缓存值
|
||||
|
||||
---
|
||||
|
||||
### 3. 变量智能提示增强 ⭐⭐⭐⭐⭐
|
||||
|
||||
**完成度:80%**
|
||||
|
||||
| 功能点 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| ✅ 按来源分组显示变量 | **已完成** | 基础变量、上游节点变量、记忆变量分组显示 |
|
||||
| ✅ 类型提示和描述 | **已完成** | 不同变量类型用不同颜色标签,鼠标悬停显示描述 |
|
||||
| ❌ 自动补全功能 | **未实现** | 输入 `{{` 时自动提示变量(需要实现) |
|
||||
| ✅ 一键插入变量 | **已完成** | 点击变量标签直接插入到提示词字段 |
|
||||
|
||||
**实现位置:**
|
||||
- 标签页:`基础` (name="basic") - LLM节点提示词字段下方
|
||||
- 文件:`frontend/src/components/WorkflowEditor/WorkflowEditor.vue` (396-476行)
|
||||
|
||||
**已实现功能:**
|
||||
- ✅ 变量分组显示(基础变量、上游变量、记忆变量)
|
||||
- ✅ 变量类型标签(不同颜色区分)
|
||||
- ✅ 变量描述提示(Tooltip)
|
||||
- ✅ 一键插入变量功能
|
||||
- ✅ 可折叠面板
|
||||
|
||||
**待完善:**
|
||||
- ❌ 自动补全:输入 `{{` 时自动弹出变量选择器
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 部分完成功能(中优先级)
|
||||
|
||||
### 4. 执行时数据预览 ⭐⭐⭐⭐
|
||||
|
||||
**完成度:70%**
|
||||
|
||||
| 功能点 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| ✅ 显示实际输入/输出数据 | **已完成** | JSON格式化显示节点的输入和输出数据 |
|
||||
| ✅ 执行时间和状态 | **已完成** | 显示执行时间、开始时间、完成时间 |
|
||||
| ❌ 缓存命中情况 | **未实现** | 未显示缓存命中信息 |
|
||||
|
||||
**实现位置:**
|
||||
- 标签页:`数据预览` (name="preview")
|
||||
- 文件:`frontend/src/components/WorkflowEditor/WorkflowEditor.vue` (1645-1783行)
|
||||
- 后端API:`backend/app/api/execution_logs.py` (节点执行数据接口)
|
||||
|
||||
**已实现功能:**
|
||||
- ✅ 执行记录选择器
|
||||
- ✅ 输入数据展示(JSON格式化)
|
||||
- ✅ 输出数据展示(JSON格式化)
|
||||
- ✅ 执行时间信息
|
||||
- ✅ 复制到剪贴板功能
|
||||
- ✅ 自动加载最近的执行记录
|
||||
|
||||
**后端API:**
|
||||
- ✅ `GET /api/v1/execution-logs/executions/{execution_id}/nodes/{node_id}/data` - 获取节点执行数据
|
||||
|
||||
**待完善:**
|
||||
- ❌ 缓存命中情况显示(需要从执行日志中提取 cache_hit 信息)
|
||||
|
||||
---
|
||||
|
||||
## ❌ 未实现功能(低优先级)
|
||||
|
||||
### 5. 智能配置助手 ⭐⭐⭐⭐
|
||||
|
||||
**完成度:30%**
|
||||
|
||||
| 功能点 | 状态 | 说明 |
|
||||
|--------|------|------|
|
||||
| ❌ 场景化配置向导 | **未实现** | 分步骤引导用户完成配置 |
|
||||
| ⚠️ 配置模板库 | **部分实现** | 有快速模板功能,但不是完整的模板库 |
|
||||
| ✅ 一键应用模板 | **已完成** | 快速模板可以一键应用 |
|
||||
|
||||
**已实现功能:**
|
||||
- ✅ 快速模板选择(在"基础"标签页中)
|
||||
- ✅ 模板一键应用功能
|
||||
- ✅ 支持多种节点类型的模板(HTTP、LLM、JSON、文本、缓存等)
|
||||
|
||||
**待实现:**
|
||||
- ❌ 场景化配置向导(分步骤引导)
|
||||
- ❌ 完整的配置模板库(分类、搜索、收藏等)
|
||||
- ❌ 配置模板的导入/导出功能
|
||||
|
||||
---
|
||||
|
||||
## 📋 详细功能清单
|
||||
|
||||
### ✅ 已实现的核心功能
|
||||
|
||||
1. **数据流转可视化**
|
||||
- ✅ 上游节点列表
|
||||
- ✅ 上游节点变量展示
|
||||
- ✅ 输出字段说明
|
||||
- ✅ 下游节点列表
|
||||
- ✅ 变量一键插入
|
||||
|
||||
2. **记忆信息管理**
|
||||
- ✅ 记忆内容实时查看
|
||||
- ✅ 对话历史预览
|
||||
- ✅ 用户画像展示
|
||||
- ✅ TTL 信息显示
|
||||
- ✅ 记忆操作(刷新、删除、清空)
|
||||
|
||||
3. **变量智能提示**
|
||||
- ✅ 变量分组显示
|
||||
- ✅ 类型和描述提示
|
||||
- ✅ 一键插入功能
|
||||
|
||||
4. **执行数据预览**
|
||||
- ✅ 输入/输出数据展示
|
||||
- ✅ 执行时间信息
|
||||
- ✅ 执行记录选择
|
||||
|
||||
5. **基础功能**
|
||||
- ✅ 快速模板功能
|
||||
- ✅ 节点测试功能(已移到独立标签页)
|
||||
|
||||
### ⚠️ 待完善的功能
|
||||
|
||||
1. **数据流转可视化**
|
||||
- ⚠️ 上游节点的实时数据预览(需要执行记录支持)
|
||||
|
||||
2. **变量智能提示**
|
||||
- ❌ 自动补全功能(输入 `{{` 时自动提示)
|
||||
|
||||
3. **执行数据预览**
|
||||
- ❌ 缓存命中情况显示
|
||||
|
||||
4. **智能配置助手**
|
||||
- ❌ 场景化配置向导
|
||||
- ❌ 完整的配置模板库
|
||||
|
||||
---
|
||||
|
||||
## 🎯 下一步建议
|
||||
|
||||
### 高优先级(建议优先完成)
|
||||
|
||||
1. **变量自动补全功能**
|
||||
- 实现输入 `{{` 时自动弹出变量选择器
|
||||
- 支持键盘导航和选择
|
||||
- 预计时间:2-3小时
|
||||
|
||||
2. **上游节点数据预览**
|
||||
- 在数据流面板中显示上游节点的实际输出数据
|
||||
- 需要从执行记录中提取数据
|
||||
- 预计时间:2-3小时
|
||||
|
||||
3. **缓存命中情况显示**
|
||||
- 在执行数据预览中显示缓存命中信息
|
||||
- 需要从执行日志中提取 cache_hit 字段
|
||||
- 预计时间:1-2小时
|
||||
|
||||
### 中优先级(后续优化)
|
||||
|
||||
4. **场景化配置向导**
|
||||
- 为复杂节点提供分步骤配置向导
|
||||
- 根据使用场景提供预设配置
|
||||
- 预计时间:4-6小时
|
||||
|
||||
5. **配置模板库**
|
||||
- 完整的模板管理系统
|
||||
- 模板分类、搜索、收藏功能
|
||||
- 预计时间:6-8小时
|
||||
|
||||
---
|
||||
|
||||
## 📊 完成度统计
|
||||
|
||||
| 优先级 | 功能模块 | 完成度 | 状态 |
|
||||
|--------|----------|--------|------|
|
||||
| 高 | 数据流转可视化面板 | 85% | ✅ 基本完成 |
|
||||
| 高 | 记忆信息展示 | 100% | ✅ 已完成 |
|
||||
| 高 | 变量智能提示增强 | 80% | ✅ 基本完成 |
|
||||
| 中 | 执行时数据预览 | 70% | ⚠️ 部分完成 |
|
||||
| 低 | 智能配置助手 | 30% | ❌ 待实现 |
|
||||
|
||||
**总体完成度:约 75%**
|
||||
|
||||
---
|
||||
|
||||
## 🎉 已实现的亮点功能
|
||||
|
||||
1. **完整的数据流转可视化** - 用户可以清楚看到数据如何流转
|
||||
2. **实时记忆信息管理** - Cache 节点可以实时查看和管理记忆
|
||||
3. **智能变量提示** - 分组显示、类型提示、一键插入
|
||||
4. **执行数据预览** - 查看实际执行时的输入/输出数据
|
||||
5. **后端API完整支持** - 所有功能都有对应的后端API支持
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**更新时间**:2024年
|
||||
**维护人员**:AI Assistant
|
||||
632
节点配置页面增强方案.md
Normal file
632
节点配置页面增强方案.md
Normal file
@@ -0,0 +1,632 @@
|
||||
# 节点配置页面增强方案
|
||||
|
||||
## 一、您的想法完全正确!
|
||||
|
||||
您的建议非常有价值,这正是提升用户体验和开发效率的关键改进点。当前系统虽然有一些基础功能,但确实需要增强以下方面:
|
||||
|
||||
### 当前系统的不足
|
||||
|
||||
1. **变量可见性不足**
|
||||
- 虽然有 `availableVariables` 计算属性,但用户看不到:
|
||||
- 上游节点实际输出了哪些变量
|
||||
- 变量的数据结构(嵌套字段)
|
||||
- 变量的实时值(执行时)
|
||||
|
||||
2. **记忆信息不直观**
|
||||
- Cache 节点配置时,用户看不到:
|
||||
- 当前记忆的内容(conversation_history、user_profile)
|
||||
- 记忆的键名和存储位置
|
||||
- TTL 和过期时间
|
||||
|
||||
3. **数据流转不透明**
|
||||
- 用户不知道:
|
||||
- 数据从哪个节点来
|
||||
- 经过哪些转换
|
||||
- 最终输出什么格式
|
||||
|
||||
4. **配置指导不足**
|
||||
- 缺少:
|
||||
- 节点使用示例
|
||||
- 常见配置模式
|
||||
- 最佳实践提示
|
||||
|
||||
## 二、增强方案设计
|
||||
|
||||
### 方案 1:数据流转可视化面板 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### 1.1 新增"数据流"标签页
|
||||
|
||||
在节点配置面板中添加"数据流"标签页,显示:
|
||||
|
||||
```vue
|
||||
<el-tab-pane label="数据流" name="dataflow">
|
||||
<!-- 上游数据源 -->
|
||||
<el-card shadow="never" class="dataflow-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>📥 输入数据源</span>
|
||||
<el-tag size="small" type="info">{{ upstreamNodes.length }} 个上游节点</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 上游节点列表 -->
|
||||
<div v-for="edge in upstreamEdges" :key="edge.id" class="upstream-item">
|
||||
<div class="node-info">
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
<span class="node-name">{{ getNodeLabel(edge.source) }}</span>
|
||||
<el-tag size="small" :type="getNodeTypeColor(edge.source)">
|
||||
{{ getNodeType(edge.source) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- 可用变量列表 -->
|
||||
<div class="variables-list">
|
||||
<div
|
||||
v-for="var in getUpstreamVariables(edge.source)"
|
||||
:key="var.name"
|
||||
class="variable-item"
|
||||
@click="insertVariable(var.name)"
|
||||
>
|
||||
<el-icon><Document /></el-icon>
|
||||
<span class="var-name">{{ var.name }}</span>
|
||||
<span class="var-type">{{ var.type }}</span>
|
||||
<el-tooltip :content="var.description" placement="right">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据预览(如果有执行记录) -->
|
||||
<el-collapse v-if="hasExecutionData(edge.source)">
|
||||
<el-collapse-item title="数据预览" name="preview">
|
||||
<pre class="data-preview">{{ formatExecutionData(edge.source) }}</pre>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 当前节点输出 -->
|
||||
<el-card shadow="never" class="dataflow-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>📤 输出数据</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 输出字段说明 -->
|
||||
<div class="output-fields">
|
||||
<div
|
||||
v-for="field in getOutputFields(selectedNode.type)"
|
||||
:key="field.name"
|
||||
class="output-field"
|
||||
>
|
||||
<span class="field-name">{{ field.name }}</span>
|
||||
<span class="field-desc">{{ field.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下游节点 -->
|
||||
<div v-if="downstreamNodes.length > 0" class="downstream-section">
|
||||
<div class="section-title">下游节点:</div>
|
||||
<div
|
||||
v-for="node in downstreamNodes"
|
||||
:key="node.id"
|
||||
class="downstream-item"
|
||||
>
|
||||
<el-icon><ArrowRight /></el-icon>
|
||||
<span>{{ node.data.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
```
|
||||
|
||||
#### 1.2 功能特性
|
||||
|
||||
- **上游节点追踪**:显示所有连接到当前节点的上游节点
|
||||
- **变量自动推断**:根据上游节点类型自动推断可用变量
|
||||
- **数据预览**:如果有执行记录,显示实际数据
|
||||
- **一键插入**:点击变量名直接插入到配置字段
|
||||
- **类型提示**:显示变量的数据类型和结构
|
||||
|
||||
### 方案 2:记忆信息展示 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### 2.1 Cache 节点增强
|
||||
|
||||
对于 Cache 节点,显示详细的记忆信息:
|
||||
|
||||
```vue
|
||||
<el-tab-pane label="记忆信息" name="memory" v-if="selectedNode.type === 'cache'">
|
||||
<!-- 当前记忆内容 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>💾 记忆内容</span>
|
||||
<el-button size="small" @click="refreshMemory">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 记忆键 -->
|
||||
<el-form-item label="记忆键">
|
||||
<el-input
|
||||
v-model="memoryKey"
|
||||
placeholder="user_memory_{user_id}"
|
||||
readonly
|
||||
/>
|
||||
<el-tag size="small" style="margin-left: 8px;">
|
||||
{{ memoryStatus }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 对话历史 -->
|
||||
<el-form-item label="对话历史">
|
||||
<el-tag
|
||||
v-if="memoryData?.conversation_history"
|
||||
type="info"
|
||||
>
|
||||
{{ memoryData.conversation_history.length }} 条记录
|
||||
</el-tag>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
@click="showConversationHistory"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 用户画像 -->
|
||||
<el-form-item label="用户画像">
|
||||
<el-tag
|
||||
v-if="memoryData?.user_profile"
|
||||
type="success"
|
||||
>
|
||||
{{ Object.keys(memoryData.user_profile).length }} 个字段
|
||||
</el-tag>
|
||||
<el-button
|
||||
size="small"
|
||||
text
|
||||
@click="showUserProfile"
|
||||
>
|
||||
查看详情
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<!-- TTL 信息 -->
|
||||
<el-form-item label="过期时间">
|
||||
<el-tag type="warning">
|
||||
{{ ttlInfo }}
|
||||
</el-tag>
|
||||
</el-form-item>
|
||||
</el-card>
|
||||
|
||||
<!-- 记忆操作 -->
|
||||
<el-card shadow="never" style="margin-top: 15px;">
|
||||
<template #header>
|
||||
<span>🔧 记忆操作</span>
|
||||
</template>
|
||||
|
||||
<el-button-group>
|
||||
<el-button size="small" @click="testMemoryQuery">测试查询</el-button>
|
||||
<el-button size="small" @click="clearMemory">清空记忆</el-button>
|
||||
<el-button size="small" type="danger" @click="deleteMemory">删除记忆</el-button>
|
||||
</el-button-group>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
```
|
||||
|
||||
#### 2.2 功能特性
|
||||
|
||||
- **实时记忆查看**:显示当前记忆的实际内容
|
||||
- **对话历史预览**:显示最近的对话记录
|
||||
- **用户画像展示**:显示用户画像字段
|
||||
- **TTL 倒计时**:显示记忆过期时间
|
||||
- **记忆操作**:提供测试、清空、删除等操作
|
||||
|
||||
### 方案 3:智能配置助手 ⭐⭐⭐⭐
|
||||
|
||||
#### 3.1 配置向导
|
||||
|
||||
为复杂节点提供配置向导:
|
||||
|
||||
```vue
|
||||
<el-tab-pane label="配置助手" name="assistant">
|
||||
<!-- 配置模式选择 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>🎯 快速配置</span>
|
||||
</template>
|
||||
|
||||
<el-radio-group v-model="configMode">
|
||||
<el-radio label="simple">简单模式</el-radio>
|
||||
<el-radio label="advanced">高级模式</el-radio>
|
||||
<el-radio label="template">模板模式</el-radio>
|
||||
</el-radio-group>
|
||||
|
||||
<!-- 简单模式 -->
|
||||
<div v-if="configMode === 'simple'" class="config-wizard">
|
||||
<el-steps :active="wizardStep" simple>
|
||||
<el-step title="选择场景" />
|
||||
<el-step title="配置参数" />
|
||||
<el-step title="完成" />
|
||||
</el-steps>
|
||||
|
||||
<!-- 场景选择 -->
|
||||
<div v-if="wizardStep === 0" class="wizard-content">
|
||||
<div class="scenario-list">
|
||||
<div
|
||||
v-for="scenario in getScenarios(selectedNode.type)"
|
||||
:key="scenario.id"
|
||||
class="scenario-item"
|
||||
@click="selectScenario(scenario)"
|
||||
>
|
||||
<el-icon><component :is="scenario.icon" /></el-icon>
|
||||
<div class="scenario-info">
|
||||
<div class="scenario-name">{{ scenario.name }}</div>
|
||||
<div class="scenario-desc">{{ scenario.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 参数配置 -->
|
||||
<div v-if="wizardStep === 1" class="wizard-content">
|
||||
<el-form :model="wizardConfig" label-width="120px">
|
||||
<el-form-item
|
||||
v-for="field in selectedScenario.fields"
|
||||
:key="field.name"
|
||||
:label="field.label"
|
||||
>
|
||||
<el-input
|
||||
v-if="field.type === 'text'"
|
||||
v-model="wizardConfig[field.name]"
|
||||
:placeholder="field.placeholder"
|
||||
/>
|
||||
<el-select
|
||||
v-else-if="field.type === 'select'"
|
||||
v-model="wizardConfig[field.name]"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in field.options"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模板模式 -->
|
||||
<div v-else-if="configMode === 'template'" class="template-selector">
|
||||
<el-select
|
||||
v-model="selectedTemplate"
|
||||
placeholder="选择配置模板"
|
||||
@change="applyTemplate"
|
||||
>
|
||||
<el-option
|
||||
v-for="template in getTemplates(selectedNode.type)"
|
||||
:key="template.id"
|
||||
:label="template.name"
|
||||
:value="template.id"
|
||||
>
|
||||
<div class="template-option">
|
||||
<span>{{ template.name }}</span>
|
||||
<el-tag size="small" type="info">{{ template.category }}</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
|
||||
<el-card v-if="selectedTemplate" shadow="never" style="margin-top: 15px;">
|
||||
<div class="template-preview">
|
||||
<div class="preview-title">模板预览:</div>
|
||||
<pre>{{ getTemplatePreview(selectedTemplate) }}</pre>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
```
|
||||
|
||||
#### 3.2 功能特性
|
||||
|
||||
- **场景化配置**:根据使用场景提供预设配置
|
||||
- **配置向导**:分步骤引导用户完成配置
|
||||
- **模板库**:提供常用配置模板
|
||||
- **一键应用**:快速应用模板配置
|
||||
|
||||
### 方案 4:变量智能提示 ⭐⭐⭐⭐⭐
|
||||
|
||||
#### 4.1 增强变量插入功能
|
||||
|
||||
```vue
|
||||
<!-- 在配置字段中添加智能提示 -->
|
||||
<el-form-item label="提示词">
|
||||
<el-input
|
||||
v-model="selectedNode.data.prompt"
|
||||
type="textarea"
|
||||
:rows="6"
|
||||
placeholder="输入提示词..."
|
||||
/>
|
||||
|
||||
<!-- 变量提示面板 -->
|
||||
<el-collapse v-model="variablePanelActive" style="margin-top: 10px;">
|
||||
<el-collapse-item title="可用变量" name="variables">
|
||||
<div class="variable-suggestions">
|
||||
<!-- 基础变量 -->
|
||||
<div class="var-group">
|
||||
<div class="group-title">基础变量</div>
|
||||
<div class="var-items">
|
||||
<el-tag
|
||||
v-for="var in basicVariables"
|
||||
:key="var"
|
||||
class="var-tag"
|
||||
@click="insertVariable('prompt', var)"
|
||||
>
|
||||
{{ var }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 上游变量 -->
|
||||
<div class="var-group" v-if="upstreamVariables.length > 0">
|
||||
<div class="group-title">上游节点变量</div>
|
||||
<div class="var-items">
|
||||
<el-tag
|
||||
v-for="var in upstreamVariables"
|
||||
:key="var.name"
|
||||
class="var-tag"
|
||||
@click="insertVariable('prompt', var.name)"
|
||||
>
|
||||
{{ var.name }}
|
||||
<el-tooltip :content="var.description">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 记忆变量 -->
|
||||
<div class="var-group" v-if="memoryVariables.length > 0">
|
||||
<div class="group-title">记忆变量</div>
|
||||
<div class="var-items">
|
||||
<el-tag
|
||||
v-for="var in memoryVariables"
|
||||
:key="var.name"
|
||||
class="var-tag"
|
||||
@click="insertVariable('prompt', var.name)"
|
||||
>
|
||||
{{ var.name }}
|
||||
<el-tooltip :content="var.description">
|
||||
<el-icon><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-form-item>
|
||||
```
|
||||
|
||||
#### 4.2 功能特性
|
||||
|
||||
- **变量分组**:按来源分组显示变量
|
||||
- **类型提示**:显示变量的数据类型
|
||||
- **描述信息**:鼠标悬停显示变量说明
|
||||
- **一键插入**:点击变量标签直接插入
|
||||
- **自动补全**:输入 `{{` 时自动提示变量
|
||||
|
||||
### 方案 5:执行时数据预览 ⭐⭐⭐⭐
|
||||
|
||||
#### 5.1 实时数据流追踪
|
||||
|
||||
```vue
|
||||
<el-tab-pane label="数据预览" name="preview">
|
||||
<!-- 执行记录选择 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>📊 执行数据</span>
|
||||
<el-select
|
||||
v-model="selectedExecutionId"
|
||||
placeholder="选择执行记录"
|
||||
size="small"
|
||||
style="width: 200px;"
|
||||
>
|
||||
<el-option
|
||||
v-for="exec in recentExecutions"
|
||||
:key="exec.id"
|
||||
:label="`${exec.created_at} - ${exec.status}`"
|
||||
:value="exec.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 输入数据 -->
|
||||
<el-card shadow="never" style="margin-bottom: 15px;">
|
||||
<template #header>
|
||||
<span>📥 输入数据</span>
|
||||
</template>
|
||||
<el-scrollbar height="200px">
|
||||
<pre class="data-viewer">{{ formatInputData(executionData?.input) }}</pre>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
|
||||
<!-- 输出数据 -->
|
||||
<el-card shadow="never">
|
||||
<template #header>
|
||||
<span>📤 输出数据</span>
|
||||
</template>
|
||||
<el-scrollbar height="200px">
|
||||
<pre class="data-viewer">{{ formatOutputData(executionData?.output) }}</pre>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
|
||||
<!-- 执行信息 -->
|
||||
<el-descriptions :column="2" border style="margin-top: 15px;">
|
||||
<el-descriptions-item label="执行时间">
|
||||
{{ executionData?.duration }}ms
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行状态">
|
||||
<el-tag :type="getStatusType(executionData?.status)">
|
||||
{{ executionData?.status }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="缓存命中" v-if="executionData?.cache_hit !== undefined">
|
||||
<el-tag :type="executionData.cache_hit ? 'success' : 'info'">
|
||||
{{ executionData.cache_hit ? '是' : '否' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-tab-pane>
|
||||
```
|
||||
|
||||
## 三、实施优先级
|
||||
|
||||
### 🔥 高优先级(立即实施)
|
||||
|
||||
1. **数据流转可视化面板** ⭐⭐⭐⭐⭐
|
||||
- 实施难度:中
|
||||
- 效果:显著提升用户体验
|
||||
- 预计时间:3-4 小时
|
||||
|
||||
2. **变量智能提示增强** ⭐⭐⭐⭐⭐
|
||||
- 实施难度:低
|
||||
- 效果:降低配置错误率
|
||||
- 预计时间:2-3 小时
|
||||
|
||||
### 🟡 中优先级(近期实施)
|
||||
|
||||
3. **记忆信息展示** ⭐⭐⭐⭐⭐
|
||||
- 实施难度:中
|
||||
- 效果:直观了解记忆状态
|
||||
- 预计时间:2-3 小时
|
||||
|
||||
4. **执行时数据预览** ⭐⭐⭐⭐
|
||||
- 实施难度:中
|
||||
- 效果:便于调试和优化
|
||||
- 预计时间:2-3 小时
|
||||
|
||||
### 🟢 低优先级(长期优化)
|
||||
|
||||
5. **智能配置助手** ⭐⭐⭐⭐
|
||||
- 实施难度:高
|
||||
- 效果:降低学习成本
|
||||
- 预计时间:4-6 小时
|
||||
|
||||
## 四、技术实现要点
|
||||
|
||||
### 1. 数据获取
|
||||
|
||||
```typescript
|
||||
// 获取上游节点变量
|
||||
const getUpstreamVariables = (nodeId: string) => {
|
||||
const node = nodes.value.find(n => n.id === nodeId)
|
||||
if (!node) return []
|
||||
|
||||
// 根据节点类型推断输出变量
|
||||
const variables = []
|
||||
switch (node.type) {
|
||||
case 'llm':
|
||||
variables.push({ name: 'output', type: 'string', description: 'LLM生成的回复' })
|
||||
variables.push({ name: 'response', type: 'string', description: '完整响应' })
|
||||
break
|
||||
case 'cache':
|
||||
variables.push({ name: 'memory', type: 'object', description: '记忆数据' })
|
||||
variables.push({ name: 'conversation_history', type: 'array', description: '对话历史' })
|
||||
variables.push({ name: 'user_profile', type: 'object', description: '用户画像' })
|
||||
break
|
||||
case 'transform':
|
||||
// 从mapping中提取
|
||||
const mapping = node.data?.mapping || {}
|
||||
Object.keys(mapping).forEach(key => {
|
||||
variables.push({ name: key, type: 'any', description: `映射字段: ${key}` })
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 记忆数据获取
|
||||
|
||||
```typescript
|
||||
// 获取记忆数据
|
||||
const fetchMemoryData = async (nodeId: string) => {
|
||||
if (selectedNode.value?.type !== 'cache') return
|
||||
|
||||
const key = selectedNode.value.data.key
|
||||
// 调用API获取记忆数据
|
||||
try {
|
||||
const response = await api.get(`/api/v1/cache/${encodeURIComponent(key)}`)
|
||||
memoryData.value = response.data
|
||||
} catch (error) {
|
||||
console.error('获取记忆数据失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 执行数据获取
|
||||
|
||||
```typescript
|
||||
// 获取节点执行数据
|
||||
const fetchNodeExecutionData = async (nodeId: string, executionId: string) => {
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/api/v1/execution-logs/executions/${executionId}/nodes/${nodeId}`
|
||||
)
|
||||
executionData.value = response.data
|
||||
} catch (error) {
|
||||
console.error('获取执行数据失败:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 五、预期效果
|
||||
|
||||
### 用户体验提升
|
||||
|
||||
1. **降低学习成本**
|
||||
- 新用户无需查阅文档即可理解数据流转
|
||||
- 配置错误率降低 50%+
|
||||
|
||||
2. **提升开发效率**
|
||||
- 节点配置时间减少 40%+
|
||||
- 调试时间减少 60%+
|
||||
|
||||
3. **增强可视化**
|
||||
- 数据流转一目了然
|
||||
- 记忆状态实时可见
|
||||
- 执行结果直观展示
|
||||
|
||||
### 开发体验提升
|
||||
|
||||
1. **快速搭建**
|
||||
- 通过模板和向导快速创建工作流
|
||||
- 减少重复配置工作
|
||||
|
||||
2. **错误预防**
|
||||
- 变量类型检查
|
||||
- 配置验证提示
|
||||
- 常见错误预警
|
||||
|
||||
## 六、总结
|
||||
|
||||
您的想法完全正确!这些增强功能将显著提升用户体验和开发效率。建议优先实施:
|
||||
|
||||
1. **数据流转可视化面板** - 让用户清楚看到数据如何流转
|
||||
2. **变量智能提示** - 降低配置错误率
|
||||
3. **记忆信息展示** - 直观了解记忆状态
|
||||
|
||||
这些功能将使平台更加易用和专业!
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**创建时间**:2024年
|
||||
**维护人员**:AI Assistant
|
||||
Reference in New Issue
Block a user