feat: add agent response depth control
This commit is contained in:
@@ -5,6 +5,7 @@ import { computed, nextTick, onMounted, reactive, ref, watch } from "vue";
|
|||||||
import { api, streamDebugAgent } from "../services/api";
|
import { api, streamDebugAgent } from "../services/api";
|
||||||
import type { AgentRuntimeConfig, KnowledgeItem, ModelItem, PromptDetail, PromptHistoryItem } from "../types/api";
|
import type { AgentRuntimeConfig, KnowledgeItem, ModelItem, PromptDetail, PromptHistoryItem } from "../types/api";
|
||||||
import AgentGenerationParameters from "./AgentGenerationParameters.vue";
|
import AgentGenerationParameters from "./AgentGenerationParameters.vue";
|
||||||
|
import AgentResponseDepthControl from "./AgentResponseDepthControl.vue";
|
||||||
import AdminPagination from "./AdminPagination.vue";
|
import AdminPagination from "./AdminPagination.vue";
|
||||||
import StreamingMarkdownMessage from "./StreamingMarkdownMessage.vue";
|
import StreamingMarkdownMessage from "./StreamingMarkdownMessage.vue";
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ const agentForm = reactive({
|
|||||||
maxToken: 8192 as number | null,
|
maxToken: 8192 as number | null,
|
||||||
streamEnabled: 1,
|
streamEnabled: 1,
|
||||||
reasoningVisible: 0,
|
reasoningVisible: 0,
|
||||||
|
responseDepth: 35,
|
||||||
question: "",
|
question: "",
|
||||||
});
|
});
|
||||||
const runtimeConfig = ref<AgentRuntimeConfig | null>(null);
|
const runtimeConfig = ref<AgentRuntimeConfig | null>(null);
|
||||||
@@ -63,6 +65,7 @@ const runtimeForm = reactive({
|
|||||||
maxToken: 8192 as number | null,
|
maxToken: 8192 as number | null,
|
||||||
streamEnabled: 1,
|
streamEnabled: 1,
|
||||||
reasoningVisible: 0,
|
reasoningVisible: 0,
|
||||||
|
responseDepth: 35,
|
||||||
});
|
});
|
||||||
|
|
||||||
const promptDirty = computed(() => promptContent.value !== savedPromptContent.value);
|
const promptDirty = computed(() => promptContent.value !== savedPromptContent.value);
|
||||||
@@ -119,6 +122,7 @@ function applyRuntimeConfig(value: AgentRuntimeConfig) {
|
|||||||
maxToken: value.maxToken,
|
maxToken: value.maxToken,
|
||||||
streamEnabled: value.streamEnabled,
|
streamEnabled: value.streamEnabled,
|
||||||
reasoningVisible: value.reasoningVisible,
|
reasoningVisible: value.reasoningVisible,
|
||||||
|
responseDepth: value.responseDepth ?? 35,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +138,7 @@ function applyDebugModelDefaults(modelId?: number) {
|
|||||||
maxToken: model.maxToken ?? (model.enabled === 1 ? runtimeConfig.value?.maxToken : null) ?? 8192,
|
maxToken: model.maxToken ?? (model.enabled === 1 ? runtimeConfig.value?.maxToken : null) ?? 8192,
|
||||||
streamEnabled: model.streamEnabled ?? 1,
|
streamEnabled: model.streamEnabled ?? 1,
|
||||||
reasoningVisible: runtimeConfig.value?.reasoningVisible ?? 0,
|
reasoningVisible: runtimeConfig.value?.reasoningVisible ?? 0,
|
||||||
|
responseDepth: runtimeConfig.value?.responseDepth ?? 35,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +156,7 @@ async function saveRuntimeConfig() {
|
|||||||
maxToken: runtimeForm.maxToken,
|
maxToken: runtimeForm.maxToken,
|
||||||
streamEnabled: runtimeForm.streamEnabled,
|
streamEnabled: runtimeForm.streamEnabled,
|
||||||
reasoningVisible: runtimeForm.reasoningVisible,
|
reasoningVisible: runtimeForm.reasoningVisible,
|
||||||
|
responseDepth: runtimeForm.responseDepth,
|
||||||
});
|
});
|
||||||
applyRuntimeConfig(saved);
|
applyRuntimeConfig(saved);
|
||||||
const model = models.value.find((item) => item.id === saved.modelId);
|
const model = models.value.find((item) => item.id === saved.modelId);
|
||||||
@@ -304,6 +310,7 @@ async function debugAgent() {
|
|||||||
maxToken: agentForm.maxToken,
|
maxToken: agentForm.maxToken,
|
||||||
streamEnabled: agentForm.streamEnabled,
|
streamEnabled: agentForm.streamEnabled,
|
||||||
reasoningVisible: agentForm.reasoningVisible,
|
reasoningVisible: agentForm.reasoningVisible,
|
||||||
|
responseDepth: agentForm.responseDepth,
|
||||||
},
|
},
|
||||||
(chunk) => {
|
(chunk) => {
|
||||||
currentAssistant().content += chunk;
|
currentAssistant().content += chunk;
|
||||||
@@ -423,6 +430,7 @@ function errorMessage(error: unknown, fallback: string) {
|
|||||||
v-model:max-token="runtimeForm.maxToken"
|
v-model:max-token="runtimeForm.maxToken"
|
||||||
:disabled="!runtimeConfig?.modelId"
|
:disabled="!runtimeConfig?.modelId"
|
||||||
/>
|
/>
|
||||||
|
<AgentResponseDepthControl v-model="runtimeForm.responseDepth" :disabled="!runtimeConfig?.modelId" />
|
||||||
<el-form-item class="agent-stream-setting">
|
<el-form-item class="agent-stream-setting">
|
||||||
<div class="agent-stream-setting-copy">
|
<div class="agent-stream-setting-copy">
|
||||||
<strong>用户端流式输出</strong>
|
<strong>用户端流式输出</strong>
|
||||||
@@ -480,6 +488,7 @@ function errorMessage(error: unknown, fallback: string) {
|
|||||||
v-model:frequency-penalty="agentForm.frequencyPenalty"
|
v-model:frequency-penalty="agentForm.frequencyPenalty"
|
||||||
v-model:max-token="agentForm.maxToken"
|
v-model:max-token="agentForm.maxToken"
|
||||||
/>
|
/>
|
||||||
|
<AgentResponseDepthControl v-model="agentForm.responseDepth" />
|
||||||
<el-form-item class="agent-stream-setting">
|
<el-form-item class="agent-stream-setting">
|
||||||
<div class="agent-stream-setting-copy">
|
<div class="agent-stream-setting-copy">
|
||||||
<strong>流式输出</strong>
|
<strong>流式输出</strong>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: number;
|
||||||
|
disabled?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
"update:modelValue": [value: number];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function depthLabel(value: number) {
|
||||||
|
if (value <= 33) return "精简";
|
||||||
|
if (value <= 66) return "平衡";
|
||||||
|
return "深入";
|
||||||
|
}
|
||||||
|
|
||||||
|
function depthCopy(value: number) {
|
||||||
|
if (value <= 33) return "少分析、短建议,优先回到当下和身体感受。";
|
||||||
|
if (value <= 66) return "保留必要解释,给出清楚但不过度展开的建议。";
|
||||||
|
return "允许更完整拆解,但仍保持回归自身、觉察和释放的方向。";
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-form-item class="agent-response-depth">
|
||||||
|
<div class="agent-response-depth-head">
|
||||||
|
<div class="agent-stream-setting-copy">
|
||||||
|
<strong>回答深度</strong>
|
||||||
|
<span>{{ depthCopy(props.modelValue) }}</span>
|
||||||
|
</div>
|
||||||
|
<el-tag effect="plain">{{ depthLabel(props.modelValue) }} {{ props.modelValue }}</el-tag>
|
||||||
|
</div>
|
||||||
|
<el-slider
|
||||||
|
:model-value="props.modelValue"
|
||||||
|
:disabled="disabled"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="5"
|
||||||
|
:marks="{ 0: '精简', 50: '平衡', 100: '深入' }"
|
||||||
|
@update:model-value="$emit('update:modelValue', Number($event))"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
ElPagination,
|
ElPagination,
|
||||||
ElProgress,
|
ElProgress,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
|
ElSlider,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
ElTabPane,
|
ElTabPane,
|
||||||
ElTable,
|
ElTable,
|
||||||
@@ -51,6 +52,7 @@ import "element-plus/theme-chalk/el-progress.css";
|
|||||||
import "element-plus/theme-chalk/el-popper.css";
|
import "element-plus/theme-chalk/el-popper.css";
|
||||||
import "element-plus/theme-chalk/el-scrollbar.css";
|
import "element-plus/theme-chalk/el-scrollbar.css";
|
||||||
import "element-plus/theme-chalk/el-select.css";
|
import "element-plus/theme-chalk/el-select.css";
|
||||||
|
import "element-plus/theme-chalk/el-slider.css";
|
||||||
import "element-plus/theme-chalk/el-switch.css";
|
import "element-plus/theme-chalk/el-switch.css";
|
||||||
import "element-plus/theme-chalk/el-table.css";
|
import "element-plus/theme-chalk/el-table.css";
|
||||||
import "element-plus/theme-chalk/el-tabs.css";
|
import "element-plus/theme-chalk/el-tabs.css";
|
||||||
@@ -86,6 +88,7 @@ app.config.globalProperties.$ELEMENT = { locale: zhCn };
|
|||||||
ElPagination,
|
ElPagination,
|
||||||
ElProgress,
|
ElProgress,
|
||||||
ElSelect,
|
ElSelect,
|
||||||
|
ElSlider,
|
||||||
ElSwitch,
|
ElSwitch,
|
||||||
ElTabPane,
|
ElTabPane,
|
||||||
ElTable,
|
ElTable,
|
||||||
|
|||||||
@@ -1049,12 +1049,46 @@ textarea {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-response-depth {
|
||||||
|
margin: 16px 0 0;
|
||||||
|
padding: 14px 16px 18px;
|
||||||
|
border: 1px solid #dce7e3;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-response-depth > .el-form-item__content {
|
||||||
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-response-depth-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-response-depth .el-slider {
|
||||||
|
--el-slider-main-bg-color: #0f735d;
|
||||||
|
--el-slider-runway-bg-color: #dce7e3;
|
||||||
|
width: calc(100% - 12px);
|
||||||
|
margin: 2px 6px 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 680px) {
|
@media (max-width: 680px) {
|
||||||
.agent-stream-setting > .el-form-item__content {
|
.agent-stream-setting > .el-form-item__content {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.agent-response-depth-head {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-parameter-label {
|
.agent-parameter-label {
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ export interface AgentGenerationConfig {
|
|||||||
maxToken: number;
|
maxToken: number;
|
||||||
streamEnabled: number;
|
streamEnabled: number;
|
||||||
reasoningVisible: number;
|
reasoningVisible: number;
|
||||||
|
responseDepth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentRuntimeConfig extends AgentGenerationConfig {
|
export interface AgentRuntimeConfig extends AgentGenerationConfig {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from app.services.feishu_service import FeishuKnowledgeService
|
|||||||
from app.services.knowledge_service import KnowledgeScope
|
from app.services.knowledge_service import KnowledgeScope
|
||||||
from app.services.model_service import ModelClientService
|
from app.services.model_service import ModelClientService
|
||||||
from app.services.reasoning_policy_service import ReasoningPolicyService
|
from app.services.reasoning_policy_service import ReasoningPolicyService
|
||||||
|
from app.services.response_style_service import ResponseStyleService
|
||||||
from app.services.secret_service import MASKED_SECRET, SENSITIVE_CONFIG_KEYS, SecretService
|
from app.services.secret_service import MASKED_SECRET, SENSITIVE_CONFIG_KEYS, SecretService
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
@@ -206,7 +207,7 @@ def get_agent_runtime_config(
|
|||||||
current_admin: Admin = Depends(get_current_admin),
|
current_admin: Admin = Depends(get_current_admin),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
model = _enabled_model(db)
|
model = _enabled_model(db)
|
||||||
return api_success(_agent_runtime_config_dict(model, ReasoningPolicyService.is_visible(db)))
|
return api_success(_agent_runtime_config_dict(model, ReasoningPolicyService.is_visible(db), ResponseStyleService.get_depth(db)))
|
||||||
|
|
||||||
|
|
||||||
@router.put("/agent/runtime-config")
|
@router.put("/agent/runtime-config")
|
||||||
@@ -230,6 +231,7 @@ def save_agent_runtime_config(
|
|||||||
model.stream_enabled = payload.streamEnabled
|
model.stream_enabled = payload.streamEnabled
|
||||||
db.add(model)
|
db.add(model)
|
||||||
ReasoningPolicyService.set_visible(db, payload.reasoningVisible == 1, current_admin.id)
|
ReasoningPolicyService.set_visible(db, payload.reasoningVisible == 1, current_admin.id)
|
||||||
|
ResponseStyleService.set_depth(db, payload.responseDepth, current_admin.id)
|
||||||
OperationLogService.write(
|
OperationLogService.write(
|
||||||
db,
|
db,
|
||||||
admin_id=current_admin.id,
|
admin_id=current_admin.id,
|
||||||
@@ -239,7 +241,7 @@ def save_agent_runtime_config(
|
|||||||
)
|
)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(model)
|
db.refresh(model)
|
||||||
return api_success(_agent_runtime_config_dict(model, ReasoningPolicyService.is_visible(db)))
|
return api_success(_agent_runtime_config_dict(model, ReasoningPolicyService.is_visible(db), ResponseStyleService.get_depth(db)))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/model/list")
|
@router.get("/model/list")
|
||||||
@@ -443,7 +445,7 @@ def _enabled_model(db: Session) -> ModelConfig | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _agent_runtime_config_dict(model: ModelConfig | None, reasoning_visible: bool = False) -> dict:
|
def _agent_runtime_config_dict(model: ModelConfig | None, reasoning_visible: bool = False, response_depth: int = 35) -> dict:
|
||||||
return {
|
return {
|
||||||
"modelId": model.id if model is not None else None,
|
"modelId": model.id if model is not None else None,
|
||||||
"modelName": (model.display_name or model.model_name) if model is not None else None,
|
"modelName": (model.display_name or model.model_name) if model is not None else None,
|
||||||
@@ -455,6 +457,7 @@ def _agent_runtime_config_dict(model: ModelConfig | None, reasoning_visible: boo
|
|||||||
"maxToken": model.max_token if model is not None and model.max_token is not None else 8192,
|
"maxToken": model.max_token if model is not None and model.max_token is not None else 8192,
|
||||||
"streamEnabled": model.stream_enabled if model is not None else 1,
|
"streamEnabled": model.stream_enabled if model is not None else 1,
|
||||||
"reasoningVisible": 1 if reasoning_visible else 0,
|
"reasoningVisible": 1 if reasoning_visible else 0,
|
||||||
|
"responseDepth": response_depth,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ class AgentDebugRequest(BaseModel):
|
|||||||
maxToken: int | None = Field(default=8192, ge=1, le=100000)
|
maxToken: int | None = Field(default=8192, ge=1, le=100000)
|
||||||
streamEnabled: int = Field(default=1, ge=0, le=1)
|
streamEnabled: int = Field(default=1, ge=0, le=1)
|
||||||
reasoningVisible: int = Field(default=0, ge=0, le=1)
|
reasoningVisible: int = Field(default=0, ge=0, le=1)
|
||||||
|
responseDepth: int = Field(default=35, ge=0, le=100)
|
||||||
|
|
||||||
|
|
||||||
class AgentRuntimeConfigSaveRequest(BaseModel):
|
class AgentRuntimeConfigSaveRequest(BaseModel):
|
||||||
@@ -109,6 +110,7 @@ class AgentRuntimeConfigSaveRequest(BaseModel):
|
|||||||
maxToken: int = Field(default=8192, ge=256, le=100000)
|
maxToken: int = Field(default=8192, ge=256, le=100000)
|
||||||
streamEnabled: int = Field(default=1, ge=0, le=1)
|
streamEnabled: int = Field(default=1, ge=0, le=1)
|
||||||
reasoningVisible: int = Field(default=0, ge=0, le=1)
|
reasoningVisible: int = Field(default=0, ge=0, le=1)
|
||||||
|
responseDepth: int = Field(default=35, ge=0, le=100)
|
||||||
|
|
||||||
|
|
||||||
class ModelSaveRequest(BaseModel):
|
class ModelSaveRequest(BaseModel):
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class AgentDebugService:
|
|||||||
version_overrides=payload.knowledgeVersions,
|
version_overrides=payload.knowledgeVersions,
|
||||||
preview_knowledge_ids=payload.knowledgeIds,
|
preview_knowledge_ids=payload.knowledgeIds,
|
||||||
prompt_override=payload.promptContent,
|
prompt_override=payload.promptContent,
|
||||||
|
response_depth=payload.responseDepth,
|
||||||
)
|
)
|
||||||
return RagResult(
|
return RagResult(
|
||||||
question=rag_result.question,
|
question=rag_result.question,
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ class KnowledgeAgentService:
|
|||||||
preview_knowledge_ids: list[int] | None = None,
|
preview_knowledge_ids: list[int] | None = None,
|
||||||
context_trace: list[dict] | None = None,
|
context_trace: list[dict] | None = None,
|
||||||
prompt_override: str | None = None,
|
prompt_override: str | None = None,
|
||||||
|
response_depth: int | None = None,
|
||||||
) -> RagResult:
|
) -> RagResult:
|
||||||
started = perf_counter()
|
started = perf_counter()
|
||||||
catalog = cls.get_knowledge_catalog(
|
catalog = cls.get_knowledge_catalog(
|
||||||
@@ -207,6 +208,7 @@ class KnowledgeAgentService:
|
|||||||
session_summary,
|
session_summary,
|
||||||
summary_up_to_message_id,
|
summary_up_to_message_id,
|
||||||
prompt_override=prompt_override,
|
prompt_override=prompt_override,
|
||||||
|
response_depth=response_depth,
|
||||||
)
|
)
|
||||||
return RagResult(
|
return RagResult(
|
||||||
question=question,
|
question=question,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from app.models.user import User
|
|||||||
from app.services.chat_context_service import ChatContextService
|
from app.services.chat_context_service import ChatContextService
|
||||||
from app.services.feishu_service import FeishuKnowledgeService
|
from app.services.feishu_service import FeishuKnowledgeService
|
||||||
from app.services.knowledge_service import KnowledgeAccessService, KnowledgeScope
|
from app.services.knowledge_service import KnowledgeAccessService, KnowledgeScope
|
||||||
|
from app.services.response_style_service import ResponseStyleService
|
||||||
|
|
||||||
NO_HIT_ANSWER = "当前知识库中未检索到相关内容,请联系管理员补充相关知识。"
|
NO_HIT_ANSWER = "当前知识库中未检索到相关内容,请联系管理员补充相关知识。"
|
||||||
|
|
||||||
@@ -119,6 +120,7 @@ class PromptService:
|
|||||||
session_summary: str | None = None,
|
session_summary: str | None = None,
|
||||||
summary_up_to_message_id: int | None = None,
|
summary_up_to_message_id: int | None = None,
|
||||||
prompt_override: str | None = None,
|
prompt_override: str | None = None,
|
||||||
|
response_depth: int | None = None,
|
||||||
) -> list[dict[str, str]]:
|
) -> list[dict[str, str]]:
|
||||||
prompt = prompt_override.strip() if prompt_override and prompt_override.strip() else cls._load_active_prompt(db)
|
prompt = prompt_override.strip() if prompt_override and prompt_override.strip() else cls._load_active_prompt(db)
|
||||||
|
|
||||||
@@ -135,7 +137,8 @@ class PromptService:
|
|||||||
+ "[不可关闭的最低安全规则 v1]\n"
|
+ "[不可关闭的最低安全规则 v1]\n"
|
||||||
+ "现实危险、自伤伤人风险应优先建议立即寻求线下专业帮助;医疗、法律、财务问题不得给出替代专业意见的结论;"
|
+ "现实危险、自伤伤人风险应优先建议立即寻求线下专业帮助;医疗、法律、财务问题不得给出替代专业意见的结论;"
|
||||||
+ "不得伪造老师观点或课程内容;不得输出整篇课程文章、大段连续原文,也不得通过多轮拼接还原完整资料。"
|
+ "不得伪造老师观点或课程内容;不得输出整篇课程文章、大段连续原文,也不得通过多轮拼接还原完整资料。"
|
||||||
+ cls._knowledge_type_rules(chunks),
|
+ cls._knowledge_type_rules(chunks)
|
||||||
|
+ ResponseStyleService.build_instruction(db, response_depth),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.models.ai_config import SystemConfig
|
||||||
|
|
||||||
|
|
||||||
|
RESPONSE_DEPTH_KEY = "agent_response_depth"
|
||||||
|
DEFAULT_RESPONSE_DEPTH = 35
|
||||||
|
MIN_RESPONSE_DEPTH = 0
|
||||||
|
MAX_RESPONSE_DEPTH = 100
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseStyleService:
|
||||||
|
@staticmethod
|
||||||
|
def get_depth(db: Session) -> int:
|
||||||
|
config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == RESPONSE_DEPTH_KEY))
|
||||||
|
if config is None:
|
||||||
|
return DEFAULT_RESPONSE_DEPTH
|
||||||
|
try:
|
||||||
|
return _clamp_depth(int(config.config_value))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return DEFAULT_RESPONSE_DEPTH
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def set_depth(db: Session, depth: int, admin_id: int | None) -> SystemConfig:
|
||||||
|
config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == RESPONSE_DEPTH_KEY))
|
||||||
|
if config is None:
|
||||||
|
config = SystemConfig(config_key=RESPONSE_DEPTH_KEY, config_value=str(DEFAULT_RESPONSE_DEPTH))
|
||||||
|
config.config_value = str(_clamp_depth(depth))
|
||||||
|
config.description = "Agent 回答深度,0 为精简,100 为深入"
|
||||||
|
config.updated_by = admin_id
|
||||||
|
db.add(config)
|
||||||
|
return config
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def build_instruction(cls, db: Session, depth: int | None = None) -> str:
|
||||||
|
value = _clamp_depth(cls.get_depth(db) if depth is None else depth)
|
||||||
|
if value <= 33:
|
||||||
|
mode = "精简"
|
||||||
|
rules = (
|
||||||
|
"回答要短,通常控制在1到3段或3条以内;不展开心理分析、不复盘学员长篇经历,"
|
||||||
|
"优先给出一句方向确认,再引导回到当下、身体感受和情绪觉察。建议只给当下最小一步,"
|
||||||
|
"避免连续列出多个深入方案。"
|
||||||
|
)
|
||||||
|
elif value <= 66:
|
||||||
|
mode = "平衡"
|
||||||
|
rules = (
|
||||||
|
"回答保持适中,可以先确认方向,再补充必要原因和1到3个可执行提醒;"
|
||||||
|
"不做过度拆解,不把分析过程完整罗列出来。"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
mode = "深入"
|
||||||
|
rules = (
|
||||||
|
"可以更完整地解释原因、步骤和注意事项,但仍要避免变成咨询式长篇分析;"
|
||||||
|
"优先围绕回到当下、回到自身、觉察情绪和身体感受、如是释放来组织内容。"
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
"\n\n[回答深度控制]\n"
|
||||||
|
f"当前回答深度:{value}/100({mode})。{rules}"
|
||||||
|
"无论深度如何,都不要暴露内部分析过程;不要替学员做复杂人格判断;"
|
||||||
|
"涉及功课反馈时,先判断方向是否偏离,再给最少必要的下一步。"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _clamp_depth(value: int) -> int:
|
||||||
|
return max(MIN_RESPONSE_DEPTH, min(MAX_RESPONSE_DEPTH, value))
|
||||||
@@ -20,7 +20,7 @@ from app.services.model_stream_service import (
|
|||||||
_stream_configured_model_async,
|
_stream_configured_model_async,
|
||||||
)
|
)
|
||||||
from app.services.model_service import ModelClientService, _copy_model_with_overrides, _max_output_tokens
|
from app.services.model_service import ModelClientService, _copy_model_with_overrides, _max_output_tokens
|
||||||
from app.services.rag_service import RagResult
|
from app.services.rag_service import PromptService, RagResult
|
||||||
|
|
||||||
|
|
||||||
def _database() -> Session:
|
def _database() -> Session:
|
||||||
@@ -73,6 +73,7 @@ def test_runtime_config_defaults_to_long_answer_safe_max_tokens():
|
|||||||
assert result["maxToken"] == 8192
|
assert result["maxToken"] == 8192
|
||||||
assert result["streamEnabled"] == 1
|
assert result["streamEnabled"] == 1
|
||||||
assert result["reasoningVisible"] == 0
|
assert result["reasoningVisible"] == 0
|
||||||
|
assert result["responseDepth"] == 35
|
||||||
assert _max_output_tokens(model) == 8192
|
assert _max_output_tokens(model) == 8192
|
||||||
|
|
||||||
|
|
||||||
@@ -97,6 +98,7 @@ def test_saved_runtime_config_is_persisted_on_enabled_model():
|
|||||||
maxToken=12000,
|
maxToken=12000,
|
||||||
streamEnabled=0,
|
streamEnabled=0,
|
||||||
reasoningVisible=1,
|
reasoningVisible=1,
|
||||||
|
responseDepth=20,
|
||||||
),
|
),
|
||||||
db=db,
|
db=db,
|
||||||
current_admin=admin,
|
current_admin=admin,
|
||||||
@@ -113,7 +115,9 @@ def test_saved_runtime_config_is_persisted_on_enabled_model():
|
|||||||
assert model.stream_enabled == 0
|
assert model.stream_enabled == 0
|
||||||
assert result["streamEnabled"] == 0
|
assert result["streamEnabled"] == 0
|
||||||
assert result["reasoningVisible"] == 1
|
assert result["reasoningVisible"] == 1
|
||||||
|
assert result["responseDepth"] == 20
|
||||||
assert db.query(SystemConfig).filter_by(config_key="show_model_reasoning").one().config_value == "true"
|
assert db.query(SystemConfig).filter_by(config_key="show_model_reasoning").one().config_value == "true"
|
||||||
|
assert db.query(SystemConfig).filter_by(config_key="agent_response_depth").one().config_value == "20"
|
||||||
|
|
||||||
payload = _openai_stream_payload(
|
payload = _openai_stream_payload(
|
||||||
model,
|
model,
|
||||||
@@ -191,10 +195,24 @@ def test_debug_preview_passes_conversation_history_and_replaces_saved_prompt():
|
|||||||
kwargs = build_result.await_args.kwargs
|
kwargs = build_result.await_args.kwargs
|
||||||
assert [item.content for item in kwargs["history"]] == ["最开始的问题", "第一次回答"]
|
assert [item.content for item in kwargs["history"]] == ["最开始的问题", "第一次回答"]
|
||||||
assert kwargs["prompt_override"] == "调试主提示词"
|
assert kwargs["prompt_override"] == "调试主提示词"
|
||||||
|
assert kwargs["response_depth"] == 35
|
||||||
assert result.messages == rag_result.messages
|
assert result.messages == rag_result.messages
|
||||||
assert result.prompt == "调试提示词渲染结果"
|
assert result.prompt == "调试提示词渲染结果"
|
||||||
|
|
||||||
|
|
||||||
|
def test_prompt_includes_response_depth_instruction():
|
||||||
|
with _database() as db:
|
||||||
|
db.add(SystemConfig(config_key="agent_response_depth", config_value="20"))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
messages = PromptService.build_messages(db, "做功课没反应怎么办?", [])
|
||||||
|
|
||||||
|
system = messages[0]["content"]
|
||||||
|
assert "回答深度:20/100(精简)" in system
|
||||||
|
assert "不展开心理分析" in system
|
||||||
|
assert "回到当下、身体感受和情绪觉察" in system
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("reasoning_visible", [True, False])
|
@pytest.mark.parametrize("reasoning_visible", [True, False])
|
||||||
def test_agent_debug_stream_respects_reasoning_visibility(reasoning_visible):
|
def test_agent_debug_stream_respects_reasoning_visibility(reasoning_visible):
|
||||||
async def chunks():
|
async def chunks():
|
||||||
|
|||||||
Reference in New Issue
Block a user