feat(agents): 重构智能体系统,引入Context替代Configuration

- 将Configuration重构为Context,支持动态工具配置
- 新增state.py和tools.py模块,优化状态管理和工具处理
- 移除旧的tools_factory.py和registry.py
- 更新前端API引用路径,修复brandApi命名错误
- 调整README和更新日志,反映架构变更
- 优化智能体历史记录管理,改进SQLite检查点
- 修复默认智能体加载和工具展示问题
This commit is contained in:
Wenjie Zhang 2025-08-31 00:34:26 +08:00
parent 808485aa5e
commit b524ca687b
30 changed files with 637 additions and 516 deletions

View File

@ -29,7 +29,7 @@
<a href="https://www.bilibili.com/video/BV1ETedzREgY/?share_source=copy_web&vd_source=37b0bdbf95b72ea38b2dc959cfadc4d8" target="_blank">
<img width="3651" height="1933" alt="视频演示缩略图" src="https://github.com/user-attachments/assets/eac4fa89-2176-46ae-a649-45a125cb6ed1" />
</a>
<!-- 视频链接文字 -->
<p style="margin-top: 12px;">
<a href="https://www.bilibili.com/video/BV1ETedzREgY/?share_source=copy_web&vd_source=37b0bdbf95b72ea38b2dc959cfadc4d8" target="_blank" style="text-decoration: none; color: #23ade5; font-weight: 500;">
@ -223,7 +223,7 @@ docker compose up paddlex --build
目前该项目默认集成了三个 Demo 智能体包含基础智能体、ReAct、DeepResearch 三个案例 Demo均使用 [LangGraph](https://github.com/langchain-ai/langgraph) 开发。代码位于 [src/agents](src/agents) 目录。在 [src/agents/react/graph.py](src/agents/react/graph.py) 中定义了 `ReActAgent` 示例。
如果需要自定义智能体应用,实现一个继承于 `BaseAgent` 的类,并实现 `get_graph` 方法返回一个 graph 实例。智能体的 `config_schema` 定义了配置参数,可继承 `Configuration` 定义
如果需要自定义智能体应用,实现一个继承于 `BaseAgent` 的类,并实现 `get_graph` 方法返回一个 graph 实例。智能体的 `context_schema` 定义了配置参数
注册智能体的方式请参考已有实现:[src/agents/__init__.py](src/agents/__init__.py)。例如:

View File

@ -1,7 +1,9 @@
目前已有的开发计划包括:
💭 **Features Todo**
- [ ] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等。
- [ ] LangGraph 升级到 0.6+ 版本,并适配新特性,如 context 等,添加 MCP 工具的支持。
- [x] 支持动态工具配置的同时,将 Configuration替换为 Context 后能够正常使用
- [ ] 使用其他的聊天记录管理方法,解决两个问题,一个是上下文长度过长,一个是上下文的类型变得更加丰富,比如多模态等等。(现在是基于 LangGraph 的 Memory 实现的v0.2.3 版本实现),暂定使用 [mem0](github.com/mem0ai/mem0) 来实现。但是目前了解下来,还不是我想要的那种方案。可能会基于这个实现一个 ThreadConvManager 这个类。
- [ ] 添加对于上传文件的支持这里的复杂的地方就在于如何和历史记录结合在一起v0.2.3 版本实现,放在记忆管理后面)
@ -9,6 +11,8 @@
- [x] LlightRAG 知识库中,点击边,没有显示,但是在全屏的时候却又能够显示出来。
- [ ] 部分 doc 格式的文件支持有问题
- [ ] 当出现不支持的文件类型的时候,前端没有限制
- [ ] 默认智能体设置后,在一些情况下依然仅加载第一个智能体
- [ ] 目前只能获取默认的 tools单个智能体的tools没法展示
💯 **More**:

View File

@ -4,7 +4,6 @@ from server.routers.auth_router import auth
from server.routers.chat_router import chat
from server.routers.knowledge_router import knowledge
from server.routers.graph_router import graph
from server.routers.tool_router import tool
router = APIRouter()
@ -14,4 +13,3 @@ router.include_router(auth) # /api/auth/*
router.include_router(chat) # /api/chat/*
router.include_router(knowledge) # /api/knowledge/*
router.include_router(graph) # /api/graph/*
router.include_router(tool) # /api/tool/*

View File

@ -14,7 +14,7 @@ from src import executor, config
from src.agents import agent_manager
from src.models import select_model
from src.utils.logging_config import logger
from src.agents.tools_factory import get_buildin_tools_info
from src.agents.common.tools import get_buildin_tools, gen_tool_info
from server.routers.auth_router import get_admin_user
from server.utils.auth_middleware import get_required_user, get_db
from server.models.user_model import User
@ -102,6 +102,8 @@ async def chat_agent(agent_id: str,
current_user: User = Depends(get_required_user)):
"""使用特定智能体进行对话(需要登录)"""
logger.info(f"agent_id: {agent_id}, query: {query}, config: {config}, meta: {meta}")
meta.update({
"query": query,
"agent_id": agent_id,
@ -134,15 +136,13 @@ async def chat_agent(agent_id: str,
messages = [{"role": "user", "content": query}]
# 构造运行时配置如果没有thread_id则生成一个
config["user_id"] = str(current_user.id)
if "thread_id" not in config or not config["thread_id"]:
config["thread_id"] = str(uuid.uuid4())
logger.debug(f"没有thread_id生成一个: {config['thread_id']=}")
user_id = str(current_user.id)
thread_id = config.get("thread_id")
runnable_config = {"configurable": {**config}}
input_context = {"user_id": user_id, "thread_id": thread_id}
try:
async for msg, metadata in agent.stream_messages(messages, config_schema=runnable_config):
async for msg, metadata in agent.stream_messages(messages, input_context=input_context):
# logger.debug(f"msg: {msg.model_dump()}, metadata: {metadata}")
if isinstance(msg, AIMessageChunk):
yield make_chunk(content=msg.content,
@ -179,9 +179,12 @@ async def update_chat_models(model_provider: str, model_names: list[str], curren
return {"models": config.model_names[model_provider]["models"]}
@chat.get("/tools")
async def get_tools(current_user: User = Depends(get_admin_user)):
async def get_tools(agent_id: str, current_user: User = Depends(get_admin_user)):
"""获取所有可用工具(需要登录)"""
return {"tools": [t.name for t in get_buildin_tools_info()]}
logger.info(f"agent_id: {agent_id}")
tools = get_buildin_tools()
tools_info = gen_tool_info(tools)
return {"tools": {tool["id"]: tool for tool in tools_info}}
@chat.post("/agent/{agent_id}/config")
async def save_agent_config(
@ -192,13 +195,11 @@ async def save_agent_config(
"""保存智能体配置到YAML文件需要管理员权限"""
try:
# 获取Agent实例和配置类
agent = agent_manager.get_agent(agent_id)
if not agent:
if not (agent := agent_manager.get_agent(agent_id)):
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
# 使用配置类的save_to_file方法保存配置
config_cls = agent.config_schema
result = config_cls.save_to_file(config, agent.module_name)
result = agent.context_schema.save_to_file(config, agent.module_name)
if result:
return {"success": True, "message": f"智能体 {agent.name} 配置已保存"}
@ -218,8 +219,7 @@ async def get_agent_history(
"""获取智能体历史消息(需要登录)"""
try:
# 获取Agent实例和配置类
agent = agent_manager.get_agent(agent_id)
if not agent:
if not (agent := agent_manager.get_agent(agent_id)):
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
# 获取历史消息
@ -241,7 +241,8 @@ async def get_agent_config(
if not (agent := agent_manager.get_agent(agent_id)):
raise HTTPException(status_code=404, detail=f"智能体 {agent_id} 不存在")
config = agent.config_schema.from_runnable_config(config={}, module_name=agent.module_name)
config = await agent.get_config()
logger.debug(f"config: {config}, ContextClass: {agent.context_schema=}")
return {"success": True, "config": config}
except Exception as e:
@ -279,6 +280,7 @@ async def create_thread(
):
"""创建新对话线程"""
thread_id = str(uuid.uuid4())
logger.debug(f"thread.agent_id: {thread.agent_id}")
new_thread = Thread(
id=thread_id,
@ -305,18 +307,19 @@ async def create_thread(
@chat.get("/threads", response_model=list[ThreadResponse])
async def list_threads(
agent_id: str | None = None,
agent_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_required_user)
):
"""获取用户的所有对话线程"""
assert agent_id, "agent_id 不能为空"
query = db.query(Thread).filter(
Thread.user_id == str(current_user.id),
Thread.status == 1
Thread.status == 1,
Thread.agent_id == agent_id,
)
if agent_id:
query = query.filter(Thread.agent_id == agent_id)
logger.debug(f"agent_id: {agent_id}")
threads = query.order_by(Thread.update_at.desc()).all()

View File

@ -1,15 +0,0 @@
from fastapi import APIRouter, Depends
from src.agents.tools_factory import get_buildin_tools_info
from server.models.user_model import User
from server.utils.auth_middleware import get_required_user
tool = chat = APIRouter(prefix="/tool", tags=["tool"])
@tool.get("/tools")
async def get_tools(current_user: User = Depends(get_required_user)):
"""获取所有可用工具的信息"""
try:
tools_info = get_buildin_tools_info()
return {"tools": {tool["id"]: tool for tool in tools_info}}
except Exception as e:
return {"error": str(e)}

View File

@ -1,6 +1,6 @@
import asyncio
from .chatbot import ChatbotAgent
from .chatbot.graph import ChatbotAgent
from .react.graph import ReActAgent
class AgentManager:

View File

@ -1,4 +1,3 @@
from .graph import ChatbotAgent
from .configuration import ChatbotConfiguration
__all__ = ["ChatbotAgent", "ChatbotConfiguration"]
__all__ = ["ChatbotAgent"]

View File

@ -1,43 +0,0 @@
import uuid
from dataclasses import dataclass, field
from src.agents.registry import Configuration
from src.agents.tools_factory import get_buildin_tools
@dataclass(kw_only=True)
class ChatbotConfiguration(Configuration):
"""Chatbot 的配置
配置说明
metadata configurable True 的配置项可以被用户配置
configurable False 的配置项不能被用户配置只能由开发者预设
除非显示配置为 False否则所有配置项都默认可配置
"""
system_prompt: str = field(
default="You are a helpful assistant.",
metadata={
"name": "系统提示词",
"description": "用来描述智能体的角色和行为"
},
)
model: str = field(
default="siliconflow/Qwen/Qwen3-235B-A22B-Instruct-2507",
metadata={
"name": "智能体模型",
"options": [],
"description": "智能体的驱动模型"
},
)
tools: list[str] = field(
default_factory=list,
metadata={
"name": "工具",
"options": [t.name for t in get_buildin_tools()], # 这里的选择是所有的工具
"description": "工具列表"
},
)

View File

@ -0,0 +1,26 @@
from typing import Annotated
from dataclasses import dataclass, field
from src.agents.common.context import BaseContext
from src.agents.common.tools import get_buildin_tools
@dataclass(kw_only=True)
class Context(BaseContext):
model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
default="siliconflow/Qwen/Qwen3-235B-A22B-Instruct-2507",
metadata={
"name": "智能体模型",
"options": [],
"description": "智能体的驱动模型"
},
)
tools: Annotated[list[str], {"__template_metadata__": {"kind": "tools"}}] = field(
default_factory=list,
metadata={
"name": "工具",
"options": [t.name for t in get_buildin_tools()], # 这里的选择是所有的工具
"description": "工具列表"
},
)

View File

@ -1,31 +1,38 @@
import os
import uuid
from typing import Any
from typing import Any, cast, Annotated
from pathlib import Path
from datetime import datetime, timezone
import sqlite3
from langchain_core.runnables import RunnableConfig
from dataclasses import dataclass, field, fields
from langchain_core.messages import AIMessage, ToolMessage
from langgraph.graph import StateGraph, START, END
from langgraph.runtime import Runtime
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver, aiosqlite
from langgraph.checkpoint.memory import InMemorySaver
from src import config as sys_config
from src.utils import logger
from src.agents.registry import State, BaseAgent
from src.agents.utils import load_chat_model, get_cur_time_with_utc
from src.agents.chatbot.configuration import ChatbotConfiguration
from src.agents.tools_factory import get_buildin_tools
from src.agents.common.utils import get_cur_time_with_utc
from src.agents.common.base import BaseAgent
from src.agents.common.models import load_chat_model
from .state import State
from .context import Context
from .tools import get_tools
class ChatbotAgent(BaseAgent):
name = "智能体助手"
description = "基础的对话机器人,可以回答问题,默认不使用任何工具,可在配置中启用需要的工具。"
config_schema = ChatbotConfiguration
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.graph = None
self.workdir = Path(sys_config.save_dir) / "agents" / self.id
self.context_schema = Context
self.workdir = Path(sys_config.save_dir) / "agents" / self.module_name
self.workdir.mkdir(parents=True, exist_ok=True)
def _get_tools(self, tools: list[str]):
@ -33,7 +40,7 @@ class ChatbotAgent(BaseAgent):
默认不使用任何工具
如果配置为列表则使用列表中的工具
"""
platform_tools = get_buildin_tools()
platform_tools = get_tools()
if tools is None or not isinstance(tools, list) or len(tools) == 0:
# 默认不使用任何工具
logger.info("未配置工具或配置为空,不使用任何工具")
@ -44,51 +51,74 @@ class ChatbotAgent(BaseAgent):
logger.info(f"使用工具: {[tool.name for tool in tools]}")
return tools
async def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]:
async def llm_call(self, state: State, runtime: Runtime[Context] = None) -> dict[str, Any]:
"""调用 llm 模型 - 异步版本以支持异步工具"""
conf = self.config_schema.from_runnable_config(config, module_name=self.module_name)
system_prompt = f"{runtime.context.system_prompt}. Current time is {get_cur_time_with_utc()}"
model = load_chat_model(runtime.context.model)
system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}"
model = load_chat_model(conf.model)
if tools := self._get_tools(conf.tools):
# 这里要根据配置动态获取工具
if tools := self._get_tools(runtime.context.tools):
model = model.bind_tools(tools)
# 使用异步调用
res = await model.ainvoke(
[{"role": "system", "content": system_prompt}, *state["messages"]]
response = cast(
AIMessage,
await model.ainvoke(
[{"role": "system", "content": system_prompt}, *state.messages]
),
)
return {"messages": [res]}
return {"messages": [response]}
async def get_graph(self, config_schema: RunnableConfig = None, **kwargs):
async def dynamic_tools_node(
self, state: State, runtime: Runtime[Context]
) -> dict[str, list[ToolMessage]]:
"""Execute tools dynamically based on configuration.
This function gets the available tools based on the current configuration
and executes the requested tool calls from the last message.
"""
# Get available tools based on configuration
available_tools = get_tools()
# Create a ToolNode with the available tools
tool_node = ToolNode(available_tools)
# Execute the tool node
result = await tool_node.ainvoke(state)
return cast(dict[str, list[ToolMessage]], result)
async def get_graph(self, **kwargs):
"""构建图"""
if self.graph:
return self.graph
runnable_tools = get_buildin_tools()
runnable_tools = get_tools()
logger.debug(f"build graph `{self.id}` with {len(runnable_tools)} tools")
workflow = StateGraph(State, config_schema=self.config_schema)
workflow.add_node("chatbot", self.llm_call)
workflow.add_node("tools", ToolNode(tools=runnable_tools))
workflow.add_edge(START, "chatbot")
workflow.add_conditional_edges(
builder = StateGraph(State, context_schema=self.context_schema)
builder.add_node("chatbot", self.llm_call)
builder.add_node("tools", self.dynamic_tools_node)
builder.add_edge(START, "chatbot")
builder.add_conditional_edges(
"chatbot",
tools_condition,
)
workflow.add_edge("tools", "chatbot")
workflow.add_edge("chatbot", END)
builder.add_edge("tools", "chatbot")
builder.add_edge("chatbot", END)
# 创建数据库连接并确保设置 checkpointer
try:
sqlite_checkpointer = AsyncSqliteSaver(await self.get_async_conn())
graph = workflow.compile(checkpointer=sqlite_checkpointer)
graph = builder.compile(checkpointer=sqlite_checkpointer, name=self.name)
self.graph = graph
return graph
except Exception as e:
logger.error(f"构建 Graph 设置 checkpointer 时出错: {e}")
logger.error(f"构建 Graph 设置 checkpointer 时出错: {e}, 尝试使用内存存储")
# 即使出错也返回一个可用的图实例,只是无法保存历史
graph = workflow.compile()
checkpointer = InMemorySaver()
graph = builder.compile(checkpointer=checkpointer, name=self.name)
self.graph = graph
return graph
@ -101,7 +131,7 @@ class ChatbotAgent(BaseAgent):
return AsyncSqliteSaver(await self.get_async_conn())
def main():
agent = ChatbotAgent(ChatbotConfiguration())
agent = ChatbotAgent(Context)
thread_id = str(uuid.uuid4())
config = {"configurable": {"thread_id": thread_id}}

View File

@ -0,0 +1,22 @@
"""Define the state structures for the agent."""
from __future__ import annotations
from dataclasses import dataclass, field
from collections.abc import Sequence
from langchain_core.messages import AnyMessage
from langgraph.graph import add_messages
from typing import Annotated
@dataclass
class State:
"""Defines the input state for the agent, representing a narrower interface to the outside world.
This class is used to define the initial state and structure of incoming data.
"""
messages: Annotated[Sequence[AnyMessage], add_messages] = field(
default_factory=list
)

View File

@ -0,0 +1,34 @@
from typing import Any
from langchain_core.tools import tool
from src.utils import logger
from src.agents.common.tools import get_buildin_tools
@tool
def calculator(a: float, b: float, operation: str) -> float:
"""Calculate two numbers. operation: add, subtract, multiply, divide"""
try:
if operation == "add":
return a + b
elif operation == "subtract":
return a - b
elif operation == "multiply":
return a * b
elif operation == "divide":
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
else:
raise ValueError(f"不支持的运算类型: {operation},仅支持 add, subtract, multiply, divide")
except Exception as e:
logger.error(f"Calculator error: {e}")
raise
def get_tools() -> dict[str, Any]:
"""获取所有可运行的工具(给大模型使用)"""
tools = get_buildin_tools()
tools.append(calculator)
return tools

102
src/agents/common/base.py Normal file
View File

@ -0,0 +1,102 @@
from __future__ import annotations
from abc import abstractmethod
from langgraph.graph.state import CompiledStateGraph
from src.utils import logger
from src.agents.common.context import BaseContext
class BaseAgent:
"""
定义一个基础 Agent 各类 graph 继承
"""
name = "base_agent"
description = "base_agent"
def __init__(self, **kwargs):
self.graph = None # will be covered by get_graph
self.context_schema = BaseContext
@property
def module_name(self) -> str:
"""Get the module name of the agent class."""
return self.__class__.__module__.split('.')[-2]
@property
def id(self) -> str:
"""Get the agent's class name."""
return self.__class__.__name__
async def get_info(self):
return {
"id": self.id,
"name": self.name if hasattr(self, "name") else "Unknown",
"description": self.description if hasattr(self, "description") else "Unknown",
"configurable_items": self.context_schema.get_configurable_items(),
"all_tools": self.all_tools if hasattr(self, "all_tools") else [],
"has_checkpointer": await self.check_checkpointer(),
}
async def get_config(self):
return self.context_schema.from_file(module_name=self.module_name)
async def stream_values(self, messages: list[str], input_context = None, **kwargs):
graph = await self.get_graph()
context = self.context_schema.from_file(module_name=self.module_name, input_context=input_context)
for event in graph.astream({"messages": messages}, stream_mode="values", context=context):
yield event["messages"]
async def stream_messages(self, messages: list[str], input_context = None, **kwargs):
graph = await self.get_graph()
logger.debug(f"stream_messages: {input_context}")
context = self.context_schema.from_file(module_name=self.module_name, input_context=input_context)
# TODO 的 Checkpointer 似乎还没有适配最新的 Context API
async for msg, metadata in graph.astream({"messages": messages}, stream_mode="messages", context=context, config={"configurable": input_context}):
yield msg, metadata
async def check_checkpointer(self):
app = await self.get_graph()
if not hasattr(app, "checkpointer") or app.checkpointer is None:
logger.warning(f"智能体 {self.name} 的 Graph 未配置 checkpointer无法获取历史记录")
return False
return True
async def get_history(self, user_id, thread_id) -> list[dict]:
"""获取历史消息"""
try:
app = await self.get_graph()
if not await self.check_checkpointer():
return []
config = {"configurable": {"thread_id": thread_id, "user_id": user_id}}
state = await app.aget_state(config)
result = []
if state:
messages = state.values.get('messages', [])
for msg in messages:
if hasattr(msg, 'model_dump'):
msg_dict = msg.model_dump() # 转换成字典
else:
msg_dict = dict(msg) if hasattr(msg, '__dict__') else {"content": str(msg)}
result.append(msg_dict)
return result
except Exception as e:
logger.error(f"获取智能体 {self.name} 历史消息出错: {e}")
return []
@abstractmethod
async def get_graph(self, **kwargs) -> CompiledStateGraph:
"""
获取并编译对话图实例
必须确保在编译时设置 checkpointer否则将无法获取历史记录
例如: graph = workflow.compile(checkpointer=sqlite_checkpointer)
"""
pass

View File

@ -0,0 +1,161 @@
"""Define the configurable parameters for the agent."""
from __future__ import annotations
import os
import yaml
import uuid
from dataclasses import dataclass, field, fields, MISSING
from pathlib import Path
from typing import get_origin, get_args
from src import config as sys_config
from src.utils import logger
@dataclass(kw_only=True)
class BaseContext:
"""
定义一个基础 Context 各类 graph 继承
配置优先级:
1. 运行时配置(RunnableConfig)最高优先级直接从函数参数传入
2. 文件配置(config.private.yaml)中等优先级从文件加载
3. 类默认配置最低优先级类中定义的默认值
"""
def update(self, data: dict):
"""更新配置字段"""
for key, value in data.items():
if hasattr(self, key):
setattr(self, key, value)
thread_id: str = field(
default_factory=lambda: str(uuid.uuid4()),
metadata={
"name": "线程ID",
"configurable": False,
"description": "用来描述智能体的角色和行为"
},
)
user_id: str = field(
default_factory=lambda: str(uuid.uuid4()),
metadata={
"name": "用户ID",
"configurable": False,
"description": "用来描述智能体的角色和行为"
},
)
system_prompt: str = field(
default="You are a helpful assistant.",
metadata={
"name": "系统提示词",
"description": "用来描述智能体的角色和行为"
},
)
@classmethod
def from_file(cls, module_name: str, input_context: dict = None) -> BaseContext:
"""Load configuration from a YAML file. 用于持久化配置"""
# 从文件加载配置
context = cls()
config_file_path = Path(sys_config.save_dir) / "agents" / module_name / "config.yaml"
if module_name is not None and os.path.exists(config_file_path):
file_config = {}
try:
with open(config_file_path, encoding='utf-8') as f:
file_config = yaml.safe_load(f) or {}
except Exception as e:
logger.error(f"加载智能体配置文件出错: {e}")
context.update(file_config)
if input_context:
context.update(input_context)
return context
@classmethod
def save_to_file(cls, config: dict, module_name: str) -> bool:
"""Save configuration to a YAML file 用于持久化配置"""
configurable_items = cls.get_configurable_items()
configurable_config = {}
for k, v in config.items():
if k in configurable_items:
configurable_config[k] = v
try:
config_file_path = Path(sys_config.save_dir) / "agents" / module_name / "config.yaml"
# 确保目录存在
os.makedirs(os.path.dirname(config_file_path), exist_ok=True)
with open(config_file_path, 'w', encoding='utf-8') as f:
yaml.dump(configurable_config, f, indent=2, allow_unicode=True)
return True
except Exception as e:
logger.error(f"保存智能体配置文件出错: {e}")
return False
@classmethod
def get_configurable_items(cls):
"""实现一个可配置的参数列表,在 UI 上配置时使用"""
configurable_items = {}
for f in fields(cls):
if f.init and not f.metadata.get("hide", False):
if f.metadata.get("configurable", True):
# 处理类型信息
field_type = f.type
type_name = cls._get_type_name(field_type)
# 提取 Annotated 的元数据
template_metadata = cls._extract_template_metadata(field_type)
configurable_items[f.name] = {
"type": type_name,
"name": f.metadata.get("name", f.name),
"options": f.metadata.get("options", []),
"default": f.default if f.default is not MISSING else f.default_factory() if f.default_factory is not MISSING else None,
"description": f.metadata.get("description", ""),
"template_metadata": template_metadata, # Annotated 的额外元数据
}
return configurable_items
@classmethod
def _get_type_name(cls, field_type) -> str:
"""获取类型名称,处理 Annotated 类型"""
# 检查是否是 Annotated 类型
if get_origin(field_type) is not None:
# 处理泛型类型如 list[str], Annotated[str, {...}]
origin = get_origin(field_type)
if hasattr(origin, '__name__'):
if origin.__name__ == 'Annotated':
# Annotated 类型,获取真实类型
args = get_args(field_type)
if args:
return cls._get_type_name(args[0]) # 递归处理真实类型
return origin.__name__
else:
return str(origin)
elif hasattr(field_type, '__name__'):
return field_type.__name__
else:
return str(field_type)
@classmethod
def _extract_template_metadata(cls, field_type) -> dict:
"""从 Annotated 类型中提取模板元数据"""
if get_origin(field_type) is not None:
origin = get_origin(field_type)
if hasattr(origin, '__name__') and origin.__name__ == 'Annotated':
args = get_args(field_type)
if len(args) > 1:
# 查找包含 __template_metadata__ 的字典
for metadata in args[1:]:
if isinstance(metadata, dict) and "__template_metadata__" in metadata:
return metadata["__template_metadata__"]
return {}

View File

@ -0,0 +1,61 @@
import os
import traceback
from src import config
from src.utils import get_docker_safe_url
from src.models import get_custom_model
from langchain_core.language_models import BaseChatModel
from pydantic import SecretStr
def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
"""
Load a chat model from a fully specified name.
"""
provider, model = fully_specified_name.split("/", maxsplit=1)
if provider == "custom":
from langchain_openai import ChatOpenAI
model_info = get_custom_model(model)
api_key = model_info.get("api_key") or "custom_model"
base_url = get_docker_safe_url(model_info["api_base"])
model_name = model_info.get("name") or "custom_model"
return ChatOpenAI(
model=model_name,
api_key=SecretStr(api_key),
base_url=base_url,
)
model_info = config.model_names.get(provider, {})
api_key = os.getenv(model_info["env"][0], model_info["env"][0])
base_url = get_docker_safe_url(model_info["base_url"])
if provider in ["deepseek", "dashscope"]:
from langchain_deepseek import ChatDeepSeek
return ChatDeepSeek(
model=model,
api_key=SecretStr(api_key),
base_url=base_url,
api_base=base_url,
)
elif provider == "together":
from langchain_together import ChatTogether
return ChatTogether(
model=model,
api_key=SecretStr(api_key),
base_url=base_url,
)
else:
try: # 其他模型默认使用OpenAIBase, like openai, zhipuai
from langchain_openai import ChatOpenAI
return ChatOpenAI(
model=model,
api_key=SecretStr(api_key),
base_url=base_url,
)
except Exception as e:
raise ValueError(f"Model provider {provider} load failed, {e} \n {traceback.format_exc()}")

View File

@ -10,6 +10,31 @@ from src import config, graph_base, knowledge_base
from src.utils import logger
@tool
def query_knowledge_graph(query: Annotated[str, "The keyword to query knowledge graph."]) -> Any:
"""Use this to query knowledge graph, which include some food domain knowledge."""
try:
logger.debug(f"Querying knowledge graph with: {query}")
result = graph_base.query_node(query, hops=2, return_format='triples')
logger.debug(f"Knowledge graph query returned {len(result.get('triples', [])) if isinstance(result, dict) else 'N/A'} triples")
return result
except Exception as e:
logger.error(f"Knowledge graph query error: {e}, {traceback.format_exc()}")
return f"知识图谱查询失败: {str(e)}"
def get_static_tools() -> dict[str, Any]:
"""注册静态工具"""
static_tools = [
query_knowledge_graph,
]
# 检查是否启用网页搜索
if config.enable_web_search:
static_tools.append(TavilySearch(max_results=10))
return static_tools
class KnowledgeRetrieverModel(BaseModel):
query_text: str = Field(
description=(
@ -19,46 +44,33 @@ class KnowledgeRetrieverModel(BaseModel):
)
def _create_retriever_wrapper(db_id: str, retriever_info: dict[str, Any]):
"""创建检索器包装函数的工厂函数,避免闭包变量捕获问题"""
async def async_retriever_wrapper(query_text: str) -> Any:
"""异步检索器包装函数"""
retriever = retriever_info["retriever"]
try:
logger.debug(f"Retrieving from database {db_id} with query: {query_text}")
if asyncio.iscoroutinefunction(retriever):
result = await retriever(query_text)
else:
result = retriever(query_text)
logger.debug(f"Retrieved {len(result) if isinstance(result, list) else 'N/A'} results from {db_id}")
return result
except Exception as e:
logger.error(f"Error in retriever {db_id}: {e}")
return f"检索失败: {str(e)}"
return async_retriever_wrapper
def get_buildin_tools() -> dict[str, Any]:
"""获取所有可运行的工具(给大模型使用)"""
tools = []
try:
# 获取所有知识库基于的工具
tools.extend(get_kb_based_tools())
tools.extend(get_static_tools())
except Exception as e:
logger.error(f"Failed to get knowledge base retrievers: {e}")
logger.info(f"Total tools available: {len(tools)}")
return tools
def get_kb_based_tools() -> dict[str, Any]:
"""获取所有知识库基于的工具"""
# 获取所有知识库
kb_tools = []
retrievers = knowledge_base.get_retrievers()
logger.debug(f"Found {len(retrievers)} knowledge base retrievers")
def _create_retriever_wrapper(db_id: str, retriever_info: dict[str, Any]):
"""创建检索器包装函数的工厂函数,避免闭包变量捕获问题"""
async def async_retriever_wrapper(query_text: str) -> Any:
"""异步检索器包装函数"""
retriever = retriever_info["retriever"]
try:
logger.debug(f"Retrieving from database {db_id} with query: {query_text}")
if asyncio.iscoroutinefunction(retriever):
result = await retriever(query_text)
else:
result = retriever(query_text)
logger.debug(f"Retrieved {len(result) if isinstance(result, list) else 'N/A'} results from {db_id}")
return result
except Exception as e:
logger.error(f"Error in retriever {db_id}: {e}")
return f"检索失败: {str(e)}"
return async_retriever_wrapper
for db_id, retrieve_info in retrievers.items():
try:
@ -94,14 +106,28 @@ def get_kb_based_tools() -> dict[str, Any]:
return kb_tools
def get_buildin_tools_info() -> dict[str, dict[str, Any]]:
def get_buildin_tools() -> dict[str, Any]:
"""获取所有可运行的工具(给大模型使用)"""
tools = []
try:
# 获取所有知识库基于的工具
tools.extend(get_kb_based_tools())
tools.extend(get_static_tools())
except Exception as e:
logger.error(f"Failed to get knowledge base retrievers: {e}")
logger.info(f"Total tools available: {len(tools)}")
return tools
def gen_tool_info(tools) -> dict[str, dict[str, Any]]:
"""获取所有工具的信息(用于前端展示)"""
tools_info = []
try:
tools = get_buildin_tools()
logger.debug(f"Processing {len(tools)} tools for info extraction")
# 获取注册的工具信息
for tool_obj in tools:
try:
@ -123,7 +149,7 @@ def get_buildin_tools_info() -> dict[str, dict[str, Any]]:
})
tools_info.append(info)
logger.debug(f"Successfully processed tool info for {tool_obj.name}")
# logger.debug(f"Successfully processed tool info for {tool_obj.name}")
except Exception as e:
logger.error(f"Failed to process tool {tool_obj.name}: {e}")
@ -136,50 +162,4 @@ def get_buildin_tools_info() -> dict[str, dict[str, Any]]:
logger.info(f"Successfully extracted info for {len(tools_info)} tools")
return tools_info
@tool
def calculator(a: float, b: float, operation: str) -> float:
"""Calculate two numbers. operation: add, subtract, multiply, divide"""
try:
if operation == "add":
return a + b
elif operation == "subtract":
return a - b
elif operation == "multiply":
return a * b
elif operation == "divide":
if b == 0:
raise ZeroDivisionError("除数不能为零")
return a / b
else:
raise ValueError(f"不支持的运算类型: {operation},仅支持 add, subtract, multiply, divide")
except Exception as e:
logger.error(f"Calculator error: {e}")
raise
@tool
def query_knowledge_graph(query: Annotated[str, "The keyword to query knowledge graph."]) -> Any:
"""Use this to query knowledge graph, which include some food domain knowledge."""
try:
logger.debug(f"Querying knowledge graph with: {query}")
result = graph_base.query_node(query, hops=2, return_format='triples')
logger.debug(f"Knowledge graph query returned {len(result.get('triples', [])) if isinstance(result, dict) else 'N/A'} triples")
return result
except Exception as e:
logger.error(f"Knowledge graph query error: {e}, {traceback.format_exc()}")
return f"知识图谱查询失败: {str(e)}"
def get_static_tools() -> dict[str, Any]:
"""注册静态工具"""
static_tools = [
calculator,
query_knowledge_graph,
]
# 检查是否启用网页搜索
if config.enable_web_search:
static_tools.append(TavilySearch(max_results=10))
return static_tools

View File

@ -4,9 +4,9 @@ import os
import traceback
from src import config
from src.utils import logger, get_docker_safe_url
from src.utils import get_docker_safe_url
from src.models import get_custom_model
from src.agents.registry import BaseAgent
from src.agents.common.base import BaseAgent
from langchain_core.language_models import BaseChatModel
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import AIMessageChunk, ToolMessage

View File

@ -1,9 +1,49 @@
from src.agents.registry import BaseAgent
import os
from pathlib import Path
from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver, aiosqlite
from langgraph.prebuilt import create_react_agent
from langchain_core.messages import AnyMessage, SystemMessage
from langgraph.runtime import get_runtime
from src import config as sys_config
from src.utils import logger
from src.agents.common.context import BaseContext
from src.agents.common.base import BaseAgent
from src.agents.common.models import load_chat_model
from src.agents.common.tools import get_buildin_tools
model = load_chat_model("siliconflow/Qwen/Qwen3-235B-A22B-Instruct-2507")
def prompt(state) -> list[AnyMessage]:
runtime = get_runtime(BaseContext)
system_msg = SystemMessage(content=runtime.context.system_prompt)
return [system_msg] + state["messages"]
class ReActAgent(BaseAgent):
name = "ReAct"
name = "ReAct (all tools)"
description = "A react agent that can answer questions and help with tasks."
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.graph = None
self.workdir = Path(sys_config.save_dir) / "agents" / self.module_name
self.workdir.mkdir(parents=True, exist_ok=True)
async def get_graph(self, **kwargs):
from .workflows import graph
if self.graph:
return self.graph
available_tools = get_buildin_tools()
sqlite_checkpointer = AsyncSqliteSaver(await aiosqlite.connect(self.workdir / "react_history.db"))
graph = create_react_agent(
model,
tools=available_tools,
checkpointer=sqlite_checkpointer,
prompt=prompt
)
self.graph = graph
logger.info("ReActAgent使用SQLite checkpointer构建成功")
return graph

View File

@ -1,13 +0,0 @@
import os
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.prebuilt import create_react_agent
model = ChatOpenAI(model="glm-4-plus",
api_key=os.getenv("ZHIPUAI_API_KEY"),
base_url="https://open.bigmodel.cn/api/paas/v4/",
temperature=0)
tools = []
graph = create_react_agent(model, tools=tools, checkpointer=InMemorySaver())

View File

@ -1,249 +0,0 @@
from __future__ import annotations
import os
import yaml
import uuid
from pathlib import Path
from typing import Annotated, TypedDict, Optional, Any
from abc import abstractmethod
from dataclasses import dataclass, fields, field
from pydantic import BaseModel, Field
from langchain_core.runnables import RunnableConfig
from langchain_core.messages import BaseMessage
from langgraph.graph.state import CompiledStateGraph
from langgraph.graph.message import add_messages
from src.utils import logger
class State(TypedDict):
messages: Annotated[list[BaseMessage], add_messages]
@dataclass(kw_only=True)
class Configuration(dict):
"""
定义一个基础 Configuration 各类 graph 继承
配置优先级:
1. 运行时配置(RunnableConfig)最高优先级直接从函数参数传入
2. 文件配置(config.private.yaml)中等优先级从文件加载
3. 类默认配置最低优先级类中定义的默认值
"""
thread_id: str = field(
default_factory=lambda: str(uuid.uuid4()),
metadata={
"name": "线程ID",
"configurable": False,
"description": "用来描述智能体的角色和行为"
},
)
user_id: str = field(
default_factory=lambda: str(uuid.uuid4()),
metadata={
"name": "用户ID",
"configurable": False,
"description": "用来描述智能体的角色和行为"
},
)
@classmethod
def from_runnable_config(
cls, config: RunnableConfig | None = None, module_name: str | None = None
) -> Configuration:
"""Create a Configuration instance from a RunnableConfig object.
Args:
config: RunnableConfig object with highest priority
module_name: Name of the agent to load config file for
Returns:
Configuration instance with merged config values
"""
# 获取类默认配置:创建一个实例获取所有默认值
instance = cls()
_fields = {f.name for f in fields(cls) if f.init}
# 尝试加载文件配置(中等优先级)
file_config = {}
if module_name:
file_config = cls.from_file(module_name)
# 获取运行时配置(最高优先级)
configurable = (config.get("configurable") or {}) if config else {}
# 合并三级配置,注意优先级
merged_config = {}
for config_field in _fields:
# 1. 默认使用类默认值
if hasattr(instance, config_field):
merged_config[config_field] = getattr(instance, config_field)
# 2. 如果文件配置中有此字段,则覆盖
if config_field in file_config:
merged_config[config_field] = file_config[config_field]
# 3. 如果运行时配置中有此字段,则覆盖
if config_field in configurable:
merged_config[config_field] = configurable[config_field]
# 创建并返回配置实例
# logger.debug(f"合并配置: {merged_config}")
return cls(**merged_config)
@classmethod
def from_file(cls, module_name: str) -> Configuration:
"""从文件加载配置"""
config_file_path = Path(f"src/agents/{module_name}/config.private.yaml")
file_config = {}
if os.path.exists(config_file_path):
try:
with open(config_file_path, encoding='utf-8') as f:
file_config = yaml.safe_load(f) or {}
# logger.info(f"从文件加载智能体 {module_name} 配置: {file_config}")
except Exception as e:
logger.error(f"加载智能体配置文件出错: {e}")
return file_config
@classmethod
def save_to_file(cls, config: dict, module_name: str) -> bool:
"""Save configuration to a YAML file
Args:
config: Configuration dictionary to save
module_name: Name of the agent to save config for
Returns:
True if saving was successful, False otherwise
"""
try:
config_file_path = Path(f"src/agents/{module_name}/config.private.yaml")
# 确保目录存在
os.makedirs(os.path.dirname(config_file_path), exist_ok=True)
with open(config_file_path, 'w', encoding='utf-8') as f:
yaml.dump(config, f, indent=2, allow_unicode=True)
# logger.info(f"智能体 {module_name} 配置已保存到 {config_file_path}")
return True
except Exception as e:
logger.error(f"保存智能体配置文件出错: {e}")
return False
@classmethod
def to_dict(cls):
# 创建一个实例来处理 default_factory
instance = cls()
confs = {}
configurable_items = {}
for f in fields(cls):
if f.init and not f.metadata.get("hide", False):
value = getattr(instance, f.name)
if callable(value) and hasattr(value, "__call__"):
confs[f.name] = value()
else:
confs[f.name] = value
if f.metadata.get("configurable", True):
configurable_items[f.name] = {
"type": f.type.__name__,
"name": f.metadata.get("name", f.name),
"options": f.metadata.get("options", []),
"default": f.default,
"description": f.metadata.get("description", ""),
}
confs["configurable_items"] = configurable_items
return confs
class BaseAgent:
"""
定义一个基础 Agent 各类 graph 继承
"""
name = "base_agent"
description = "base_agent"
config_schema: Configuration = Configuration
def __init__(self, **kwargs):
pass
@property
def module_name(self) -> str:
"""Get the module name of the agent class."""
return self.__class__.__module__.split('.')[-2]
@property
def id(self) -> str:
"""Get the agent's class name."""
return self.__class__.__name__
async def get_info(self):
return {
"id": self.id,
"name": self.name if hasattr(self, "name") else "Unknown",
"description": self.description if hasattr(self, "description") else "Unknown",
"config_schema": self.config_schema.to_dict(),
"all_tools": self.all_tools if hasattr(self, "all_tools") else [],
"has_checkpointer": await self.check_checkpointer(),
}
async def stream_values(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
graph = await self.get_graph()
logger.debug(f"stream_values: {config_schema}")
for event in graph.astream({"messages": messages}, stream_mode="values", config=config_schema):
yield event["messages"]
async def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
graph = await self.get_graph()
logger.debug(f"stream_messages: {config_schema}")
async for msg, metadata in graph.astream({"messages": messages}, stream_mode="messages", config=config_schema):
yield msg, metadata
async def check_checkpointer(self):
app = await self.get_graph()
if not hasattr(app, "checkpointer") or app.checkpointer is None:
logger.warning(f"智能体 {self.name} 的 Graph 未配置 checkpointer无法获取历史记录")
return False
return True
async def get_history(self, user_id, thread_id) -> list[dict]:
"""获取历史消息"""
try:
app = await self.get_graph()
if not await self.check_checkpointer():
return []
config = {"configurable": {"thread_id": thread_id, "user_id": user_id}}
state = await app.aget_state(config)
result = []
if state:
messages = state.values.get('messages', [])
for msg in messages:
if hasattr(msg, 'model_dump'):
msg_dict = msg.model_dump() # 转换成字典
else:
msg_dict = dict(msg) if hasattr(msg, '__dict__') else {"content": str(msg)}
result.append(msg_dict)
return result
except Exception as e:
logger.error(f"获取智能体 {self.name} 历史消息出错: {e}")
return []
@abstractmethod
async def get_graph(self, **kwargs) -> CompiledStateGraph:
"""
获取并编译对话图实例
必须确保在编译时设置 checkpointer否则将无法获取历史记录
例如: graph = workflow.compile(checkpointer=sqlite_checkpointer)
"""
pass

View File

@ -28,7 +28,7 @@ class KnowledgeBaseFactory:
cls._kb_types[kb_type] = kb_class
cls._default_configs[kb_type] = default_config or {}
logger.info(f"Registered knowledge base type: {kb_type}")
# logger.info(f"Registered knowledge base type: {kb_type}")
@classmethod
def create(cls, kb_type: str, work_dir: str, **kwargs) -> KnowledgeBase:

View File

@ -112,7 +112,7 @@ export const agentApi = {
* 获取所有可用工具的信息
* @returns {Promise} - 工具信息列表
*/
getTools: () => apiGet('/api/tool/tools')
getTools: (agentId) => apiGet(`/api/chat/tools?agent_id=${agentId}`)
}
@ -127,7 +127,7 @@ export const threadApi = {
* @returns {Promise} - 对话线程列表
*/
getThreads: (agentId) => {
const url = agentId ? `/api/chat/threads?agent_id=${agentId}` : '/api/chat/threads';
const url = `/api/chat/threads?agent_id=${agentId}`;
return apiGet(url);
},

View File

@ -7,8 +7,7 @@
export * from './system_api' // 系统管理API
export * from './knowledge_api' // 知识库管理API
export * from './graph_api' // 图谱API
export * from './tools.js' // 工具API
export * from './agent.js' // 智能体API
export * from './agent_api' // 智能体API
// 导出基础工具函数
export { apiGet, apiPost, apiPut, apiDelete,

View File

@ -68,7 +68,7 @@ export const configApi = {
// === 信息管理分组 ===
// =============================================================================
export const brandAPi = {
export const brandApi = {
/**
* 获取系统信息配置公开接口
* @returns {Promise} - 系统信息配置

View File

@ -30,7 +30,7 @@
<a-divider />
<div v-if="selectedAgentId && configSchema" class="config-form-content">
<div v-if="selectedAgentId && configurableItems" class="config-form-content">
<!-- 配置表单 -->
<a-form :model="agentConfig" layout="vertical" class="config-form">
<a-alert
@ -57,8 +57,9 @@
>
<p v-if="value.description" class="config-description">{{ value.description }}</p>
<!-- <div>{{ value }}</div> -->
<!-- 模型选择 -->
<div v-if="key === 'model'" class="model-selector">
<div v-if="value.template_metadata.kind === 'llm'" class="model-selector">
<ModelSelectorComponent
@select-model="handleModelChange"
:model_name="agentConfig[key] ? agentConfig[key].split('/').slice(1).join('/') : ''"
@ -77,7 +78,7 @@
/>
<!-- 工具选择 -->
<div v-else-if="key === 'tools'" class="tools-selector">
<div v-else-if="value.template_metadata.kind === 'tools'" class="tools-selector">
<div class="tools-summary">
<div class="tools-summary-info">
<span class="tools-count">已选择 {{ getSelectedCount(key) }} 个工具</span>
@ -307,7 +308,8 @@ const {
availableTools,
selectedAgent,
selectedAgentId,
agentConfig
agentConfig,
configurableItems
} = storeToRefs(agentStore);
// console.log(availableTools.value)
@ -318,21 +320,6 @@ const toolsModalOpen = ref(false);
const selectedTools = ref([]);
const toolsSearchText = ref('');
//
const configSchema = computed(() => selectedAgent.value?.config_schema || {});
const configurableItems = computed(() => {
const items = configSchema.value.configurable_items || {};
// x_oap_ui_config
Object.keys(items).forEach(key => {
const item = items[key];
if (item.x_oap_ui_config) {
items[key] = { ...item, ...item.x_oap_ui_config };
delete items[key].x_oap_ui_config;
}
});
return items;
});
const isEmptyConfig = computed(() => {
return !selectedAgentId.value || Object.keys(configurableItems.value).length === 0;

View File

@ -116,7 +116,6 @@ import {
} from '@ant-design/icons-vue';
import dayjs from 'dayjs';
import { configApi } from '@/apis/system_api';
import { agentApi } from '@/apis/agent';
import { checkAdminPermission } from '@/stores/user';
const configStore = useConfigStore()
@ -402,7 +401,6 @@ const printAgentConfig = async () => {
console.log('当前选中智能体:', {
agent: toRaw(agentStore.selectedAgent),
isDefault: agentStore.isDefaultAgent,
configSchema: toRaw(agentStore.configSchema),
configurableItems: Object.keys(agentStore.configurableItems).length
});
@ -419,7 +417,7 @@ const printAgentConfig = async () => {
// 线
console.log('线程信息:', {
currentAgentThreads: agentStore.currentAgentThreads.length,
currentAgentThreads: agentStore.currentAgentThreads,
currentThread: agentStore.currentThread ? toRaw(agentStore.currentThread) : null,
currentThreadMessages: agentStore.currentThreadMessages.length
});

View File

@ -312,11 +312,11 @@ const selectedEdgeData = computed(() => graphStore.selectedEdgeData)
// -
const intelligentEdgePanelPosition = computed(() => {
if (!sigmaContainer.value) return edgePanelPosition.value
const containerHeight = sigmaContainer.value.clientHeight
const panelHeight = 200 //
const nodePanelBottom = selectedNodeData.value ? nodePanelPosition.value.y + 200 : 0
//
if (selectedNodeData.value) {
return {
@ -324,7 +324,7 @@ const intelligentEdgePanelPosition = computed(() => {
y: Math.min(nodePanelPosition.value.y, containerHeight - panelHeight - 20)
}
}
//
return {
x: edgePanelPosition.value.x,
@ -551,10 +551,10 @@ const registerEvents = () => {
// Sigma
const sigmaEdgeData = graph.getEdgeAttributes(edge)
console.log('Sigma边属性:', sigmaEdgeData)
// - 使SigmaID
graphStore.setSelectedEdge(edge)
//
nextTick(() => {
if (selectedEdgeData.value) {

View File

@ -202,7 +202,7 @@ import {
} from '@ant-design/icons-vue';
import { useConfigStore } from '@/stores/config';
import { modelIcons } from '@/utils/modelIcon';
import { agentApi } from '@/apis/agent';
import { agentApi } from '@/apis/agent_api';
const configStore = useConfigStore();

View File

@ -1,5 +1,5 @@
import { defineStore } from 'pinia';
import { agentApi, threadApi } from '@/apis/agent';
import { agentApi, threadApi } from '@/apis/agent_api';
import { MessageProcessor } from '@/utils/messageProcessor';
import { handleChatError } from '@/utils/errorHandler';
@ -46,14 +46,11 @@ export const useAgentStore = defineStore('agent', {
defaultAgent: (state) => state.defaultAgentId ? state.agents[state.defaultAgentId] : state.agents[Object.keys(state.agents)[0]],
agentsList: (state) => Object.values(state.agents),
isDefaultAgent: (state) => state.selectedAgentId === state.defaultAgentId,
configSchema: (state) => {
const agent = state.selectedAgentId ? state.agents[state.selectedAgentId] : null;
return agent?.config_schema || {};
},
configurableItems: (state) => {
const schema = state.configSchema || {};
if (!schema || !schema.configurable_items) return {};
const items = { ...schema.configurable_items };
const agent = state.selectedAgentId ? state.agents[state.selectedAgentId] : null;
const agentConfigurableItems = agent.configurable_items || {};
if (!agentConfigurableItems) return {};
const items = { ...agentConfigurableItems };
Object.keys(items).forEach(key => {
const item = items[key];
if (item && item.x_oap_ui_config) {
@ -156,7 +153,7 @@ export const useAgentStore = defineStore('agent', {
// 设置默认智能体
async setDefaultAgent(agentId) {
try {
await agentConfigApi.setDefaultAgent(agentId);
await agentApi.setDefaultAgent(agentId);
this.defaultAgentId = agentId;
} catch (error) {
console.error('Failed to set default agent:', error);
@ -427,7 +424,7 @@ export const useAgentStore = defineStore('agent', {
try {
const response = await agentApi.sendAgentMessage(this.selectedAgentId, requestData);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

View File

@ -1,6 +1,6 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { brandAPi } from '@/apis/system_api'
import { brandApi } from '@/apis/system_api'
export const useInfoStore = defineStore('info', () => {
// 状态
@ -49,7 +49,7 @@ export const useInfoStore = defineStore('info', () => {
try {
isLoading.value = true
const response = await brandAPi.getInfoConfig()
const response = await brandApi.getInfoConfig()
if (response.success && response.data) {
setInfoConfig(response.data)
@ -70,7 +70,7 @@ export const useInfoStore = defineStore('info', () => {
async function reloadInfoConfig() {
try {
isLoading.value = true
const response = await brandAPi.reloadInfoConfig()
const response = await brandApi.reloadInfoConfig()
if (response.success && response.data) {
setInfoConfig(response.data)