Add selectable scopes and models to RAG playground
This commit is contained in:
parent
1847eaefbe
commit
93985976d6
5 changed files with 135 additions and 2 deletions
|
|
@ -14,6 +14,7 @@ const queryMode = document.getElementById("queryMode");
|
||||||
const queryIntent = document.getElementById("queryIntent");
|
const queryIntent = document.getElementById("queryIntent");
|
||||||
const queryOperation = document.getElementById("queryOperation");
|
const queryOperation = document.getElementById("queryOperation");
|
||||||
const answerModel = document.getElementById("answerModel");
|
const answerModel = document.getElementById("answerModel");
|
||||||
|
const useModelInRetrieve = document.getElementById("useModelInRetrieve");
|
||||||
const scopePresetSelect = document.getElementById("scopePresetSelect");
|
const scopePresetSelect = document.getElementById("scopePresetSelect");
|
||||||
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
||||||
const scopeTags = document.getElementById("scopeTags");
|
const scopeTags = document.getElementById("scopeTags");
|
||||||
|
|
@ -83,6 +84,30 @@ async function loadScopes() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadAnswerModels() {
|
||||||
|
try {
|
||||||
|
const payload = await fetch("/models/answer").then((response) => response.json());
|
||||||
|
answerModel.innerHTML = "";
|
||||||
|
|
||||||
|
for (const model of payload.models || []) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = model;
|
||||||
|
option.textContent = model;
|
||||||
|
answerModel.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.defaultModel) {
|
||||||
|
answerModel.value = payload.defaultModel;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
answerModel.innerHTML = "";
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = "openai/gpt-4.1-mini";
|
||||||
|
option.textContent = "openai/gpt-4.1-mini";
|
||||||
|
answerModel.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scopePresetSelect.addEventListener("change", () => {
|
scopePresetSelect.addEventListener("change", () => {
|
||||||
if (!scopePresetSelect.value) {
|
if (!scopePresetSelect.value) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -134,6 +159,7 @@ queryButton.addEventListener("click", async () => {
|
||||||
intent: queryIntent.value,
|
intent: queryIntent.value,
|
||||||
query: queryInput.value,
|
query: queryInput.value,
|
||||||
model: answerModel.value,
|
model: answerModel.value,
|
||||||
|
useModelInRetrieve: useModelInRetrieve.checked,
|
||||||
scope: {
|
scope: {
|
||||||
sourceRef: scopeSourceRef.value,
|
sourceRef: scopeSourceRef.value,
|
||||||
tags: splitTags(scopeTags.value)
|
tags: splitTags(scopeTags.value)
|
||||||
|
|
@ -186,3 +212,4 @@ presetCode.addEventListener("click", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
loadScopes();
|
loadScopes();
|
||||||
|
loadAnswerModels();
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,9 @@
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<label>Modelo answer
|
<label>Modelo answer
|
||||||
<input id="answerModel" value="openai/gpt-4.1-mini" />
|
<select id="answerModel">
|
||||||
|
<option value="openai/gpt-4.1-mini">openai/gpt-4.1-mini</option>
|
||||||
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -91,6 +93,11 @@
|
||||||
Comparar tambien con respuesta sin RAG
|
Comparar tambien con respuesta sin RAG
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" id="useModelInRetrieve" />
|
||||||
|
Usar tambien un modelo para sintetizar el resultado de retrieve
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button id="queryButton">Ejecutar consulta</button>
|
<button id="queryButton">Ejecutar consulta</button>
|
||||||
<button id="presetDocs" class="secondary">Preset docs</button>
|
<button id="presetDocs" class="secondary">Preset docs</button>
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,18 @@ export function createApp() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/models/answer", async (_req, res) => {
|
||||||
|
try {
|
||||||
|
const models = await answerService.listAvailableAnswerModels();
|
||||||
|
res.json({
|
||||||
|
defaultModel: env.answerModel,
|
||||||
|
models
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown answer models error" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.post("/ingest", async (req, res) => {
|
app.post("/ingest", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await ingestService.ingest(req.body);
|
const result = await ingestService.ingest(req.body);
|
||||||
|
|
@ -72,6 +84,8 @@ export function createApp() {
|
||||||
const mode = (req.body.mode ?? "auto") as ChunkMode;
|
const mode = (req.body.mode ?? "auto") as ChunkMode;
|
||||||
const intent = (req.body.intent ?? "specific") as RetrieveIntent;
|
const intent = (req.body.intent ?? "specific") as RetrieveIntent;
|
||||||
const query = String(req.body.query ?? "");
|
const query = String(req.body.query ?? "");
|
||||||
|
const model = req.body.model ? String(req.body.model) : undefined;
|
||||||
|
const useModel = Boolean(req.body.useModelInRetrieve);
|
||||||
const scope: RetrieveScope | undefined = req.body.scope
|
const scope: RetrieveScope | undefined = req.body.scope
|
||||||
? {
|
? {
|
||||||
sourceId: req.body.scope.sourceId,
|
sourceId: req.body.scope.sourceId,
|
||||||
|
|
@ -80,6 +94,15 @@ export function createApp() {
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const result = await retrieveService.retrieve(mode, intent, query, scope);
|
const result = await retrieveService.retrieve(mode, intent, query, scope);
|
||||||
|
if (useModel) {
|
||||||
|
const modelSummary = await answerService.summarizeRetrieve(query, result, model);
|
||||||
|
res.json({
|
||||||
|
...result,
|
||||||
|
model: modelSummary.model,
|
||||||
|
modelSummary: modelSummary.summary
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown retrieve error" });
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown retrieve error" });
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import OpenAI from "openai";
|
import OpenAI from "openai";
|
||||||
import { env } from "../../config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
import type { AnswerResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
import type { AnswerResponse, ChunkMode, DirectAnswerResponse, RetrieveIntent, RetrieveResponse, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||||
import { RetrieveService } from "../retrieve/service.js";
|
import { RetrieveService } from "../retrieve/service.js";
|
||||||
|
|
||||||
|
const answerModelFallbacks = [
|
||||||
|
"openai/gpt-4.1-mini",
|
||||||
|
"qwen/qwen-2.5-72b-instruct",
|
||||||
|
"google/gemini-2.0-flash-001",
|
||||||
|
"anthropic/claude-3.5-haiku",
|
||||||
|
"mistralai/mistral-small-3.1-24b-instruct"
|
||||||
|
];
|
||||||
|
|
||||||
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
|
function buildPrompt(query: string, summary: string, items: RetrievedItem[]): string {
|
||||||
const context = items
|
const context = items
|
||||||
.map((item, index) => [
|
.map((item, index) => [
|
||||||
|
|
@ -104,4 +112,70 @@ export class AnswerService {
|
||||||
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
|
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async summarizeRetrieve(query: string, retrieved: RetrieveResponse, modelOverride?: string): Promise<{ model: string; summary: string }> {
|
||||||
|
if (!env.answerApiKey) {
|
||||||
|
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||||
|
}
|
||||||
|
|
||||||
|
const answerModel = modelOverride?.trim() || env.answerModel;
|
||||||
|
const completion = await this.client.chat.completions.create({
|
||||||
|
model: answerModel,
|
||||||
|
temperature: 0.2,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: "system",
|
||||||
|
content: "Eres un sintetizador de contexto RAG. No des una respuesta final al usuario; devuelve una panoramica util y breve del contexto recuperado para que otro agente pueda continuar trabajando con el."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: "user",
|
||||||
|
content: [
|
||||||
|
`Consulta original: ${query}`,
|
||||||
|
`Intent: ${retrieved.intent}`,
|
||||||
|
`Resumen base: ${retrieved.summary}`,
|
||||||
|
`Temas: ${retrieved.topics.join(", ")}`,
|
||||||
|
`Puntos criticos: ${retrieved.criticalPoints.join(", ")}`,
|
||||||
|
"Fragmentos recuperados:",
|
||||||
|
retrieved.items.map((item, index) => [
|
||||||
|
`Fuente ${index + 1}: ${item.title}`,
|
||||||
|
item.sectionTitle ? `section_title: ${item.sectionTitle}` : undefined,
|
||||||
|
item.startLine ? `line_range: ${item.startLine}-${item.endLine ?? item.startLine}` : undefined,
|
||||||
|
item.content
|
||||||
|
].filter(Boolean).join("\n")).join("\n\n---\n\n")
|
||||||
|
].join("\n\n")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
model: answerModel,
|
||||||
|
summary: completion.choices[0]?.message?.content?.trim() || retrieved.summary
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async listAvailableAnswerModels(): Promise<string[]> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${env.answerBaseUrl}/models`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${env.answerApiKey}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Model list request failed with ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await response.json() as { data?: Array<{ id?: string }> };
|
||||||
|
const ids = (payload.data ?? [])
|
||||||
|
.map((item) => item.id)
|
||||||
|
.filter((id): id is string => Boolean(id))
|
||||||
|
.filter((id) => !/embed/i.test(id));
|
||||||
|
|
||||||
|
const preferred = answerModelFallbacks.filter((id) => ids.includes(id));
|
||||||
|
const others = ids.filter((id) => !preferred.includes(id)).sort();
|
||||||
|
return [...preferred, ...others].slice(0, 80);
|
||||||
|
} catch {
|
||||||
|
return answerModelFallbacks;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,9 @@ export interface AvailableScope {
|
||||||
export interface RetrieveResponse {
|
export interface RetrieveResponse {
|
||||||
mode: ChunkMode;
|
mode: ChunkMode;
|
||||||
intent: RetrieveIntent;
|
intent: RetrieveIntent;
|
||||||
|
model?: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
|
modelSummary?: string;
|
||||||
topics: string[];
|
topics: string[];
|
||||||
criticalPoints: string[];
|
criticalPoints: string[];
|
||||||
items: RetrievedItem[];
|
items: RetrievedItem[];
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue