Separate agent tuning from model setup
This commit is contained in:
@@ -6,6 +6,7 @@ import { api, clearToken, getToken, saveToken } from "./services/api";
|
||||
import type {
|
||||
AdminProfile,
|
||||
AdminUser,
|
||||
AgentDebugResult,
|
||||
AiLogRecord,
|
||||
ChatDetail,
|
||||
ChatRecord,
|
||||
@@ -33,6 +34,10 @@ const chatDetail = ref<ChatDetail | null>(null);
|
||||
const chatDetailOpen = ref(false);
|
||||
const recordTab = ref("chats");
|
||||
const editingModelId = ref<number | null>(null);
|
||||
const agentDebugging = ref(false);
|
||||
const agentPreviewMessages = ref<{ role: "user" | "assistant" | "system"; content: string }[]>([
|
||||
{ role: "assistant", content: "选择模型和知识库后,可以在这里调试 Agent 的真实问答效果。" },
|
||||
]);
|
||||
|
||||
const knowledgeForm = reactive({
|
||||
name: "",
|
||||
@@ -52,12 +57,12 @@ const modelForm = reactive({
|
||||
apiKey: "",
|
||||
authType: "bearer",
|
||||
apiVersion: "",
|
||||
temperature: 0.2 as number | null,
|
||||
temperature: null as number | null,
|
||||
topP: null as number | null,
|
||||
topK: null as number | null,
|
||||
presencePenalty: null as number | null,
|
||||
frequencyPenalty: null as number | null,
|
||||
maxToken: 1024,
|
||||
maxToken: null as number | null,
|
||||
contextWindow: null as number | null,
|
||||
streamEnabled: 1,
|
||||
responseFormat: "",
|
||||
@@ -80,10 +85,10 @@ const quickModelProviders = [
|
||||
baseUrl: "https://api.deepseek.com",
|
||||
authType: "bearer",
|
||||
apiVersion: "",
|
||||
temperature: 0.2,
|
||||
temperature: null,
|
||||
topP: null,
|
||||
maxToken: 1024,
|
||||
contextWindow: 128000,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
models: [
|
||||
{ label: "deepseek-v4-pro", value: "deepseek-v4-pro" },
|
||||
{ label: "deepseek-v4-flash", value: "deepseek-v4-flash" },
|
||||
@@ -98,8 +103,8 @@ const quickModelProviders = [
|
||||
apiVersion: "",
|
||||
temperature: null,
|
||||
topP: null,
|
||||
maxToken: 1024,
|
||||
contextWindow: 1000000,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
models: [
|
||||
{ label: "MiniMax-M3", value: "MiniMax-M3" },
|
||||
{ label: "MiniMax-M2.7", value: "MiniMax-M2.7" },
|
||||
@@ -161,6 +166,18 @@ const chatFilters = reactive({
|
||||
dateRange: [] as string[],
|
||||
});
|
||||
|
||||
const agentForm = reactive({
|
||||
modelId: undefined as number | undefined,
|
||||
knowledgeIds: [] as number[],
|
||||
temperature: 0.2 as number | null,
|
||||
topP: null as number | null,
|
||||
topK: null as number | null,
|
||||
presencePenalty: null as number | null,
|
||||
frequencyPenalty: null as number | null,
|
||||
maxToken: 1024 as number | null,
|
||||
question: "",
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (!getToken()) return;
|
||||
try {
|
||||
@@ -201,7 +218,15 @@ async function loadCurrentMenu() {
|
||||
if (activeMenu.value === "dashboard") stats.value = await api.dashboard();
|
||||
if (activeMenu.value === "users") users.value = await api.users(userKeyword.value);
|
||||
if (activeMenu.value === "knowledge") knowledge.value = await api.knowledge();
|
||||
if (activeMenu.value === "prompt") promptContent.value = (await api.prompt()).promptContent;
|
||||
if (activeMenu.value === "prompt") {
|
||||
const [prompt, modelRows, knowledgeRows] = await Promise.all([api.prompt(), api.models(), api.knowledge()]);
|
||||
promptContent.value = prompt.promptContent;
|
||||
models.value = modelRows;
|
||||
knowledge.value = knowledgeRows;
|
||||
if (!agentForm.modelId && modelRows.length) {
|
||||
agentForm.modelId = modelRows.find((model) => model.enabled === 1)?.id ?? modelRows[0].id;
|
||||
}
|
||||
}
|
||||
if (activeMenu.value === "models") models.value = await api.models();
|
||||
if (activeMenu.value === "configs") configs.value = await api.configs();
|
||||
if (activeMenu.value === "records") {
|
||||
@@ -260,6 +285,18 @@ function buildModelPayload() {
|
||||
payload[field] = null;
|
||||
}
|
||||
});
|
||||
Object.assign(payload, {
|
||||
temperature: null,
|
||||
topP: null,
|
||||
topK: null,
|
||||
presencePenalty: null,
|
||||
frequencyPenalty: null,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
streamEnabled: 1,
|
||||
responseFormat: null,
|
||||
extraParams: null,
|
||||
});
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -281,13 +318,13 @@ async function quickAddModel() {
|
||||
apiKey: quickModelForm.apiKey,
|
||||
authType: provider.authType,
|
||||
apiVersion: provider.apiVersion,
|
||||
temperature: provider.temperature,
|
||||
topP: provider.topP,
|
||||
temperature: null,
|
||||
topP: null,
|
||||
topK: null,
|
||||
presencePenalty: null,
|
||||
frequencyPenalty: null,
|
||||
maxToken: provider.maxToken,
|
||||
contextWindow: provider.contextWindow,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
streamEnabled: 1,
|
||||
responseFormat: null,
|
||||
extraParams: null,
|
||||
@@ -332,7 +369,7 @@ function applyModelPreset(label: string) {
|
||||
{
|
||||
apiVersion: "",
|
||||
authType: "bearer",
|
||||
temperature: 0.2,
|
||||
temperature: null,
|
||||
topP: null,
|
||||
topK: null,
|
||||
presencePenalty: null,
|
||||
@@ -356,13 +393,13 @@ function editModel(row: ModelItem) {
|
||||
apiKey: "",
|
||||
authType: row.authType,
|
||||
apiVersion: row.apiVersion ?? "",
|
||||
temperature: row.temperature ?? null,
|
||||
topP: row.topP ?? null,
|
||||
topK: row.topK ?? null,
|
||||
presencePenalty: row.presencePenalty ?? null,
|
||||
frequencyPenalty: row.frequencyPenalty ?? null,
|
||||
maxToken: row.maxToken ?? 1024,
|
||||
contextWindow: row.contextWindow ?? null,
|
||||
temperature: null,
|
||||
topP: null,
|
||||
topK: null,
|
||||
presencePenalty: null,
|
||||
frequencyPenalty: null,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
streamEnabled: row.streamEnabled,
|
||||
responseFormat: row.responseFormat ?? "",
|
||||
extraParams: row.extraParams ?? "",
|
||||
@@ -383,12 +420,12 @@ function resetModelForm() {
|
||||
apiKey: "",
|
||||
authType: "bearer",
|
||||
apiVersion: "",
|
||||
temperature: 0.2,
|
||||
temperature: null,
|
||||
topP: null,
|
||||
topK: null,
|
||||
presencePenalty: null,
|
||||
frequencyPenalty: null,
|
||||
maxToken: 1024,
|
||||
maxToken: null,
|
||||
contextWindow: null,
|
||||
streamEnabled: 1,
|
||||
responseFormat: "",
|
||||
@@ -427,6 +464,52 @@ async function saveConfig() {
|
||||
await loadCurrentMenu();
|
||||
}
|
||||
|
||||
async function debugAgent() {
|
||||
if (!agentForm.modelId) {
|
||||
ElMessage.error("请选择调试模型");
|
||||
return;
|
||||
}
|
||||
if (!agentForm.question.trim()) {
|
||||
ElMessage.error("请输入调试问题");
|
||||
return;
|
||||
}
|
||||
agentPreviewMessages.value.push({ role: "user", content: agentForm.question.trim() });
|
||||
agentDebugging.value = true;
|
||||
try {
|
||||
const result: AgentDebugResult = await api.debugAgent({
|
||||
promptContent: promptContent.value,
|
||||
modelId: agentForm.modelId,
|
||||
knowledgeIds: agentForm.knowledgeIds,
|
||||
question: agentForm.question.trim(),
|
||||
temperature: agentForm.temperature,
|
||||
topP: agentForm.topP,
|
||||
topK: agentForm.topK,
|
||||
presencePenalty: agentForm.presencePenalty,
|
||||
frequencyPenalty: agentForm.frequencyPenalty,
|
||||
maxToken: agentForm.maxToken,
|
||||
});
|
||||
if (!result.ok) {
|
||||
ElMessage.error(result.message);
|
||||
agentPreviewMessages.value.push({ role: "assistant", content: result.message });
|
||||
return;
|
||||
}
|
||||
agentPreviewMessages.value.push({ role: "assistant", content: result.answer });
|
||||
ElMessage.success(result.message);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Agent 调试失败";
|
||||
ElMessage.error(message);
|
||||
agentPreviewMessages.value.push({ role: "assistant", content: message });
|
||||
} finally {
|
||||
agentDebugging.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clearAgentPreview() {
|
||||
agentPreviewMessages.value = [
|
||||
{ role: "assistant", content: "预览已清空,可以继续发起新的 Agent 调试。" },
|
||||
];
|
||||
}
|
||||
|
||||
async function searchChats() {
|
||||
chats.value = await api.chats(buildChatQuery());
|
||||
}
|
||||
@@ -461,7 +544,7 @@ function buildChatQuery() {
|
||||
<section class="login-box">
|
||||
<div class="brand">AI</div>
|
||||
<h1>AI 知识库后台</h1>
|
||||
<p>用于维护用户、知识库、Prompt、模型和审计记录。</p>
|
||||
<p>用于维护用户、知识库、Agent、模型和审计记录。</p>
|
||||
<el-form label-position="top" @submit.prevent="login">
|
||||
<el-form-item label="管理员账号">
|
||||
<el-input v-model="loginForm.username" />
|
||||
@@ -480,7 +563,7 @@ function buildChatQuery() {
|
||||
<button :class="{ active: activeMenu === 'dashboard' }" @click="switchMenu('dashboard')">Dashboard</button>
|
||||
<button :class="{ active: activeMenu === 'users' }" @click="switchMenu('users')">用户管理</button>
|
||||
<button :class="{ active: activeMenu === 'knowledge' }" @click="switchMenu('knowledge')">知识库管理</button>
|
||||
<button :class="{ active: activeMenu === 'prompt' }" @click="switchMenu('prompt')">Prompt 管理</button>
|
||||
<button :class="{ active: activeMenu === 'prompt' }" @click="switchMenu('prompt')">Agent 管理</button>
|
||||
<button :class="{ active: activeMenu === 'models' }" @click="switchMenu('models')">模型管理</button>
|
||||
<button :class="{ active: activeMenu === 'configs' }" @click="switchMenu('configs')">系统配置</button>
|
||||
<button :class="{ active: activeMenu === 'records' }" @click="switchMenu('records')">记录审计</button>
|
||||
@@ -549,11 +632,83 @@ function buildChatQuery() {
|
||||
|
||||
<template v-if="activeMenu === 'prompt'">
|
||||
<div class="page-head inline">
|
||||
<div><h2>Prompt 管理</h2><p>保存后会影响后续问答 Prompt 组装。</p></div>
|
||||
<el-button @click="resetPrompt">恢复默认</el-button>
|
||||
<div><h2>Agent 管理</h2><p>配置提示词、模型、知识库和调试参数,并在右侧实时预览对话效果。</p></div>
|
||||
<el-button @click="resetPrompt">恢复默认提示词</el-button>
|
||||
</div>
|
||||
<el-input v-model="promptContent" type="textarea" :rows="16" />
|
||||
<div class="actions"><el-button type="primary" @click="savePrompt">保存 Prompt</el-button></div>
|
||||
<section class="agent-workbench">
|
||||
<div class="agent-config-panel">
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="Agent 提示词">
|
||||
<el-input v-model="promptContent" type="textarea" :rows="10" />
|
||||
</el-form-item>
|
||||
<div class="agent-config-grid">
|
||||
<el-form-item label="调试模型">
|
||||
<el-select v-model="agentForm.modelId" placeholder="选择已接入模型">
|
||||
<el-option
|
||||
v-for="model in models"
|
||||
:key="model.id"
|
||||
:label="model.displayName || model.modelName"
|
||||
:value="model.id"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="调试知识库">
|
||||
<el-select v-model="agentForm.knowledgeIds" multiple collapse-tags placeholder="默认使用全部启用知识库">
|
||||
<el-option v-for="item in knowledge" :key="item.id" :label="item.name" :value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="Temperature">
|
||||
<el-input-number v-model="agentForm.temperature" :min="0" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Top P">
|
||||
<el-input-number v-model="agentForm.topP" :min="0" :max="1" :step="0.05" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Top K">
|
||||
<el-input-number v-model="agentForm.topK" :min="1" :max="1000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Tokens">
|
||||
<el-input-number v-model="agentForm.maxToken" :min="1" :max="100000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Presence Penalty">
|
||||
<el-input-number v-model="agentForm.presencePenalty" :min="-2" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Frequency Penalty">
|
||||
<el-input-number v-model="agentForm.frequencyPenalty" :min="-2" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="调试问题">
|
||||
<el-input
|
||||
v-model="agentForm.question"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="输入一个真实用户可能会问的问题"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="savePrompt">保存 Agent 提示词</el-button>
|
||||
<el-button type="primary" :loading="agentDebugging" @click="debugAgent">发送调试</el-button>
|
||||
<el-button @click="clearAgentPreview">清空预览</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
<aside class="agent-preview-panel">
|
||||
<div class="agent-preview-head">
|
||||
<h3>调试预览</h3>
|
||||
<span>{{ models.find((model) => model.id === agentForm.modelId)?.modelName || "未选择模型" }}</span>
|
||||
</div>
|
||||
<div class="agent-preview-chat">
|
||||
<article
|
||||
v-for="(message, index) in agentPreviewMessages"
|
||||
:key="`${message.role}-${index}`"
|
||||
class="agent-preview-message"
|
||||
:class="message.role"
|
||||
>
|
||||
<strong>{{ message.role === "user" ? "用户" : "Agent" }}</strong>
|
||||
<pre>{{ message.content }}</pre>
|
||||
</article>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<template v-if="activeMenu === 'models'">
|
||||
@@ -639,48 +794,10 @@ function buildChatQuery() {
|
||||
<el-form-item label="API Version">
|
||||
<el-input v-model="modelForm.apiVersion" placeholder="Anthropic 默认 2023-06-01" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Temperature">
|
||||
<el-input-number v-model="modelForm.temperature" :min="0" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Top P">
|
||||
<el-input-number v-model="modelForm.topP" :min="0" :max="1" :step="0.05" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Top K">
|
||||
<el-input-number v-model="modelForm.topK" :min="1" :max="1000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Max Tokens">
|
||||
<el-input-number v-model="modelForm.maxToken" :min="1" :max="100000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="上下文窗口">
|
||||
<el-input-number v-model="modelForm.contextWindow" :min="1" :max="2000000" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Presence Penalty">
|
||||
<el-input-number v-model="modelForm.presencePenalty" :min="-2" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="Frequency Penalty">
|
||||
<el-input-number v-model="modelForm.frequencyPenalty" :min="-2" :max="2" :step="0.1" />
|
||||
</el-form-item>
|
||||
<el-form-item label="流式输出">
|
||||
<el-switch v-model="modelForm.streamEnabled" :active-value="1" :inactive-value="0" />
|
||||
</el-form-item>
|
||||
<el-form-item label="响应格式">
|
||||
<el-select v-model="modelForm.responseFormat" clearable>
|
||||
<el-option label="默认文本" value="" />
|
||||
<el-option label="JSON Object" value="json_object" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="超时秒数">
|
||||
<el-input-number v-model="modelForm.timeoutSecond" :min="1" :max="300" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="高级 JSON 参数">
|
||||
<el-input
|
||||
v-model="modelForm.extraParams"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='例如:{"stop":["###"],"seed":42}'
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注">
|
||||
<el-input v-model="modelForm.remark" placeholder="用途、额度、注意事项等" />
|
||||
</el-form-item>
|
||||
@@ -695,9 +812,7 @@ function buildChatQuery() {
|
||||
<el-table-column prop="apiType" label="协议" width="170" />
|
||||
<el-table-column prop="modelName" label="模型" />
|
||||
<el-table-column prop="baseUrl" label="Base URL" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="temperature" label="温度" width="80" />
|
||||
<el-table-column prop="topP" label="Top P" width="80" />
|
||||
<el-table-column prop="maxToken" label="Max" width="90" />
|
||||
<el-table-column prop="authType" label="鉴权" width="110" />
|
||||
<el-table-column prop="enabled" label="启用" width="90" />
|
||||
<el-table-column label="操作" width="210" fixed="right">
|
||||
<template #default="{ row }">
|
||||
@@ -740,14 +855,18 @@ function buildChatQuery() {
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-model="chatFilters.dateRange"
|
||||
class="record-date-range"
|
||||
type="datetimerange"
|
||||
unlink-panels
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DDTHH:mm:ss"
|
||||
/>
|
||||
<el-button type="primary" @click="searchChats">搜索</el-button>
|
||||
<el-button @click="resetChatFilters">重置</el-button>
|
||||
<el-button @click="exportChats">导出</el-button>
|
||||
<div class="record-filter-actions">
|
||||
<el-button type="primary" @click="searchChats">搜索</el-button>
|
||||
<el-button @click="resetChatFilters">重置</el-button>
|
||||
<el-button @click="exportChats">导出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table :data="chats" stripe>
|
||||
<el-table-column prop="id" label="会话ID" width="90" />
|
||||
|
||||
Reference in New Issue
Block a user