se agrega wh en asignaturas y se guardan campos correctamente y ademas se corrige incidencia Agregar contexto de la asignatura al chat #55
This commit is contained in:
@@ -3,7 +3,7 @@ import { corsHeaders, withCors } from "./lib/cors.ts";
|
||||
import { HttpError, jsonResponse } from "./lib/errors.ts";
|
||||
import { getOpenAI } from "./lib/openai.ts";
|
||||
import { getSupabaseServiceClient, requireUser } from "./lib/supabase.ts";
|
||||
import { assertUuid, pickSchemaFields, safePlanForPrompt,pickSchemaAsignaturaFields } from "./lib/plan.ts";
|
||||
import { assertUuid, pickSchemaFields, safePlanForPrompt,pickSchemaAsignaturaFields, safeAsignaturaForPrompt, getAsignaturaSystemPrompt } from "./lib/plan.ts";
|
||||
import { OpenAIService } from "../_shared/openai-service.ts";
|
||||
|
||||
type CreateBody = {
|
||||
@@ -309,8 +309,8 @@ app.post(`${prefix}/conversations/asignatura/:id/messages`, async (c) => {
|
||||
|
||||
const asignatura = (row as any).asignaturas;
|
||||
const definicion = asignatura?.estructuras_asignatura?.definicion;
|
||||
const isStructured = !!definicion && (body.campos?.length ?? 0) > 0;
|
||||
|
||||
const campos = body.campos ?? [];
|
||||
const isStructured = !!definicion && campos.length > 0;
|
||||
// 2. Insertar el mensaje en estado PROCESANDO (para que el front vea el spinner)
|
||||
const { data: mensajeInsertado, error: insertErr } = await supabase
|
||||
.from("asignatura_mensajes_ia")
|
||||
@@ -339,6 +339,8 @@ app.post(`${prefix}/conversations/asignatura/:id/messages`, async (c) => {
|
||||
additionalProperties: false
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 4. Llamada asincrónica con background: true
|
||||
const aiResult = await svc.createStructuredResponse({
|
||||
conversation: row.openai_conversation_id,
|
||||
@@ -353,17 +355,12 @@ app.post(`${prefix}/conversations/asignatura/:id/messages`, async (c) => {
|
||||
text: {
|
||||
format: {
|
||||
type: "json_schema",
|
||||
name: isStructured ? "mejora_asignatura" : "respuesta_basica",
|
||||
name: "mejoras_asignatura",
|
||||
schema: schema
|
||||
}
|
||||
},
|
||||
input: [
|
||||
{
|
||||
role: "system",
|
||||
content: isStructured
|
||||
? `Asistente de asignatura. Datos: ${JSON.stringify(asignatura)}`
|
||||
: "Eres un experto en diseño curricular."
|
||||
},
|
||||
{ role: "system", content: getAsignaturaSystemPrompt(asignatura, campos) },
|
||||
{ role: "user", content: body.content },
|
||||
],
|
||||
});
|
||||
|
||||
@@ -49,46 +49,54 @@ export function pickSchemaAsignaturaFields(
|
||||
definicion: any,
|
||||
campos: string[],
|
||||
) {
|
||||
// 1. Validación inicial
|
||||
console.log(campos);
|
||||
console.log(definicion);
|
||||
if (!definicion || definicion.type !== "object" || !definicion.properties) {
|
||||
return {
|
||||
type: "object",
|
||||
properties: {
|
||||
"ai-message": { type: "string" },
|
||||
"is_refusal": { type: "boolean" }
|
||||
},
|
||||
required: ["ai-message", "is_refusal"],
|
||||
additionalProperties: false
|
||||
};
|
||||
return definicion;
|
||||
}
|
||||
|
||||
// 2. Filtrar solo las propiedades técnicas que el usuario pidió
|
||||
const filteredProperties = Object.fromEntries(
|
||||
Object.entries(definicion.properties).filter(([k]) => campos.includes(k))
|
||||
);
|
||||
|
||||
// 3. RECONSTRUIR el esquema incluyendo SIEMPRE los campos de control
|
||||
const finalSchema = {
|
||||
type: "object",
|
||||
// Campos extra de control
|
||||
const extra = {
|
||||
properties: {
|
||||
"ai-message": {
|
||||
type: "string",
|
||||
description: "Tu respuesta conversacional dirigida al profesor."
|
||||
description: "Tu respuesta conversacional dirigida al profesor explicando qué mejoraste.",
|
||||
},
|
||||
"is_refusal": {
|
||||
type: "boolean",
|
||||
description: "Indica si la solicitud es inapropiada o no relacionada."
|
||||
description: "Indica si la solicitud es inapropiada.",
|
||||
},
|
||||
...filteredProperties // Aquí entran objetivo, contenido, etc.
|
||||
},
|
||||
// Forzamos que ai-message e is_refusal sean obligatorios siempre
|
||||
required: ["ai-message", "is_refusal", ...Object.keys(filteredProperties)],
|
||||
additionalProperties: false
|
||||
};
|
||||
|
||||
return finalSchema;
|
||||
}
|
||||
const out = structuredClone(definicion);
|
||||
|
||||
// FILTRADO: Si el usuario pidió campos específicos, filtramos.
|
||||
// Si campos está vacío, la lógica de Planes devolvería todo,
|
||||
// pero aquí asumimos que siempre vienen campos si isStructured es true.
|
||||
const entries = Object.entries(out.properties).filter(([k]) =>
|
||||
campos.includes(k)
|
||||
);
|
||||
|
||||
out.properties = Object.fromEntries(entries);
|
||||
|
||||
if (Array.isArray(out.required)) {
|
||||
out.required = out.required.filter((k: string) => campos.includes(k));
|
||||
}
|
||||
|
||||
// SIEMPRE agregamos los campos de control (ai-message, is_refusal)
|
||||
out.properties = { ...out.properties, ...extra.properties };
|
||||
|
||||
// Reconstruimos los requeridos para que OpenAI sepa que DEBE llenar los campos solicitados
|
||||
out.required = Array.isArray(out.required)
|
||||
? [...new Set([...out.required, ...Object.keys(extra.properties)])]
|
||||
: Object.keys(extra.properties);
|
||||
|
||||
// Forzamos que no invente campos
|
||||
out.additionalProperties = false;
|
||||
|
||||
return out;
|
||||
}
|
||||
export function safePlanForPrompt(plan: any) {
|
||||
const copy = structuredClone(plan);
|
||||
if (copy?.estructuras_plan) delete copy.estructuras_plan;
|
||||
@@ -101,3 +109,40 @@ export function assertUuid(v: string, name: string) {
|
||||
throw new HttpError(400, "bad_input", `Invalid ${name}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function safeAsignaturaForPrompt(asignatura: any) {
|
||||
const copy = structuredClone(asignatura);
|
||||
// Eliminamos la definición de la estructura para que no ensucie el prompt
|
||||
// y solo queden los datos reales de la asignatura
|
||||
if (copy?.estructuras_asignatura) delete copy.estructuras_asignatura;
|
||||
return copy;
|
||||
}
|
||||
|
||||
export function getAsignaturaSystemPrompt(asignatura: any, campos: string[]) {
|
||||
const asignaturaLimpia = safeAsignaturaForPrompt(asignatura);
|
||||
const nombreAsig = asignaturaLimpia?.nombre || "la asignatura";
|
||||
|
||||
if (campos.length === 0) {
|
||||
return `Eres un asistente experto en diseño curricular.
|
||||
Tu objetivo es ayudar al profesor a mejorar su asignatura: "${nombreAsig}".
|
||||
DATOS ACTUALES: ${JSON.stringify(asignaturaLimpia)}.
|
||||
|
||||
COMPORTAMIENTO:
|
||||
- El usuario aún no ha seleccionado campos específicos para mejorar.
|
||||
- NO propongas cambios técnicos detallados ni rellenes campos del JSON todavía.
|
||||
- Saluda cordialmente y menciona qué partes de esta asignatura puedes ayudar a mejorar (objetivos, contenidos, criterios, etc.).
|
||||
- Mantén una conversación fluida y espera a que el usuario elija qué quiere trabajar.`;
|
||||
}
|
||||
|
||||
return `Eres un asistente experto en diseño curricular trabajando sobre: "${nombreAsig}".
|
||||
DATOS ACTUALES: ${JSON.stringify(asignaturaLimpia)}.
|
||||
|
||||
TAREA CRÍTICA:
|
||||
El usuario ha solicitado mejorar estos ${campos.length} campos: ${campos.join(", ")}.
|
||||
|
||||
REGLAS DE ORO:
|
||||
1. Debes proporcionar una propuesta de mejora para CADA UNO de los campos solicitados.
|
||||
2. No omitas ninguno. Si un campo no requiere cambios drásticos, optimiza su redacción técnica.
|
||||
3. En el JSON, cada campo debe contener tu propuesta de texto mejorado.
|
||||
4. En 'ai-message', resume los cambios hechos en cada uno de los campos solicitados.`;
|
||||
}
|
||||
Reference in New Issue
Block a user