feat: 更新工具工厂和聊天机器人逻辑,增强前端多选组件
- 在 tools_factory.py 中修改工具名称生成逻辑,确保兼容 Deepseek 的要求。 - 在 graph.py 中优化聊天模型与工具的绑定逻辑,增加条件判断以提高灵活性。 - 在 AgentView.vue 中新增环境变量显示部分,增强用户界面,添加多选卡片组件以改善选项选择体验。 - 更新样式以支持多选卡片的响应式设计,提升整体用户体验。
This commit is contained in:
parent
286e06f25b
commit
fd7cd8cf25
@ -51,9 +51,11 @@ class ChatbotAgent(BaseAgent):
|
|||||||
|
|
||||||
system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}"
|
system_prompt = f"{conf.system_prompt} Now is {get_cur_time_with_utc()}"
|
||||||
model = load_chat_model(conf.model)
|
model = load_chat_model(conf.model)
|
||||||
model_with_tools = model.bind_tools(self._get_tools(conf.tools))
|
|
||||||
|
|
||||||
res = model_with_tools.invoke(
|
if tools := self._get_tools(conf.tools):
|
||||||
|
model = model.bind_tools(tools)
|
||||||
|
|
||||||
|
res = model.invoke(
|
||||||
[{"role": "system", "content": system_prompt}, *state["messages"]]
|
[{"role": "system", "content": system_prompt}, *state["messages"]]
|
||||||
)
|
)
|
||||||
return {"messages": [res]}
|
return {"messages": [res]}
|
||||||
|
|||||||
@ -81,7 +81,7 @@ def get_all_tools():
|
|||||||
|
|
||||||
# 获取所有知识库
|
# 获取所有知识库
|
||||||
for db_Id, retrieve_info in knowledge_base.get_retrievers().items():
|
for db_Id, retrieve_info in knowledge_base.get_retrievers().items():
|
||||||
name = f"retrieve_{retrieve_info['name']}"
|
name = f"retrieve_{db_Id}" # Deepseek does not support non-alphanumeric characters in tool names
|
||||||
description = (
|
description = (
|
||||||
f"使用 {retrieve_info['name']} 知识库进行检索。\n"
|
f"使用 {retrieve_info['name']} 知识库进行检索。\n"
|
||||||
f"下面是这个知识库的描述:\n{retrieve_info['description']}"
|
f"下面是这个知识库的描述:\n{retrieve_info['description']}"
|
||||||
|
|||||||
@ -62,6 +62,18 @@
|
|||||||
<p>{{ selectedAgent.description }}</p>
|
<p>{{ selectedAgent.description }}</p>
|
||||||
<pre v-if="state.debug_mode">{{ selectedAgent }}</pre>
|
<pre v-if="state.debug_mode">{{ selectedAgent }}</pre>
|
||||||
|
|
||||||
|
<!-- 添加requirements显示部分 -->
|
||||||
|
<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">
|
||||||
|
{{ req }}
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
<div v-if="selectedAgentId && configSchema" class="config-modal-content">
|
<div v-if="selectedAgentId && configSchema" class="config-modal-content">
|
||||||
<!-- 配置表单 -->
|
<!-- 配置表单 -->
|
||||||
<a-form :model="agentConfig" layout="vertical">
|
<a-form :model="agentConfig" layout="vertical">
|
||||||
@ -78,7 +90,7 @@
|
|||||||
|
|
||||||
<!-- key匹配 -->
|
<!-- key匹配 -->
|
||||||
<div v-if="key === 'model'" class="agent-model">
|
<div v-if="key === 'model'" class="agent-model">
|
||||||
<p><small>注意,部分模型对于 Tool Calling 的支持不稳定,建议采用{{ value.options }} </small></p>
|
<!-- <p><small>注意,部分模型对于 Tool Calling 的支持不稳定,建议采用{{ value.options }} </small></p> -->
|
||||||
<ModelSelectorComponent
|
<ModelSelectorComponent
|
||||||
@select-model="handleModelChange"
|
@select-model="handleModelChange"
|
||||||
:model_name="agentConfig[key] ? agentConfig[key].split('/').slice(1).join('/') : ''"
|
:model_name="agentConfig[key] ? agentConfig[key].split('/').slice(1).join('/') : ''"
|
||||||
@ -103,13 +115,35 @@
|
|||||||
>
|
>
|
||||||
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
<a-select
|
<!-- 多选标签卡片 -->
|
||||||
v-else-if="value?.options && value?.type === 'list'"
|
<div v-else-if="value?.options && value?.type === 'list'" class="multi-select-cards">
|
||||||
v-model:value="agentConfig[key]"
|
<div class="multi-select-label">
|
||||||
mode="multiple"
|
<span>已选择 {{ getSelectedCount(key) }} 项</span>
|
||||||
>
|
<a-button type="link" size="small" @click="clearSelection(key)" v-if="getSelectedCount(key) > 0" >
|
||||||
<a-select-option v-for="option in value.options" :key="option" :value="option"></a-select-option>
|
清空
|
||||||
</a-select>
|
</a-button>
|
||||||
|
</div>
|
||||||
|
<div class="options-grid">
|
||||||
|
<div
|
||||||
|
v-for="option in value.options"
|
||||||
|
:key="option"
|
||||||
|
class="option-card"
|
||||||
|
:class="{
|
||||||
|
'selected': isOptionSelected(key, option),
|
||||||
|
'unselected': !isOptionSelected(key, option)
|
||||||
|
}"
|
||||||
|
@click="toggleOption(key, option)"
|
||||||
|
>
|
||||||
|
<div class="option-content">
|
||||||
|
<span class="option-text">{{ option }}</span>
|
||||||
|
<div class="option-indicator">
|
||||||
|
<CheckCircleOutlined v-if="isOptionSelected(key, option)" />
|
||||||
|
<PlusCircleOutlined v-else />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<a-input
|
<a-input
|
||||||
v-else
|
v-else
|
||||||
v-model:value="agentConfig[key]"
|
v-model:value="agentConfig[key]"
|
||||||
@ -118,6 +152,8 @@
|
|||||||
</a-form-item>
|
</a-form-item>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<a-divider />
|
||||||
|
|
||||||
<!-- 弹窗底部按钮 -->
|
<!-- 弹窗底部按钮 -->
|
||||||
<div class="form-actions" v-if="!state.isEmptyConfig">
|
<div class="form-actions" v-if="!state.isEmptyConfig">
|
||||||
<div class="form-actions-left">
|
<div class="form-actions-left">
|
||||||
@ -128,15 +164,6 @@
|
|||||||
</a-form>
|
</a-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加requirements显示部分 -->
|
|
||||||
<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">
|
|
||||||
{{ req }}
|
|
||||||
</a-tag>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -163,7 +190,9 @@ import {
|
|||||||
LinkOutlined,
|
LinkOutlined,
|
||||||
StarOutlined,
|
StarOutlined,
|
||||||
MenuOutlined,
|
MenuOutlined,
|
||||||
StarFilled
|
StarFilled,
|
||||||
|
CheckCircleOutlined,
|
||||||
|
PlusCircleOutlined
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
import AgentChatComponent from '@/components/AgentChatComponent.vue';
|
||||||
@ -215,6 +244,42 @@ const setAsDefaultAgent = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 多选组件相关方法
|
||||||
|
const ensureArray = (key) => {
|
||||||
|
if (!agentConfig.value[key] || !Array.isArray(agentConfig.value[key])) {
|
||||||
|
agentConfig.value[key] = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isOptionSelected = (key, option) => {
|
||||||
|
ensureArray(key);
|
||||||
|
return agentConfig.value[key].includes(option);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSelectedCount = (key) => {
|
||||||
|
ensureArray(key);
|
||||||
|
return agentConfig.value[key].length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleOption = (key, option) => {
|
||||||
|
ensureArray(key);
|
||||||
|
|
||||||
|
const currentOptions = [...agentConfig.value[key]];
|
||||||
|
const index = currentOptions.indexOf(option);
|
||||||
|
|
||||||
|
if (index > -1) {
|
||||||
|
currentOptions.splice(index, 1);
|
||||||
|
} else {
|
||||||
|
currentOptions.push(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
agentConfig.value[key] = currentOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSelection = (key) => {
|
||||||
|
agentConfig.value[key] = [];
|
||||||
|
};
|
||||||
|
|
||||||
// 获取默认智能体ID
|
// 获取默认智能体ID
|
||||||
const fetchDefaultAgent = async () => {
|
const fetchDefaultAgent = async () => {
|
||||||
try {
|
try {
|
||||||
@ -278,6 +343,9 @@ const loadAgentConfig = async () => {
|
|||||||
// 根据类型设置默认值
|
// 根据类型设置默认值
|
||||||
if (typeof item.default === 'boolean') {
|
if (typeof item.default === 'boolean') {
|
||||||
agentConfig.value[key] = item.default;
|
agentConfig.value[key] = item.default;
|
||||||
|
} else if (item.type === 'list') {
|
||||||
|
// 对于 list 类型,确保初始化为数组
|
||||||
|
agentConfig.value[key] = Array.isArray(item.default) ? item.default : [];
|
||||||
} else {
|
} else {
|
||||||
agentConfig.value[key] = item.default || '';
|
agentConfig.value[key] = item.default || '';
|
||||||
}
|
}
|
||||||
@ -290,7 +358,13 @@ const loadAgentConfig = async () => {
|
|||||||
// 合并服务器配置
|
// 合并服务器配置
|
||||||
Object.keys(response.config).forEach(key => {
|
Object.keys(response.config).forEach(key => {
|
||||||
if (key in agentConfig.value) {
|
if (key in agentConfig.value) {
|
||||||
agentConfig.value[key] = response.config[key];
|
const item = items[key];
|
||||||
|
// 对于 list 类型,确保是数组
|
||||||
|
if (item && item.type === 'list') {
|
||||||
|
agentConfig.value[key] = Array.isArray(response.config[key]) ? response.config[key] : [];
|
||||||
|
} else {
|
||||||
|
agentConfig.value[key] = response.config[key];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`);
|
console.log(`从服务器加载 ${selectedAgentId.value} 配置成功, ${JSON.stringify(agentConfig.value)}`);
|
||||||
@ -568,7 +642,6 @@ const toggleSidebar = () => {
|
|||||||
// 添加requirements相关样式
|
// 添加requirements相关样式
|
||||||
.info-section {
|
.info-section {
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
border-top: 1px solid var(--main-light-3);
|
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
@ -693,6 +766,92 @@ const toggleSidebar = () => {
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 多选卡片样式
|
||||||
|
.multi-select-cards {
|
||||||
|
.multi-select-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--gray-600);
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-card {
|
||||||
|
border: 1px solid var(--gray-300);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: white;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-color: var(--main-color);
|
||||||
|
background: var(--main-10);
|
||||||
|
|
||||||
|
.option-indicator {
|
||||||
|
color: var(--main-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
color: var(--main-color);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unselected {
|
||||||
|
.option-indicator {
|
||||||
|
color: var(--gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
color: var(--gray-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-text {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.option-indicator {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 响应式适配
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.multi-select-cards {
|
||||||
|
.options-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user