超级大更新
This commit is contained in:
parent
9451b01ae9
commit
ff61e4cb48
@ -1,62 +1,36 @@
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from langchain_community.tools.tavily_search import TavilySearchResults
|
||||
|
||||
from src.agents.registry import Configuration
|
||||
from src.agents.tools_factory import multiply, add, subtract, divide
|
||||
|
||||
|
||||
|
||||
def get_default_tools():
|
||||
return ["TavilySearchResults", "multiply", "add", "subtract", "divide"]
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ChatbotConfiguration(Configuration):
|
||||
"""Chatbot 的配置"""
|
||||
"""Chatbot 的配置
|
||||
|
||||
配置说明:
|
||||
|
||||
metadata 中 configurable 为 True 的配置项可以被用户配置,
|
||||
configurable 为 False 的配置项不能被用户配置,只能由开发者预设。
|
||||
"""
|
||||
|
||||
system_prompt: str = field(
|
||||
default=f"You are a helpful assistant. Now is {datetime.now(tz=timezone.utc).isoformat()}",
|
||||
default="You are a helpful assistant.",
|
||||
metadata={
|
||||
"description": "The system prompt to use for the agent's interactions. "
|
||||
"This prompt sets the context and behavior for the agent."
|
||||
"name": "系统提示词",
|
||||
"configurable": True,
|
||||
"description": "用来描述智能体的角色和行为"
|
||||
},
|
||||
)
|
||||
|
||||
model: str = field(
|
||||
default="zhipu/glm-4-plus",
|
||||
metadata={
|
||||
"description": "The name of the language model to use for the agent's main interactions. "
|
||||
"Should be in the form: provider/model-name."
|
||||
"name": "智能体模型",
|
||||
"configurable": True,
|
||||
"options": [
|
||||
"zhipu/glm-4-plus",
|
||||
"siliconflow/Qwen/QwQ-32B",
|
||||
"siliconflow/deepseek-ai/DeepSeek-V3",
|
||||
],
|
||||
"description": "智能体的驱动模型"
|
||||
},
|
||||
)
|
||||
|
||||
tools: list = field(
|
||||
default_factory=get_default_tools,
|
||||
metadata={
|
||||
"description": "The tools to use for the agent's interactions. "
|
||||
"Should be in the form: provider/model-name."
|
||||
},
|
||||
)
|
||||
|
||||
temperature: float = field(
|
||||
default=0.7,
|
||||
metadata={
|
||||
"description": "控制模型生成结果的随机性,值越大随机性越高,建议范围 0.0-1.0"
|
||||
},
|
||||
)
|
||||
|
||||
use_tools: bool = field(
|
||||
default=True,
|
||||
metadata={
|
||||
"description": "是否启用工具调用功能"
|
||||
},
|
||||
)
|
||||
|
||||
max_iterations: int = field(
|
||||
default=10,
|
||||
metadata={
|
||||
"description": "智能体最大执行步数,防止无限循环"
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
from typing import Any
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
@ -9,8 +9,9 @@ from langgraph.prebuilt import ToolNode, tools_condition
|
||||
from langgraph.checkpoint.memory import MemorySaver # 实际上没有起作用
|
||||
|
||||
|
||||
from src.utils import logger
|
||||
from src.agents.registry import State, BaseAgent
|
||||
from src.agents.utils import load_chat_model
|
||||
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 _TOOLS_REGISTRY
|
||||
|
||||
@ -18,27 +19,38 @@ class ChatbotAgent(BaseAgent):
|
||||
name = "chatbot"
|
||||
description = "A chatbot that can answer questions and help with tasks."
|
||||
requirements = ["TAVILY_API_KEY", "ZHIPUAI_API_KEY"]
|
||||
_graph_cache = None
|
||||
all_tools = ["TavilySearchResults", "multiply", "add", "subtract", "divide"]
|
||||
config_schema = ChatbotConfiguration
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def _get_tools(self, config_schema: RunnableConfig):
|
||||
"""根据配置获取工具"""
|
||||
default_tools_names = config_schema.get("tools", [])
|
||||
default_tools = [_TOOLS_REGISTRY[tool] for tool in default_tools_names]
|
||||
return default_tools
|
||||
"""根据配置获取工具,如果配置为空,则使用所有工具,如果配置为列表,则使用列表中的工具,
|
||||
如果配置为其他类型,则抛出错误"""
|
||||
conf_tools = config_schema.get("tools")
|
||||
if conf_tools == None:
|
||||
tool_names = self.all_tools
|
||||
elif isinstance(conf_tools, list):
|
||||
tool_names = [tool for tool in self.all_tools if tool in conf_tools]
|
||||
else:
|
||||
raise ValueError(f"tools 配置错误: {conf_tools}")
|
||||
|
||||
logger.info(f"Tools: {tool_names}")
|
||||
return [_TOOLS_REGISTRY[tool] for tool in tool_names]
|
||||
|
||||
def llm_call(self, state: State, config: RunnableConfig = None) -> dict[str, Any]:
|
||||
"""调用 llm 模型"""
|
||||
config_schema = config or {}
|
||||
conf = self.config_schema.from_runnable_config(config_schema)
|
||||
model = load_chat_model(conf.model, temperature=conf.temperature)
|
||||
|
||||
system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}"
|
||||
model = load_chat_model(conf.model)
|
||||
model_with_tools = model.bind_tools(self._get_tools(config_schema))
|
||||
logger.info(f"llm_call with config: {conf}, {conf.model}")
|
||||
|
||||
res = model_with_tools.invoke(
|
||||
[{"role": "system", "content": conf.system_prompt}, *state["messages"]]
|
||||
[{"role": "system", "content": system_prompt}, *state["messages"]]
|
||||
)
|
||||
return {"messages": [res]}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ from langgraph.graph.state import CompiledStateGraph
|
||||
from langgraph.graph.message import add_messages
|
||||
|
||||
from src.config import SimpleConfig
|
||||
|
||||
from src.utils import logger
|
||||
|
||||
class State(TypedDict):
|
||||
"""
|
||||
@ -26,7 +26,7 @@ class State(TypedDict):
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Configuration(SimpleConfig):
|
||||
class Configuration(dict):
|
||||
"""
|
||||
定义一个基础 Configuration 供 各类 graph 继承
|
||||
"""
|
||||
@ -44,7 +44,26 @@ class Configuration(SimpleConfig):
|
||||
def to_dict(cls):
|
||||
# 创建一个实例来处理 default_factory
|
||||
instance = cls()
|
||||
return {f.name: getattr(instance, f.name) for f in fields(cls) if f.init}
|
||||
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"):
|
||||
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
|
||||
|
||||
|
||||
|
||||
@ -69,6 +88,7 @@ class BaseAgent():
|
||||
"description": cls.description,
|
||||
"config_schema": cls.config_schema.to_dict(),
|
||||
"requirements": cls.requirements if hasattr(cls, "requirements") else [],
|
||||
"all_tools": cls.all_tools if hasattr(cls, "all_tools") else [],
|
||||
}
|
||||
|
||||
def check_requirements(self):
|
||||
@ -85,13 +105,8 @@ class BaseAgent():
|
||||
|
||||
def stream_messages(self, messages: list[str], config_schema: RunnableConfig = None, **kwargs):
|
||||
graph = self.get_graph(config_schema=config_schema, **kwargs)
|
||||
conf = self.config_schema.from_runnable_config(config_schema)
|
||||
|
||||
for msg, metadata in graph.stream({"messages": messages}, stream_mode="messages", config=config_schema):
|
||||
# msg_type = msg.type
|
||||
# return_keys = conf.get("return_keys", [])
|
||||
# if not return_keys or msg_type in return_keys:
|
||||
# yield msg, metadata
|
||||
yield msg, metadata
|
||||
|
||||
@abstractmethod
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from src.models import select_model
|
||||
from src.agents.registry import BaseAgent
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
@ -17,12 +19,6 @@ def load_chat_model(fully_specified_name: str, **kwargs) -> BaseChatModel:
|
||||
provider, model = fully_specified_name.split("/", maxsplit=1)
|
||||
model_instance = select_model(model_name=model, model_provider=provider)
|
||||
|
||||
# 配置额外参数,如temperature
|
||||
if kwargs and hasattr(model_instance, 'chat_open_ai'):
|
||||
for key, value in kwargs.items():
|
||||
if value is not None:
|
||||
setattr(model_instance.chat_open_ai, key, value)
|
||||
|
||||
return model_instance.chat_open_ai
|
||||
|
||||
|
||||
@ -58,3 +54,7 @@ def agent_cli(agent: BaseAgent, config: RunnableConfig = None):
|
||||
|
||||
if isinstance(msg, ToolMessage):
|
||||
print(f"Tool: {msg.content}")
|
||||
|
||||
def get_cur_time_with_utc():
|
||||
return datetime.now(tz=timezone.utc).isoformat()
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ class SimpleConfig(dict):
|
||||
return self.get(self.__key(key))
|
||||
|
||||
def __getitem__(self, key):
|
||||
return super().get(self.__key(key))
|
||||
return self.get(self.__key(key))
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
return super().__setitem__(self.__key(key), value)
|
||||
|
||||
@ -131,20 +131,22 @@ async def get_agent():
|
||||
def chat_agent(agent_name: str,
|
||||
query: str = Body(...),
|
||||
history: list = Body(...),
|
||||
config: dict = Body({})):
|
||||
config: dict = Body({}),
|
||||
meta: dict = Body({})):
|
||||
|
||||
meta.update({
|
||||
"query": query,
|
||||
"agent_name": agent_name,
|
||||
"server_model_name": config["model"] ,
|
||||
"thread_id": config.get("thread_id"),
|
||||
})
|
||||
|
||||
# 将meta和thread_id整合到config中
|
||||
def make_chunk(content=None, **kwargs):
|
||||
chat_metadata = {
|
||||
"agent_name": agent_name,
|
||||
"thread_id": config.get("thread_id"),
|
||||
}
|
||||
if update_metadata := kwargs.get("chat_metadata"):
|
||||
chat_metadata.update(update_metadata)
|
||||
|
||||
return json.dumps({
|
||||
"request_id": meta.get("request_id"),
|
||||
"response": content,
|
||||
"chat_metadata": chat_metadata,
|
||||
**kwargs
|
||||
}, ensure_ascii=False).encode('utf-8') + b"\n"
|
||||
|
||||
@ -186,7 +188,9 @@ def chat_agent(agent_name: str,
|
||||
metadata=metadata,
|
||||
status="loading")
|
||||
|
||||
yield make_chunk(status="finished", history=history_manager.update_ai(content))
|
||||
yield make_chunk(status="finished",
|
||||
history=history_manager.update_ai(content),
|
||||
meta=meta)
|
||||
|
||||
return StreamingResponse(stream_messages(), media_type='application/json')
|
||||
|
||||
|
||||
@ -26,15 +26,16 @@
|
||||
<p>{{ currentAgent ? currentAgent.description : '不同的智能体有不同的专长和能力' }}</p>
|
||||
</div>
|
||||
|
||||
<div class="chat-box" ref="messagesContainer" :class="{ 'is-debug': config.debug_mode }">
|
||||
<div class="chat-box" ref="messagesContainer" :class="{ 'is-debug': state.debug_mode }">
|
||||
<MessageComponent
|
||||
v-for="(message, index) in messages"
|
||||
:message="message"
|
||||
:key="index"
|
||||
:is-processing="isProcessing"
|
||||
@retry="retryMessage(index)"
|
||||
:show-refs="showMsgRefs(message)"
|
||||
@retry="retryMessage(message)"
|
||||
>
|
||||
<div v-if="config.debug_mode" class="status-info">{{ message }}</div>
|
||||
<div v-if="state.debug_mode" class="status-info">{{ message }}</div>
|
||||
|
||||
<!-- 工具调用 -->
|
||||
<template #tool-calls>
|
||||
@ -66,7 +67,7 @@
|
||||
</div>
|
||||
<div class="tool-params" v-if="toolCall.toolResultMsg && toolCall.toolResultMsg.content">
|
||||
<div class="tool-params-header">
|
||||
执行结果:
|
||||
执行结果
|
||||
</div>
|
||||
<div class="tool-params-content">{{ toolCall.toolResultMsg.content }}</div>
|
||||
</div>
|
||||
@ -118,14 +119,23 @@ const props = defineProps({
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
state: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== 状态管理 ====================
|
||||
|
||||
// UI状态
|
||||
const state = reactive({});
|
||||
|
||||
const state = ref(props.state);
|
||||
const showMsgRefs = (msg) => {
|
||||
if (msg.isLast) {
|
||||
return ['copy', 'regenerate']
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DOM引用
|
||||
const messagesContainer = ref(null);
|
||||
@ -251,14 +261,6 @@ const prepareMessageHistory = (msgs) => {
|
||||
|
||||
// ==================== 用户交互处理 ====================
|
||||
|
||||
// 选择智能体
|
||||
const selectAgent = (agentName) => {
|
||||
currentAgent.value = agents.value[agentName];
|
||||
messages.value = [];
|
||||
resetStatusSteps();
|
||||
saveState();
|
||||
};
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
@ -287,16 +289,17 @@ const sendMessage = () => {
|
||||
};
|
||||
|
||||
// 重试消息
|
||||
const retryMessage = (index) => {
|
||||
// 获取用户消息
|
||||
const userMessage = messages.value[index - 1]?.content;
|
||||
if (!userMessage) return;
|
||||
const retryMessage = (message) => {
|
||||
// 获取用户消息的request_id
|
||||
const requestId = message.request_id;
|
||||
const sendMessage = messages.value.find(msg => msg.id === requestId);
|
||||
const sendMessageIndex = messages.value.indexOf(sendMessage);
|
||||
|
||||
// 删除错误消息和对应的用户消息
|
||||
messages.value = messages.value.slice(0, index - 1);
|
||||
// 删除包含 request_id 之后的所有消息
|
||||
messages.value = messages.value.slice(0, sendMessageIndex);
|
||||
|
||||
// 重新发送消息
|
||||
sendMessageWithText(userMessage);
|
||||
sendMessageWithText(sendMessage.content);
|
||||
};
|
||||
|
||||
// ==================== 核心消息处理 ====================
|
||||
@ -309,11 +312,13 @@ const sendMessageWithText = async (text) => {
|
||||
resetStatusSteps();
|
||||
|
||||
const userMessage = text.trim();
|
||||
const requestId = currentAgent.value.name + '-' + new Date().getTime();
|
||||
|
||||
// 添加用户消息
|
||||
messages.value.push({
|
||||
role: 'user',
|
||||
content: userMessage
|
||||
content: userMessage,
|
||||
id: requestId
|
||||
});
|
||||
|
||||
isProcessing.value = true;
|
||||
@ -335,6 +340,9 @@ const sendMessageWithText = async (text) => {
|
||||
history: history.slice(0, -1), // 去掉最后一条刚添加的用户消息
|
||||
config: {
|
||||
...props.config
|
||||
},
|
||||
meta: {
|
||||
request_id: requestId
|
||||
}
|
||||
};
|
||||
|
||||
@ -373,16 +381,16 @@ const sendMessageWithText = async (text) => {
|
||||
const handleStreamResponse = async (response) => {
|
||||
const reader = response.body.getReader();
|
||||
// 创建一个初始的助手消息
|
||||
const assistantMsg = {
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
status: 'loading',
|
||||
toolCalls: {},
|
||||
toolCallIds: {}
|
||||
};
|
||||
// const assistantMsg = {
|
||||
// role: 'assistant',
|
||||
// content: '',
|
||||
// status: 'init',
|
||||
// toolCalls: {},
|
||||
// toolCallIds: {}
|
||||
// };
|
||||
|
||||
// 添加到消息列表
|
||||
messages.value.push(assistantMsg);
|
||||
// // 添加到消息列表
|
||||
// messages.value.push(assistantMsg);
|
||||
await scrollToBottom();
|
||||
|
||||
while (true) {
|
||||
@ -395,6 +403,9 @@ const handleStreamResponse = async (response) => {
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const data = JSON.parse(line);
|
||||
if (data.debug_mode) {
|
||||
console.log("debug_mode", data);
|
||||
}
|
||||
|
||||
// 处理元数据
|
||||
handleMetadata(data);
|
||||
@ -416,9 +427,9 @@ const handleStreamResponse = async (response) => {
|
||||
};
|
||||
|
||||
// 处理完成状态
|
||||
const handleFinished = async () => {
|
||||
const handleFinished = async (data) => {
|
||||
// 更新最后一条助手消息的状态
|
||||
const lastAssistantMsg = messages.value.find(m => m.role === 'assistant' && m.status === 'loading' || m.status === 'processing');
|
||||
const lastAssistantMsg = messages.value[messages.value.length - 1];
|
||||
if (lastAssistantMsg) {
|
||||
// 如果既没有内容也没有工具调用,添加一个完成提示
|
||||
if ((!lastAssistantMsg.content || lastAssistantMsg.content.trim().length === 0) &&
|
||||
@ -427,6 +438,8 @@ const handleFinished = async () => {
|
||||
}
|
||||
// 更新状态为已完成
|
||||
lastAssistantMsg.status = 'finished';
|
||||
lastAssistantMsg.isLast = true;
|
||||
lastAssistantMsg.meta = data.meta;
|
||||
}
|
||||
|
||||
// 标记处理完成
|
||||
@ -474,6 +487,7 @@ const createAssistantMessage = async (data) => {
|
||||
const msgContent = data.response || '';
|
||||
const runId = data.metadata?.run_id;
|
||||
const step = data.metadata?.langgraph_step;
|
||||
const requestId = data.metadata?.request_id || data.request_id;
|
||||
|
||||
// 创建新消息
|
||||
const newMsg = {
|
||||
@ -484,7 +498,8 @@ const createAssistantMessage = async (data) => {
|
||||
step: step,
|
||||
status: 'processing',
|
||||
toolCalls: {},
|
||||
toolCallIds: {}
|
||||
toolCallIds: {},
|
||||
request_id: requestId
|
||||
};
|
||||
|
||||
// 处理工具调用
|
||||
@ -514,8 +529,8 @@ const updateExistingMessage = async (data, existingMsgIndex) => {
|
||||
// console.log("updateExistingMessage", msgInstance);
|
||||
|
||||
// 如果消息状态是loading,更新为processing
|
||||
if (msgInstance.status === 'loading') {
|
||||
msgInstance.status = 'loading';
|
||||
if (msgInstance.status === 'init') {
|
||||
msgInstance.status = data.status || 'loading';
|
||||
msgInstance.run_id = data.metadata?.run_id;
|
||||
msgInstance.step = data.metadata?.langgraph_step;
|
||||
}
|
||||
@ -554,9 +569,6 @@ const updateExistingMessage = async (data, existingMsgIndex) => {
|
||||
await scrollToBottom();
|
||||
};
|
||||
|
||||
const handleAssistantClick = (message) => {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
const appendToolMessageToExistingAssistant = async (data) => {
|
||||
// console.log("appendToolMessageToExistingAssistant", data);
|
||||
@ -666,6 +678,42 @@ const toggleToolCall = (toolCallId) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 加载智能体数据的方法
|
||||
const loadAgentData = async () => {
|
||||
try {
|
||||
// 确保智能体列表已加载
|
||||
if (Object.keys(agents.value).length === 0) {
|
||||
await fetchAgents();
|
||||
}
|
||||
|
||||
// 设置当前智能体
|
||||
if (props.agentId && agents.value && agents.value[props.agentId]) {
|
||||
// 如果传入了指定的agentId,就加载对应的智能体
|
||||
currentAgent.value = agents.value[props.agentId];
|
||||
console.log("设置当前智能体", currentAgent.value.name);
|
||||
} else if (!props.agentId) {
|
||||
// 多智能体模式下,尝试从本地存储恢复上次选择的智能体
|
||||
const storagePrefix = 'agent-multi';
|
||||
const savedAgent = localStorage.getItem(`${storagePrefix}-current-agent`);
|
||||
if (savedAgent && agents.value && agents.value[savedAgent]) {
|
||||
currentAgent.value = agents.value[savedAgent];
|
||||
console.log("从存储中恢复智能体", currentAgent.value.name);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载保存的状态
|
||||
loadState();
|
||||
|
||||
// 处理消息历史
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
console.log("处理消息历史:", messages.value.length);
|
||||
messages.value = prepareMessageHistory(messages.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载智能体数据出错:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 从localStorage加载状态
|
||||
const loadState = () => {
|
||||
try {
|
||||
@ -729,42 +777,6 @@ watch(() => props.agentId, async (newAgentId, oldAgentId) => {
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 加载智能体数据的方法
|
||||
const loadAgentData = async () => {
|
||||
try {
|
||||
// 确保智能体列表已加载
|
||||
if (Object.keys(agents.value).length === 0) {
|
||||
await fetchAgents();
|
||||
}
|
||||
|
||||
// 设置当前智能体
|
||||
if (props.agentId && agents.value && agents.value[props.agentId]) {
|
||||
// 如果传入了指定的agentId,就加载对应的智能体
|
||||
currentAgent.value = agents.value[props.agentId];
|
||||
console.log("设置当前智能体", currentAgent.value.name);
|
||||
} else if (!props.agentId) {
|
||||
// 多智能体模式下,尝试从本地存储恢复上次选择的智能体
|
||||
const storagePrefix = 'agent-multi';
|
||||
const savedAgent = localStorage.getItem(`${storagePrefix}-current-agent`);
|
||||
if (savedAgent && agents.value && agents.value[savedAgent]) {
|
||||
currentAgent.value = agents.value[savedAgent];
|
||||
console.log("从存储中恢复智能体", currentAgent.value.name);
|
||||
}
|
||||
}
|
||||
|
||||
// 加载保存的状态
|
||||
loadState();
|
||||
|
||||
// 处理消息历史
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
console.log("处理消息历史:", messages.value.length);
|
||||
messages.value = prepareMessageHistory(messages.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载智能体数据出错:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 保存状态到localStorage
|
||||
const saveState = () => {
|
||||
try {
|
||||
@ -776,7 +788,7 @@ const saveState = () => {
|
||||
|
||||
// 确定存储前缀
|
||||
const prefix = props.agentId ? `agent-${props.agentId}` : 'agent-multi';
|
||||
console.log("saveState with prefix:", prefix);
|
||||
// console.log("saveState with prefix:", prefix);
|
||||
|
||||
// 如果是多智能体模式,保存当前选择的智能体
|
||||
if (!props.agentId && currentAgent.value) {
|
||||
@ -785,7 +797,7 @@ const saveState = () => {
|
||||
|
||||
// 保存消息历史
|
||||
if (messages.value && messages.value.length > 0) {
|
||||
console.log(`保存消息历史 (${prefix}):`, messages.value.length);
|
||||
// console.log(`保存消息历史 (${prefix}):`, messages.value.length);
|
||||
localStorage.setItem(`${prefix}-messages`, JSON.stringify(messages.value));
|
||||
} else {
|
||||
localStorage.removeItem(`${prefix}-messages`);
|
||||
@ -794,7 +806,7 @@ const saveState = () => {
|
||||
// 保存线程ID
|
||||
if (currentRunId.value) {
|
||||
localStorage.setItem(`${prefix}-thread-id`, currentRunId.value);
|
||||
console.log(`保存线程ID (${prefix}):`, currentRunId.value);
|
||||
// console.log(`保存线程ID (${prefix}):`, currentRunId.value);
|
||||
} else {
|
||||
localStorage.removeItem(`${prefix}-thread-id`);
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@
|
||||
<slot name="tool-calls"></slot>
|
||||
|
||||
<div v-if="(message.role=='received' || message.role=='assistant') && message.status=='finished' && showRefs">
|
||||
<RefsComponent :message="message" @retry="emit('retry')" />
|
||||
<RefsComponent :message="message" :show-refs="showRefs" @retry="emit('retry')" />
|
||||
</div>
|
||||
<!-- 错误消息 -->
|
||||
</div>
|
||||
@ -94,8 +94,8 @@ const props = defineProps({
|
||||
},
|
||||
// 是否显示推理过程
|
||||
showRefs: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
type: [Array, Boolean],
|
||||
default: () => false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -4,19 +4,23 @@
|
||||
<!-- <span class="item btn" @click="likeThisResponse(msg)"><LikeOutlined /></span> -->
|
||||
<!-- <span class="item btn" @click="dislikeThisResponse(msg)"><DislikeOutlined /></span> -->
|
||||
<span v-if="msg.meta?.server_model_name" class="item"><BulbOutlined /> {{ msg.meta.server_model_name }}</span>
|
||||
<span class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
|
||||
<span class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
|
||||
<span
|
||||
v-if="showKey('copy')"
|
||||
class="item btn" @click="copyText(msg.content)" title="复制"><CopyOutlined /></span>
|
||||
<span
|
||||
v-if="showKey('regenerate')"
|
||||
class="item btn" @click="regenerateMessage()" title="重新生成"><ReloadOutlined /></span>
|
||||
<span
|
||||
v-if="showKey('subGraph') && hasSubGraphData(msg)"
|
||||
class="item btn"
|
||||
@click="openSubGraph(msg)"
|
||||
v-if="hasSubGraphData(msg)"
|
||||
>
|
||||
<DeploymentUnitOutlined /> 关系图
|
||||
</span>
|
||||
<span
|
||||
class="item btn"
|
||||
@click="showWebResult(msg)"
|
||||
v-if="msg.refs?.web_search.results.length > 0"
|
||||
v-if="showKey('webSearch') && msg.refs?.web_search.results.length > 0"
|
||||
>
|
||||
<GlobalOutlined /> 网页搜索 {{ msg.refs.web_search?.results.length }}
|
||||
</span>
|
||||
@ -120,6 +124,10 @@ import GraphContainer from './GraphContainer.vue' // 导入 GraphContainer 组
|
||||
const emit = defineEmits(['retry']);
|
||||
const props = defineProps({
|
||||
message: Object,
|
||||
showRefs: {
|
||||
type: [Array, Boolean],
|
||||
default: () => false
|
||||
}
|
||||
})
|
||||
|
||||
const msg = ref(props.message)
|
||||
@ -127,6 +135,13 @@ const msg = ref(props.message)
|
||||
// 使用 useClipboard 实现复制功能
|
||||
const { copy, isSupported } = useClipboard()
|
||||
|
||||
const showKey = (key) => {
|
||||
if (props.showRefs === true) {
|
||||
return true
|
||||
}
|
||||
return props.showRefs.includes(key)
|
||||
}
|
||||
|
||||
// 定义 copy 方法
|
||||
const copyText = async (text) => {
|
||||
if (isSupported) {
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
<div class="sidebar" :class="{ 'is-open': state.isSidebarOpen }">
|
||||
<h2 class="sidebar-title">
|
||||
智能体列表
|
||||
<div class="toggle-sidebar" @click="toggleSidebar">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
</div>
|
||||
<div class="toggle-sidebar" @click="toggleSidebar">
|
||||
<img src="@/assets/icons/sidebar_left.svg" class="iconfont icon-20" alt="设置" />
|
||||
</div>
|
||||
</h2>
|
||||
<div class="agent-info">
|
||||
<a-select
|
||||
@ -28,7 +28,7 @@
|
||||
</p>
|
||||
|
||||
<!-- 添加requirements显示部分 -->
|
||||
<div v-if="agents[selectedAgentId]?.requirements && agents[selectedAgentId]?.requirements.length > 0" class="requirements-section">
|
||||
<div v-if="agents[selectedAgentId]?.requirements && agents[selectedAgentId]?.requirements.length > 0" class="info-section">
|
||||
<h3>所需环境变量:</h3>
|
||||
<div class="requirements-list">
|
||||
<a-tag v-for="req in agents[selectedAgentId].requirements" :key="req">
|
||||
@ -36,6 +36,16 @@
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加all_tools显示部分 -->
|
||||
<div v-if="agents[selectedAgentId]?.all_tools && agents[selectedAgentId]?.all_tools.length > 0" class="info-section">
|
||||
<h3>可用工具:</h3>
|
||||
<div class="all-tools-list">
|
||||
<a-tag v-for="tool in agents[selectedAgentId].all_tools" :key="tool">
|
||||
{{ tool }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -44,6 +54,7 @@
|
||||
<AgentChatComponent
|
||||
:agent-id="selectedAgentId"
|
||||
:config="agentConfig"
|
||||
:state="state"
|
||||
@open-config="toggleConfigSidebar(true)"
|
||||
>
|
||||
<template #header-left>
|
||||
@ -63,7 +74,7 @@
|
||||
<!-- 右侧配置侧边栏 -->
|
||||
<div class="config-sidebar" :class="{ 'is-open': state.isConfigSidebarOpen }">
|
||||
<h2 class="sidebar-title">
|
||||
智能体配置
|
||||
<div class="sidebar-title-text" @click="toggleDebugMode">智能体配置</div>
|
||||
<div class="toggle-sidebar" @click="toggleConfigSidebar(false)">
|
||||
<CloseOutlined class="iconfont icon-20" />
|
||||
</div>
|
||||
@ -72,54 +83,42 @@
|
||||
<!-- 配置表单 -->
|
||||
<a-form :model="agentConfig" layout="vertical">
|
||||
<!-- 系统提示词 -->
|
||||
|
||||
<div class="empty-config" v-if="state.isEmptyConfig">
|
||||
<a-alert type="warning" message="该智能体没有配置项" show-icon/>
|
||||
</div>
|
||||
<a-form-item v-if="configSchema.system_prompt" label="系统提示词" name="system_prompt">
|
||||
<a-textarea
|
||||
v-model:value="agentConfig.system_prompt"
|
||||
:rows="4"
|
||||
placeholder="设置智能体的系统提示词"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<a-form-item v-if="configSchema.model" :label="`模型 (默认: ${configSchema.model})`" name="model">
|
||||
<a-input
|
||||
v-model:value="agentConfig.model"
|
||||
placeholder="provider/model-name"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 工具选择: 多选,默认全选 -->
|
||||
<a-form-item v-if="configSchema.tools" :label="`工具`" name="tools">
|
||||
<a-select
|
||||
v-model:value="agentConfig.tools"
|
||||
placeholder="选择工具"
|
||||
mode="multiple"
|
||||
>
|
||||
<a-select-option v-for="tool in configSchema.tools" :key="tool">
|
||||
{{ tool }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 其他配置项,可按需扩展 -->
|
||||
<div v-if="Object.keys(additionalConfig).length > 0">
|
||||
<a-divider>其他配置</a-divider>
|
||||
<!-- 统一显示所有配置项 -->
|
||||
<template v-for="(value, key) in configurableItems" :key="key">
|
||||
<a-form-item
|
||||
v-for="(value, key) in additionalConfig"
|
||||
:key="key"
|
||||
:label="key"
|
||||
:label="getConfigLabel(key, value)"
|
||||
:name="key"
|
||||
class="config-item"
|
||||
>
|
||||
<!-- <p>{{ value }}</p> -->
|
||||
<!-- 根据值的类型使用不同的输入控件 -->
|
||||
<p v-if="value.description" class="description">{{ value.description }}</p>
|
||||
<a-switch
|
||||
v-if="typeof agentConfig[key] === 'boolean'"
|
||||
v-model:checked="agentConfig[key]"
|
||||
/>
|
||||
<a-textarea
|
||||
v-else-if="key === 'system_prompt'"
|
||||
v-model:value="agentConfig[key]"
|
||||
:rows="4"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="value?.options"
|
||||
v-model:value="agentConfig[key]"
|
||||
>
|
||||
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
||||
</a-select>
|
||||
<a-input
|
||||
v-model:value="additionalConfig[key]"
|
||||
:placeholder="`设置${key}`"
|
||||
v-else
|
||||
v-model:value="agentConfig[key]"
|
||||
:placeholder="getPlaceholder(key, value)"
|
||||
/>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 保存和重置按钮 -->
|
||||
<div class="form-actions" v-if="!state.isEmptyConfig">
|
||||
@ -151,71 +150,75 @@ import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||
const agents = ref({});
|
||||
const selectedAgentId = ref(null);
|
||||
const state = reactive({
|
||||
debug_mode: false,
|
||||
isSidebarOpen: JSON.parse(localStorage.getItem('agent-sidebar-open') || 'true'),
|
||||
isConfigSidebarOpen: false,
|
||||
isEmptyConfig: computed(() => Object.keys(agents.value[selectedAgentId.value]?.config_schema || {}).length === 0),
|
||||
isEmptyConfig: computed(() =>
|
||||
!selectedAgentId.value ||
|
||||
Object.keys(configurableItems.value).length === 0
|
||||
)
|
||||
});
|
||||
const configSchema = computed(() => agents.value[selectedAgentId.value]?.config_schema || {});
|
||||
const configurableItems = computed(() => configSchema.value.configurable_items || {});
|
||||
|
||||
// 配置状态
|
||||
const agentConfig = ref({
|
||||
system_prompt: '',
|
||||
model: '',
|
||||
debug_mode: false,
|
||||
});
|
||||
const agentConfig = ref({});
|
||||
|
||||
// 存储额外配置项
|
||||
const additionalConfig = ref({});
|
||||
// 调试模式
|
||||
const toggleDebugMode = () => {
|
||||
state.debug_mode = !state.debug_mode;
|
||||
};
|
||||
|
||||
// 根据选中的智能体加载配置
|
||||
const loadAgentConfig = () => {
|
||||
// BUG: 目前消息重置有问题,需要重置消息
|
||||
if (!selectedAgentId.value || !agents.value[selectedAgentId.value]) return;
|
||||
|
||||
const agent = agents.value[selectedAgentId.value];
|
||||
const configSchema = agent.config_schema || {};
|
||||
const schema = agent.config_schema || {};
|
||||
const items = schema.configurable_items || {};
|
||||
|
||||
// 重置配置
|
||||
agentConfig.value = {
|
||||
system_prompt: configSchema.system_prompt || '',
|
||||
model: configSchema.model || '',
|
||||
// 默认全选所有工具
|
||||
tools: configSchema.tools || [],
|
||||
};
|
||||
agentConfig.value = {};
|
||||
|
||||
// 清空额外配置
|
||||
additionalConfig.value = {};
|
||||
// 初始化基础配置项
|
||||
if (schema.system_prompt) {
|
||||
agentConfig.value.system_prompt = schema.system_prompt;
|
||||
}
|
||||
|
||||
if (schema.model) {
|
||||
agentConfig.value.model = schema.model;
|
||||
}
|
||||
|
||||
// 初始化可配置项
|
||||
Object.keys(items).forEach(key => {
|
||||
const item = items[key];
|
||||
|
||||
// 根据类型设置默认值
|
||||
if (typeof item.default === 'boolean') {
|
||||
agentConfig.value[key] = item.default;
|
||||
} else {
|
||||
agentConfig.value[key] = item.default || '';
|
||||
}
|
||||
});
|
||||
|
||||
// 加载存储的配置
|
||||
const savedConfig = JSON.parse(localStorage.getItem(`agent-config-${selectedAgentId.value}`) || '{}');
|
||||
|
||||
// 合并已保存的配置
|
||||
if (savedConfig) {
|
||||
Object.assign(agentConfig.value, savedConfig);
|
||||
Object.keys(savedConfig).forEach(key => {
|
||||
if (key in agentConfig.value) {
|
||||
agentConfig.value[key] = savedConfig[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果没有保存过tools配置,则默认全选
|
||||
if (!savedConfig.tools && configSchema.tools) {
|
||||
agentConfig.value.tools = [...configSchema.tools];
|
||||
}
|
||||
|
||||
// 加载额外配置项
|
||||
Object.keys(configSchema).forEach(key => {
|
||||
if (!['system_prompt', 'model', 'tools'].includes(key)) {
|
||||
additionalConfig.value[key] = savedConfig[key] || configSchema[key] || '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = () => {
|
||||
// 合并所有配置
|
||||
const fullConfig = {
|
||||
...agentConfig.value,
|
||||
...additionalConfig.value
|
||||
};
|
||||
|
||||
// 保存配置到本地存储
|
||||
localStorage.setItem(`agent-config-${selectedAgentId.value}`, JSON.stringify(fullConfig));
|
||||
localStorage.setItem(`agent-config-${selectedAgentId.value}`, JSON.stringify(agentConfig.value));
|
||||
|
||||
// 提示保存成功
|
||||
message.success('配置已保存');
|
||||
@ -223,6 +226,9 @@ const saveConfig = () => {
|
||||
|
||||
// 重置配置
|
||||
const resetConfig = () => {
|
||||
// 清除本地存储中的配置
|
||||
localStorage.removeItem(`agent-config-${selectedAgentId.value}`);
|
||||
// 重新加载默认配置
|
||||
loadAgentConfig();
|
||||
message.info('配置已重置');
|
||||
};
|
||||
@ -308,6 +314,21 @@ onMounted(async () => {
|
||||
// 加载配置
|
||||
loadAgentConfig();
|
||||
});
|
||||
|
||||
// 获取配置标签
|
||||
const getConfigLabel = (key, value) => {
|
||||
// 根据配置项属性选择合适的显示文本
|
||||
if (value.description) {
|
||||
return `${value.name}(${key})`;
|
||||
}
|
||||
return key;
|
||||
};
|
||||
|
||||
// 获取占位符
|
||||
const getPlaceholder = (key, value) => {
|
||||
// 返回描述作为占位符
|
||||
return `(默认: ${value.default})` ;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
@ -357,6 +378,11 @@ onMounted(async () => {
|
||||
min-width: calc(var(--config-sidebar-width) - 16px);
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
color: var(--gray-700);
|
||||
}
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
@ -470,7 +496,7 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
// 添加requirements相关样式
|
||||
.requirements-section {
|
||||
.info-section {
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid var(--main-light-3);
|
||||
padding-top: 12px;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user