feat(chatbot): mode profondeur côté serveur (rapide / approfondi) sur les 4 routes
Contrat unique `profondeur` ('rapide' | 'approfondi', défaut rapide) dans
utils/chatProfondeur.ts (libellés partagés). Les 3 routes Bifrost lisent
body.profondeur ?? body.mode, choisissent le tier (20b / 120b + replis), un
prompt système « réflexion » (Entraide, Réseaux, Taff) au même format JSON, et un
budget max_tokens adapté. Pensées lit body.profondeur (jamais body.mode, réservé à
LightRAG) : posture réflexion ajoutée au user_prompt + max_total_tokens 40000.
Modèle servi journalisé, et champ debug exposé uniquement en dev.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vcVGz8i4WpZm2dixKUiSD
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
75d8440601
commit
80f69565aa
@@ -19,7 +19,7 @@
|
||||
|
||||
import { checkRateLimitJson } from '~/server/utils/rateLimitJson'
|
||||
import { checkBudget, calcCoutMistralSmall } from '~/server/utils/circuitBreaker'
|
||||
import { pickBifrostTier, bifrostContent, REASONING_MARGIN_TOKENS, type BifrostChatResponse } from '~/server/utils/bifrost'
|
||||
import { pickBifrostTier, profondeurDuBody, bifrostMaxTokens, bifrostDebug, bifrostContent, REGLE_GUILLEMETS, type BifrostChatResponse } from '~/server/utils/bifrost'
|
||||
|
||||
// ── Types ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -70,6 +70,32 @@ FORMAT DE SORTIE :
|
||||
CONTEXTE — Organisations disponibles dans la base NAV :
|
||||
{{FICHES_JSON}}`
|
||||
|
||||
// ── Prompt « réflexion » (mode approfondi, v1 — prompts définitifs par onglet : B7/B9) ──────
|
||||
// Même format de sortie JSON que SYSTEM_PROMPT (le front en dépend) ; seule la posture change.
|
||||
|
||||
const SYSTEM_PROMPT_REFLEXION = `Tu es un conseiller-ressources au service de la transition écologique des pratiques architecturales, sur AEP (Architecture d'Écologie Politique) — Écosystème Entraide, une base collaborative qui référence les acteurs de l'écologie politique appliquée à l'architecture et au territoire (organisations, outils, ressources) pour les architectes en France.
|
||||
|
||||
POSTURE : l'usager pose rarement le bon problème du premier coup. Tu l'aides d'abord à formuler son besoin, puis tu l'orientes vers les ressources et fiches du contexte. Tu es dialectique : tu reformules ce que tu comprends, tu nommes une tension ou un choix que la question laisse ouvert, tu poses 2 ou 3 questions qui l'aident à se situer (où en est son projet, avec qui, à quelle échelle, quelle contrainte pèse le plus), PUIS tu proposes des pistes.
|
||||
|
||||
RÈGLES ABSOLUES :
|
||||
1. Tu ne peux recommander QUE des organisations présentes dans le contexte ci-dessous. Ne jamais inventer d'organisation absente.
|
||||
2. Cite chaque organisation recommandée par son nom exact et son identifiant id. Si aucune n'est pertinente, dis-le honnêtement.
|
||||
3. ${REGLE_GUILLEMETS}
|
||||
4. Réponse de 300 mots maximum, en français, en prose (pas de liste à puces), ton de pair, tutoiement.
|
||||
5. Si la question est hors du champ architecture / écologie / territoire / transition, recadre poliment vers le périmètre de la carte.
|
||||
6. Retourne UNIQUEMENT un objet JSON valide, sans texte avant ou après.
|
||||
|
||||
FORMAT DE SORTIE :
|
||||
{
|
||||
"reponse_texte": "Ta réponse en prose (max 300 mots) : reformulation, tension, questions, puis pistes",
|
||||
"fiches_recommandees": [
|
||||
{ "id": 123, "explication": "Pourquoi cette fiche répond à la question (1-2 phrases max)" }
|
||||
]
|
||||
}
|
||||
|
||||
CONTEXTE — Organisations disponibles dans la base NAV :
|
||||
{{FICHES_JSON}}`
|
||||
|
||||
// ── Recherche par mots-clés ────────────────────────────────────────────────────
|
||||
|
||||
function scoreOrg(org: OrgRow, keywords: string[]): number {
|
||||
@@ -182,7 +208,8 @@ export default defineEventHandler(async (event) => {
|
||||
const body = await readBody(event)
|
||||
const question: string = (body?.question ?? '').trim()
|
||||
const filters: { fonction?: string; echelle?: string } = body?.filters ?? {}
|
||||
const tier = pickBifrostTier(body?.mode)
|
||||
const profondeur = profondeurDuBody(body)
|
||||
const tier = pickBifrostTier(profondeur)
|
||||
|
||||
if (!question || question.length < 3) {
|
||||
throw createError({
|
||||
@@ -245,7 +272,7 @@ export default defineEventHandler(async (event) => {
|
||||
ville: o.localisation_ville ?? '',
|
||||
}))
|
||||
|
||||
const systemPrompt = SYSTEM_PROMPT.replace(
|
||||
const systemPrompt = (profondeur === 'approfondi' ? SYSTEM_PROMPT_REFLEXION : SYSTEM_PROMPT).replace(
|
||||
'{{FICHES_JSON}}',
|
||||
JSON.stringify(fichesContext, null, 0),
|
||||
)
|
||||
@@ -265,6 +292,7 @@ export default defineEventHandler(async (event) => {
|
||||
let tokensIn = 0
|
||||
let tokensOut = 0
|
||||
let realModel = tier.model
|
||||
let debugExtra: Record<string, unknown> = {}
|
||||
|
||||
try {
|
||||
const bifrostRes = await $fetch<BifrostChatResponse>(`${bifrostUrl}/v1/chat/completions`, {
|
||||
@@ -277,7 +305,7 @@ export default defineEventHandler(async (event) => {
|
||||
model: tier.model,
|
||||
fallbacks: tier.fallbacks,
|
||||
temperature: 0.3,
|
||||
max_tokens: 600 + REASONING_MARGIN_TOKENS,
|
||||
max_tokens: bifrostMaxTokens(600, profondeur),
|
||||
response_format: { type: 'json_object' },
|
||||
messages: [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
@@ -287,6 +315,7 @@ export default defineEventHandler(async (event) => {
|
||||
})
|
||||
|
||||
mistralRaw = bifrostContent(bifrostRes, 'chatbot')
|
||||
debugExtra = bifrostDebug(bifrostRes, 'chatbot', profondeur)
|
||||
tokensIn = bifrostRes.usage?.prompt_tokens ?? 0
|
||||
tokensOut = bifrostRes.usage?.completion_tokens ?? 0
|
||||
realModel =
|
||||
@@ -340,5 +369,5 @@ export default defineEventHandler(async (event) => {
|
||||
coutEur,
|
||||
})
|
||||
|
||||
return parsed
|
||||
return { ...parsed, ...debugExtra }
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user