Align user chat with agent model flow
This commit is contained in:
@@ -10,7 +10,7 @@ from sqlalchemy import select
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from app.core.config import get_settings
|
from app.core.config import get_settings
|
||||||
from app.models.ai_config import ModelConfig
|
from app.models.ai_config import ModelConfig, SystemConfig
|
||||||
from app.services.external_errors import ExternalServiceError
|
from app.services.external_errors import ExternalServiceError
|
||||||
from app.services.rag_service import NO_HIT_ANSWER, RagResult
|
from app.services.rag_service import NO_HIT_ANSWER, RagResult
|
||||||
|
|
||||||
@@ -27,17 +27,19 @@ class ModelCompletion:
|
|||||||
class ModelClientService:
|
class ModelClientService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def complete(db: Session, rag_result: RagResult) -> ModelCompletion:
|
def complete(db: Session, rag_result: RagResult) -> ModelCompletion:
|
||||||
|
mock_model_enabled = _system_config_bool(db, "mock_model_enabled", get_settings().mock_model_enabled)
|
||||||
model = db.scalar(
|
model = db.scalar(
|
||||||
select(ModelConfig)
|
select(ModelConfig)
|
||||||
.where(ModelConfig.enabled == 1)
|
.where(ModelConfig.enabled == 1)
|
||||||
.order_by(ModelConfig.id.desc())
|
.order_by(ModelConfig.id.desc())
|
||||||
.limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
settings = get_settings()
|
if mock_model_enabled:
|
||||||
if settings.mock_model_enabled or model is None:
|
|
||||||
model_name = model.model_name if model is not None else "mock-model"
|
model_name = model.model_name if model is not None else "mock-model"
|
||||||
answer = _mock_answer(rag_result)
|
answer = _mock_answer(rag_result)
|
||||||
else:
|
else:
|
||||||
|
if model is None:
|
||||||
|
raise ExternalServiceError("未启用可用模型,请先在模型管理中启用一个模型。", provider="model")
|
||||||
model_name = model.model_name
|
model_name = model.model_name
|
||||||
answer = _call_configured_model(model, rag_result)
|
answer = _call_configured_model(model, rag_result)
|
||||||
|
|
||||||
@@ -92,6 +94,13 @@ def _mock_answer(rag_result: RagResult) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _system_config_bool(db: Session, key: str, default: bool) -> bool:
|
||||||
|
config = db.scalar(select(SystemConfig).where(SystemConfig.config_key == key))
|
||||||
|
if config is None or not config.config_value.strip():
|
||||||
|
return default
|
||||||
|
return config.config_value.strip().lower() in {"1", "true", "yes", "on", "启用"}
|
||||||
|
|
||||||
|
|
||||||
def _copy_model_with_overrides(model: ModelConfig, overrides: dict[str, Any]) -> ModelConfig:
|
def _copy_model_with_overrides(model: ModelConfig, overrides: dict[str, Any]) -> ModelConfig:
|
||||||
debug_model = ModelConfig(
|
debug_model = ModelConfig(
|
||||||
provider=model.provider,
|
provider=model.provider,
|
||||||
|
|||||||
@@ -14,9 +14,32 @@ const markdown = new MarkdownIt({
|
|||||||
linkify: true,
|
linkify: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const assistantParts = computed(() => {
|
||||||
|
if (props.role === "user") {
|
||||||
|
return { reasoning: "", answer: props.content };
|
||||||
|
}
|
||||||
|
const matched = props.content.match(/<think>([\s\S]*?)<\/think>/i);
|
||||||
|
if (matched) {
|
||||||
|
return {
|
||||||
|
reasoning: matched[1].trim(),
|
||||||
|
answer: props.content.replace(matched[0], "").trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const opening = props.content.match(/<think>([\s\S]*)$/i);
|
||||||
|
if (opening) {
|
||||||
|
return {
|
||||||
|
reasoning: opening[1].trim(),
|
||||||
|
answer: props.content.slice(0, opening.index).trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { reasoning: "", answer: props.content };
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderedReasoning = computed(() => markdown.render(assistantParts.value.reasoning || ""));
|
||||||
|
|
||||||
const renderedContent = computed(() => {
|
const renderedContent = computed(() => {
|
||||||
if (props.role === "user") return props.content;
|
if (props.role === "user") return props.content;
|
||||||
return markdown.render(props.content || "");
|
return markdown.render(assistantParts.value.answer || "");
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -24,7 +47,13 @@ const renderedContent = computed(() => {
|
|||||||
<article class="message" :class="role">
|
<article class="message" :class="role">
|
||||||
<div class="avatar">{{ role === "assistant" ? "AI" : "我" }}</div>
|
<div class="avatar">{{ role === "assistant" ? "AI" : "我" }}</div>
|
||||||
<div class="bubble">
|
<div class="bubble">
|
||||||
<div v-if="role === 'assistant'" class="content markdown-content" v-html="renderedContent"></div>
|
<template v-if="role === 'assistant'">
|
||||||
|
<details v-if="assistantParts.reasoning" class="reasoning-panel" :open="streaming">
|
||||||
|
<summary>{{ streaming ? "思考中" : "思考过程" }}</summary>
|
||||||
|
<div class="markdown-content reasoning-content" v-html="renderedReasoning"></div>
|
||||||
|
</details>
|
||||||
|
<div class="content markdown-content" v-html="renderedContent"></div>
|
||||||
|
</template>
|
||||||
<div v-else class="content">{{ renderedContent }}</div>
|
<div v-else class="content">{{ renderedContent }}</div>
|
||||||
<span v-if="streaming" class="typing">正在生成</span>
|
<span v-if="streaming" class="typing">正在生成</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -291,6 +291,33 @@ button:disabled {
|
|||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reasoning-panel {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #dbe7e3;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #f2f7f5;
|
||||||
|
color: #496259;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reasoning-panel summary {
|
||||||
|
min-height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
color: #315d52;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reasoning-content {
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 0 10px 10px;
|
||||||
|
color: #5d7169;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.markdown-content > :first-child {
|
.markdown-content > :first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user