Improve playground scope and answer model control
This commit is contained in:
parent
c641286372
commit
1847eaefbe
9 changed files with 202 additions and 14 deletions
|
|
@ -47,6 +47,10 @@ Respuesta esperada aproximada:
|
||||||
"provider": "openrouter",
|
"provider": "openrouter",
|
||||||
"model": "qwen/qwen3-embedding-8b"
|
"model": "qwen/qwen3-embedding-8b"
|
||||||
},
|
},
|
||||||
|
"answer": {
|
||||||
|
"provider": "openrouter",
|
||||||
|
"model": "openai/gpt-4.1-mini"
|
||||||
|
},
|
||||||
"vectorStore": {
|
"vectorStore": {
|
||||||
"ok": true,
|
"ok": true,
|
||||||
"kind": "qdrant"
|
"kind": "qdrant"
|
||||||
|
|
@ -137,6 +141,7 @@ Payload base:
|
||||||
{
|
{
|
||||||
"mode": "documental",
|
"mode": "documental",
|
||||||
"intent": "specific",
|
"intent": "specific",
|
||||||
|
"model": "openai/gpt-4.1-mini",
|
||||||
"query": "que tenemos pendiente por hacer en este workspace",
|
"query": "que tenemos pendiente por hacer en este workspace",
|
||||||
"scope": {
|
"scope": {
|
||||||
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
|
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
|
||||||
|
|
@ -242,12 +247,15 @@ Es util para:
|
||||||
- respuestas listas para usuario
|
- respuestas listas para usuario
|
||||||
- pruebas rapidas desde n8n o aplicaciones
|
- pruebas rapidas desde n8n o aplicaciones
|
||||||
|
|
||||||
|
Tambien permite indicar el modelo de respuesta explicitamente mediante el campo opcional `model`.
|
||||||
|
|
||||||
Payload base:
|
Payload base:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mode": "documental",
|
"mode": "documental",
|
||||||
"intent": "specific",
|
"intent": "specific",
|
||||||
|
"model": "openai/gpt-4.1-mini",
|
||||||
"query": "que tenemos pendiente por hacer en este workspace",
|
"query": "que tenemos pendiente por hacer en este workspace",
|
||||||
"scope": {
|
"scope": {
|
||||||
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
|
"sourceRef": "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs"
|
||||||
|
|
@ -291,6 +299,7 @@ Respuesta resumida esperada:
|
||||||
{
|
{
|
||||||
"mode": "codigo",
|
"mode": "codigo",
|
||||||
"intent": "specific",
|
"intent": "specific",
|
||||||
|
"model": "openai/gpt-4.1-mini",
|
||||||
"answer": "...",
|
"answer": "...",
|
||||||
"summary": "...",
|
"summary": "...",
|
||||||
"topics": ["ids.ts"],
|
"topics": ["ids.ts"],
|
||||||
|
|
@ -311,6 +320,32 @@ Respuesta resumida esperada:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 5. `POST /answer/direct`
|
||||||
|
|
||||||
|
Sirve para pedir una respuesta directa al modelo sin usar contexto del RAG.
|
||||||
|
|
||||||
|
Es util para comparar:
|
||||||
|
- respuesta sin RAG
|
||||||
|
- respuesta con RAG
|
||||||
|
|
||||||
|
Payload base:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"query": "que es este servicio",
|
||||||
|
"model": "openai/gpt-4.1-mini"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Respuesta esperada:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"model": "openai/gpt-4.1-mini",
|
||||||
|
"answer": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Recomendacion practica para n8n
|
## Recomendacion practica para n8n
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,14 @@ El backend lo sirve desde:
|
||||||
3. `retrieve`
|
3. `retrieve`
|
||||||
4. `answer`
|
4. `answer`
|
||||||
5. `answer` sin RAG para comparar impacto del contexto
|
5. `answer` sin RAG para comparar impacto del contexto
|
||||||
|
6. seleccion explicita del modelo de `answer`
|
||||||
|
|
||||||
Tambien permite:
|
Tambien permite:
|
||||||
|
|
||||||
- cambiar `mode`
|
- cambiar `mode`
|
||||||
- cambiar `intent`
|
- cambiar `intent`
|
||||||
- ajustar `scope`
|
- ajustar `scope`
|
||||||
|
- seleccionar el modelo de respuesta
|
||||||
- usar presets para docs, docs del modulo y codigo del RAG
|
- usar presets para docs, docs del modulo y codigo del RAG
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ const queryInput = document.getElementById("queryInput");
|
||||||
const queryMode = document.getElementById("queryMode");
|
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 scopePresetSelect = document.getElementById("scopePresetSelect");
|
||||||
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
const scopeSourceRef = document.getElementById("scopeSourceRef");
|
||||||
const scopeTags = document.getElementById("scopeTags");
|
const scopeTags = document.getElementById("scopeTags");
|
||||||
const compareWithoutRag = document.getElementById("compareWithoutRag");
|
const compareWithoutRag = document.getElementById("compareWithoutRag");
|
||||||
|
|
@ -30,6 +32,11 @@ function splitTags(value) {
|
||||||
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildScopeLabel(scope) {
|
||||||
|
const modes = scope.chunkModes.join(", ") || "sin modo";
|
||||||
|
return `${scope.sourceRef} [${modes}]`;
|
||||||
|
}
|
||||||
|
|
||||||
async function request(url, payload, method = "POST") {
|
async function request(url, payload, method = "POST") {
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
|
|
@ -47,6 +54,51 @@ async function request(url, payload, method = "POST") {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadScopes() {
|
||||||
|
try {
|
||||||
|
const scopes = await fetch("/sources").then((response) => response.json());
|
||||||
|
scopePresetSelect.innerHTML = "";
|
||||||
|
|
||||||
|
const placeholder = document.createElement("option");
|
||||||
|
placeholder.value = "";
|
||||||
|
placeholder.textContent = "Selecciona un scope disponible";
|
||||||
|
scopePresetSelect.appendChild(placeholder);
|
||||||
|
|
||||||
|
for (const scope of scopes) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = JSON.stringify(scope);
|
||||||
|
option.textContent = buildScopeLabel(scope);
|
||||||
|
scopePresetSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scopes.length === 0) {
|
||||||
|
placeholder.textContent = "No hay scopes detectados";
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
scopePresetSelect.innerHTML = "";
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = "";
|
||||||
|
option.textContent = `Error cargando scopes: ${error}`;
|
||||||
|
scopePresetSelect.appendChild(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scopePresetSelect.addEventListener("change", () => {
|
||||||
|
if (!scopePresetSelect.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scope = JSON.parse(scopePresetSelect.value);
|
||||||
|
scopeSourceRef.value = scope.sourceRef || "";
|
||||||
|
scopeTags.value = (scope.tags || []).join(", ");
|
||||||
|
|
||||||
|
if (scope.chunkModes.includes("codigo")) {
|
||||||
|
queryMode.value = "codigo";
|
||||||
|
} else if (scope.chunkModes.includes("documental")) {
|
||||||
|
queryMode.value = "documental";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
healthButton.addEventListener("click", async () => {
|
healthButton.addEventListener("click", async () => {
|
||||||
healthResult.textContent = "Comprobando...";
|
healthResult.textContent = "Comprobando...";
|
||||||
try {
|
try {
|
||||||
|
|
@ -81,6 +133,7 @@ queryButton.addEventListener("click", async () => {
|
||||||
mode: queryMode.value,
|
mode: queryMode.value,
|
||||||
intent: queryIntent.value,
|
intent: queryIntent.value,
|
||||||
query: queryInput.value,
|
query: queryInput.value,
|
||||||
|
model: answerModel.value,
|
||||||
scope: {
|
scope: {
|
||||||
sourceRef: scopeSourceRef.value,
|
sourceRef: scopeSourceRef.value,
|
||||||
tags: splitTags(scopeTags.value)
|
tags: splitTags(scopeTags.value)
|
||||||
|
|
@ -92,12 +145,13 @@ queryButton.addEventListener("click", async () => {
|
||||||
const data = await request(endpoint, payload);
|
const data = await request(endpoint, payload);
|
||||||
mainResult.textContent = format(data);
|
mainResult.textContent = format(data);
|
||||||
|
|
||||||
if (compareWithoutRag.checked && queryOperation.value === "answer") {
|
if (compareWithoutRag.checked && queryOperation.value === "answer") {
|
||||||
const comparison = await request("/answer/direct", {
|
const comparison = await request("/answer/direct", {
|
||||||
query: queryInput.value
|
query: queryInput.value,
|
||||||
});
|
model: answerModel.value
|
||||||
compareResult.textContent = format(comparison);
|
});
|
||||||
}
|
compareResult.textContent = format(comparison);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
mainResult.textContent = String(error);
|
mainResult.textContent = String(error);
|
||||||
compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada.";
|
compareResult.textContent = compareWithoutRag.checked ? String(error) : "Desactivada.";
|
||||||
|
|
@ -110,6 +164,7 @@ presetDocs.addEventListener("click", () => {
|
||||||
queryOperation.value = "answer";
|
queryOperation.value = "answer";
|
||||||
queryInput.value = "que tenemos pendiente por hacer en este workspace";
|
queryInput.value = "que tenemos pendiente por hacer en este workspace";
|
||||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs";
|
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/docs";
|
||||||
|
answerModel.value = "openai/gpt-4.1-mini";
|
||||||
});
|
});
|
||||||
|
|
||||||
presetRagDocs.addEventListener("click", () => {
|
presetRagDocs.addEventListener("click", () => {
|
||||||
|
|
@ -118,6 +173,7 @@ presetRagDocs.addEventListener("click", () => {
|
||||||
queryOperation.value = "retrieve";
|
queryOperation.value = "retrieve";
|
||||||
queryInput.value = "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave";
|
queryInput.value = "dame un mapa inicial del modulo RAG, su arquitectura, decisiones, estado actual y documentos clave";
|
||||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs";
|
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/docs";
|
||||||
|
answerModel.value = "openai/gpt-4.1-mini";
|
||||||
});
|
});
|
||||||
|
|
||||||
presetCode.addEventListener("click", () => {
|
presetCode.addEventListener("click", () => {
|
||||||
|
|
@ -126,4 +182,7 @@ presetCode.addEventListener("click", () => {
|
||||||
queryOperation.value = "answer";
|
queryOperation.value = "answer";
|
||||||
queryInput.value = "como se construye source_id en el rag";
|
queryInput.value = "como se construye source_id en el rag";
|
||||||
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src";
|
scopeSourceRef.value = "/home/pancho/Documentos/Empresa/Desarrollo/IA/RAG/src";
|
||||||
|
answerModel.value = "openai/gpt-4.1-mini";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
loadScopes();
|
||||||
|
|
|
||||||
|
|
@ -67,9 +67,17 @@
|
||||||
<option value="answer">answer</option>
|
<option value="answer">answer</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<label>Modelo answer
|
||||||
|
<input id="answerModel" value="openai/gpt-4.1-mini" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<label>Scope disponible
|
||||||
|
<select id="scopePresetSelect">
|
||||||
|
<option value="">Cargando scopes...</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<label>Scope por sourceRef
|
<label>Scope por sourceRef
|
||||||
<input id="scopeSourceRef" value="/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" />
|
<input id="scopeSourceRef" value="/home/pancho/Documentos/Empresa/Desarrollo/IA/docs" />
|
||||||
</label>
|
</label>
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,25 @@ export function createApp() {
|
||||||
provider: embeddingProvider.providerName,
|
provider: embeddingProvider.providerName,
|
||||||
model: embeddingProvider.modelName
|
model: embeddingProvider.modelName
|
||||||
},
|
},
|
||||||
|
answer: {
|
||||||
|
provider: env.answerProvider,
|
||||||
|
model: env.answerModel
|
||||||
|
},
|
||||||
vectorStore: vectorHealth,
|
vectorStore: vectorHealth,
|
||||||
parsers: supportedParserExtensions(),
|
parsers: supportedParserExtensions(),
|
||||||
chunking: documentalChunkingPolicy
|
chunking: documentalChunkingPolicy
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get("/sources", async (_req, res) => {
|
||||||
|
try {
|
||||||
|
const scopes = await vectorStore.listScopes();
|
||||||
|
res.json(scopes);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown sources 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);
|
||||||
|
|
@ -85,7 +98,8 @@ export function createApp() {
|
||||||
tags: Array.isArray(req.body.scope.tags) ? req.body.scope.tags.map(String) : undefined
|
tags: Array.isArray(req.body.scope.tags) ? req.body.scope.tags.map(String) : undefined
|
||||||
}
|
}
|
||||||
: undefined;
|
: undefined;
|
||||||
const result = await answerService.answer(mode, intent, query, scope);
|
const model = req.body.model ? String(req.body.model) : undefined;
|
||||||
|
const result = await answerService.answer(mode, intent, query, scope, model);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown answer error" });
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown answer error" });
|
||||||
|
|
@ -95,7 +109,8 @@ export function createApp() {
|
||||||
app.post("/answer/direct", async (req, res) => {
|
app.post("/answer/direct", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const query = String(req.body.query ?? "");
|
const query = String(req.body.query ?? "");
|
||||||
const result = await answerService.answerWithoutRag(query);
|
const model = req.body.model ? String(req.body.model) : undefined;
|
||||||
|
const result = await answerService.answerWithoutRag(query, model);
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown direct answer error" });
|
res.status(500).json({ ok: false, error: error instanceof Error ? error.message : "Unknown direct answer error" });
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,15 @@ export class AnswerService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async answer(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope): Promise<AnswerResponse> {
|
async answer(mode: ChunkMode, intent: RetrieveIntent, query: string, scope?: RetrieveScope, modelOverride?: string): Promise<AnswerResponse> {
|
||||||
if (!env.answerApiKey) {
|
if (!env.answerApiKey) {
|
||||||
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
const retrieved = await this.retrieveService.retrieve(mode, intent, query, scope);
|
const retrieved = await this.retrieveService.retrieve(mode, intent, query, scope);
|
||||||
|
const answerModel = modelOverride?.trim() || env.answerModel;
|
||||||
const completion = await this.client.chat.completions.create({
|
const completion = await this.client.chat.completions.create({
|
||||||
model: env.answerModel,
|
model: answerModel,
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
|
|
@ -60,6 +61,7 @@ export class AnswerService {
|
||||||
return {
|
return {
|
||||||
mode: retrieved.mode,
|
mode: retrieved.mode,
|
||||||
intent: retrieved.intent,
|
intent: retrieved.intent,
|
||||||
|
model: answerModel,
|
||||||
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta.",
|
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta.",
|
||||||
summary: retrieved.summary,
|
summary: retrieved.summary,
|
||||||
topics: retrieved.topics,
|
topics: retrieved.topics,
|
||||||
|
|
@ -76,13 +78,14 @@ export class AnswerService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async answerWithoutRag(query: string): Promise<DirectAnswerResponse> {
|
async answerWithoutRag(query: string, modelOverride?: string): Promise<DirectAnswerResponse> {
|
||||||
if (!env.answerApiKey) {
|
if (!env.answerApiKey) {
|
||||||
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
throw new Error("Missing ANSWER_API_KEY for answer provider");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const answerModel = modelOverride?.trim() || env.answerModel;
|
||||||
const completion = await this.client.chat.completions.create({
|
const completion = await this.client.chat.completions.create({
|
||||||
model: env.answerModel,
|
model: answerModel,
|
||||||
temperature: 0.2,
|
temperature: 0.2,
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
|
|
@ -97,7 +100,7 @@ export class AnswerService {
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
model: env.answerModel,
|
model: answerModel,
|
||||||
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
|
answer: completion.choices[0]?.message?.content?.trim() || "No se pudo generar respuesta."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { QdrantClient } from "@qdrant/js-client-rest";
|
import { QdrantClient } from "@qdrant/js-client-rest";
|
||||||
import { env } from "../../config/env.js";
|
import { env } from "../../config/env.js";
|
||||||
import type { IngestedChunk, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
import type { AvailableScope, IngestedChunk, RetrieveScope, RetrievedItem } from "../../shared/types/rag.js";
|
||||||
|
|
||||||
function buildQdrantClient(): QdrantClient {
|
function buildQdrantClient(): QdrantClient {
|
||||||
const url = new URL(env.qdrantUrl);
|
const url = new URL(env.qdrantUrl);
|
||||||
|
|
@ -19,6 +19,7 @@ export interface VectorStoreClient {
|
||||||
ensureCollection(vectorSize: number): Promise<void>;
|
ensureCollection(vectorSize: number): Promise<void>;
|
||||||
upsert(chunks: IngestedChunk[]): Promise<void>;
|
upsert(chunks: IngestedChunk[]): Promise<void>;
|
||||||
search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
search(queryVector: number[], limit: number, mode?: string, scope?: RetrieveScope): Promise<RetrievedItem[]>;
|
||||||
|
listScopes(): Promise<AvailableScope[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSearchFilter(mode?: string, scope?: RetrieveScope) {
|
function buildSearchFilter(mode?: string, scope?: RetrieveScope) {
|
||||||
|
|
@ -133,4 +134,59 @@ export class QdrantVectorStoreClient implements VectorStoreClient {
|
||||||
endLine: point.payload?.end_line ? Number(point.payload.end_line) : undefined
|
endLine: point.payload?.end_line ? Number(point.payload.end_line) : undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async listScopes(): Promise<AvailableScope[]> {
|
||||||
|
const collections = await this.client.getCollections();
|
||||||
|
const exists = collections.collections.some((collection) => collection.name === env.qdrantCollection);
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const scopeMap = new Map<string, AvailableScope>();
|
||||||
|
let nextOffset = undefined;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const response = await this.client.scroll(env.qdrantCollection, {
|
||||||
|
limit: 128,
|
||||||
|
offset: nextOffset,
|
||||||
|
with_payload: ["source_id", "source_ref", "chunk_mode", "tags"]
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const point of response.points) {
|
||||||
|
const sourceId = String(point.payload?.source_id ?? "");
|
||||||
|
const sourceRef = String(point.payload?.source_ref ?? "");
|
||||||
|
const chunkMode = String(point.payload?.chunk_mode ?? "");
|
||||||
|
const tags = Array.isArray(point.payload?.tags) ? point.payload.tags.map(String) : [];
|
||||||
|
|
||||||
|
if (!sourceId || !sourceRef) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = `${sourceId}::${sourceRef}`;
|
||||||
|
const existing = scopeMap.get(key) ?? {
|
||||||
|
sourceId,
|
||||||
|
sourceRef,
|
||||||
|
chunkModes: [],
|
||||||
|
tags: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((chunkMode === "documental" || chunkMode === "codigo" || chunkMode === "auto") && !existing.chunkModes.includes(chunkMode)) {
|
||||||
|
existing.chunkModes.push(chunkMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of tags) {
|
||||||
|
if (!existing.tags.includes(tag)) {
|
||||||
|
existing.tags.push(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scopeMap.set(key, existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextOffset = response.next_page_offset;
|
||||||
|
} while (nextOffset);
|
||||||
|
|
||||||
|
return [...scopeMap.values()].sort((left, right) => left.sourceRef.localeCompare(right.sourceRef));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,13 @@ export interface RetrieveScope {
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AvailableScope {
|
||||||
|
sourceId: string;
|
||||||
|
sourceRef: string;
|
||||||
|
chunkModes: ChunkMode[];
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface RetrieveResponse {
|
export interface RetrieveResponse {
|
||||||
mode: ChunkMode;
|
mode: ChunkMode;
|
||||||
intent: RetrieveIntent;
|
intent: RetrieveIntent;
|
||||||
|
|
@ -62,6 +69,7 @@ export interface RetrieveResponse {
|
||||||
export interface AnswerResponse {
|
export interface AnswerResponse {
|
||||||
mode: ChunkMode;
|
mode: ChunkMode;
|
||||||
intent: RetrieveIntent;
|
intent: RetrieveIntent;
|
||||||
|
model: string;
|
||||||
answer: string;
|
answer: string;
|
||||||
summary: string;
|
summary: string;
|
||||||
topics: string[];
|
topics: string[];
|
||||||
|
|
|
||||||
|
|
@ -60,8 +60,10 @@ Este archivo registra agentes y sesiones de trabajo de este workspace.
|
||||||
- Pruebas satisfactorias en produccion de `health`, `retrieve` y `answer`, tanto en modo documental como en modo codigo.
|
- Pruebas satisfactorias en produccion de `health`, `retrieve` y `answer`, tanto en modo documental como en modo codigo.
|
||||||
- Deteccion y anotacion como pendiente del cambio futuro del modelo actual de `answer`.
|
- Deteccion y anotacion como pendiente del cambio futuro del modelo actual de `answer`.
|
||||||
- Creacion de `RAG/docs/API_RAG.md` como referencia operativa de la API para conectar rapidamente el servicio desde n8n, agentes y otras aplicaciones.
|
- Creacion de `RAG/docs/API_RAG.md` como referencia operativa de la API para conectar rapidamente el servicio desde n8n, agentes y otras aplicaciones.
|
||||||
|
- Alineacion de las variables de entorno para EasyPanel, dejando `.env.example` ajustado al despliegue esperado y `RAG/.env.easypanel.local` como respaldo operativo local ignorado por Git.
|
||||||
- Implementacion de un playground web interno servido por el propio backend del RAG para probar `health`, `ingest`, `retrieve`, `answer` y comparacion sin RAG.
|
- Implementacion de un playground web interno servido por el propio backend del RAG para probar `health`, `ingest`, `retrieve`, `answer` y comparacion sin RAG.
|
||||||
- Creacion de `RAG/docs/PLAYGROUND.md` para documentar la tecnologia elegida, su ubicacion y su papel dentro del modulo.
|
- Creacion de `RAG/docs/PLAYGROUND.md` para documentar la tecnologia elegida, su ubicacion y su papel dentro del modulo.
|
||||||
|
- Ajuste de la API y del playground para hacer visible y seleccionable el modelo de `answer`, evitando dejarlo oculto como una decision fija del backend.
|
||||||
- Reorganizacion de RAG como modulo raiz independiente con documentacion propia en `RAG/docs/`.
|
- Reorganizacion de RAG como modulo raiz independiente con documentacion propia en `RAG/docs/`.
|
||||||
- Ajuste del indice documental global para reflejar la separacion entre documentacion global y documentacion por tool.
|
- Ajuste del indice documental global para reflejar la separacion entre documentacion global y documentacion por tool.
|
||||||
- Creacion de `docs/TASK.md` para descomponer lineas de trabajo amplias en puntos de analisis y acuerdos.
|
- Creacion de `docs/TASK.md` para descomponer lineas de trabajo amplias en puntos de analisis y acuerdos.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue