Skip to content

Commit 970124b

Browse files
committed
Merge remote-tracking branch 'origin/change-model' into develop
2 parents dae372a + c647c16 commit 970124b

File tree

3 files changed

+150
-66
lines changed

3 files changed

+150
-66
lines changed

src/app/api/chat/route.ts

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,60 @@
1-
import { getAgent } from "@/services/RagAgent";
2-
//import { LangChainAdapter } from "ai";
3-
import { toBaseMessages, toUIMessageStream } from "@ai-sdk/langchain";
4-
import { createUIMessageStreamResponse, UIMessage } from "ai";
5-
import { AIMessageChunk } from "@langchain/core/messages";
1+
import { getAgent } from '@/services/RagAgent';
2+
import { HumanMessage, AIMessage } from '@langchain/core/messages';
63

74
export const maxDuration = 60;
85

96
export async function POST(req: Request) {
107
try {
11-
const { messages }: { messages: UIMessage[] } = await req.json();
8+
const body = await req.json();
9+
const { messages } = body;
1210

13-
const agent = await getAgent();
14-
// Convert to LangChain format
15-
const langchainMessages = await toBaseMessages(messages);
11+
if (!messages || !Array.isArray(messages)) {
12+
return new Response(
13+
JSON.stringify({ error: 'Invalid messages format' }),
14+
{
15+
status: 400,
16+
},
17+
);
18+
}
1619

17-
const stream = await agent.streamEvents(
18-
{ messages: langchainMessages },
19-
{ version: "v2" },
20-
);
20+
const agent = await getAgent();
2121

22-
// Create a simple text stream that yields AIMessageChunks
23-
// This ensures only the final text reaches the client, keeping history clean
24-
const textStream = async function* () {
25-
for await (const event of stream) {
26-
if (
27-
event.event === "on_chat_model_stream" &&
28-
event.data.chunk?.content
29-
) {
30-
const content = event.data.chunk.content;
31-
if (typeof content === "string" && content.length > 0) {
32-
yield new AIMessageChunk({ content });
33-
}
34-
}
35-
}
36-
};
37-
38-
return createUIMessageStreamResponse({
39-
stream: toUIMessageStream(textStream()),
22+
// Conversión manual a formato LangChain para evitar fallos de toBaseMessages
23+
const langchainMessages = messages.map((m: any) => {
24+
if (m.role === 'user') return new HumanMessage(m.content);
25+
if (m.role === 'assistant') return new AIMessage(m.content);
26+
return new HumanMessage(m.content);
4027
});
28+
29+
// Invocación única en lugar de streaming
30+
const response = await agent.invoke({ messages: langchainMessages });
31+
32+
// Extraer el contenido del último mensaje generado por el agente
33+
const lastMessage = response.messages[response.messages.length - 1];
34+
35+
// El contenido puede ser un string o un array de partes
36+
const content =
37+
typeof lastMessage.content === 'string'
38+
? lastMessage.content
39+
: Array.isArray(lastMessage.content)
40+
? lastMessage.content.map((p: any) => p.text || '').join('')
41+
: '';
42+
43+
// Devolvemos el mensaje en un formato que el frontend pueda procesar.
44+
return new Response(
45+
JSON.stringify({
46+
id: crypto.randomUUID(),
47+
role: 'assistant',
48+
content: content,
49+
createdAt: new Date(),
50+
}),
51+
{
52+
headers: { 'Content-Type': 'application/json' },
53+
},
54+
);
4155
} catch (error) {
42-
console.error("❌ [Server Action] Error in sendChatRequest:", error);
43-
return new Response(JSON.stringify({ error: "Error processing request" }), {
56+
console.error('❌ [Server Action] Error in chat API:', error);
57+
return new Response(JSON.stringify({ error: 'Error processing request' }), {
4458
status: 500,
4559
});
4660
}

src/providers/ChatProvider.tsx

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,66 @@
1-
"use client";
1+
'use client';
22

3-
import React, { createContext, useContext } from "react";
4-
import { useChat } from "@ai-sdk/react";
3+
import React, { createContext, useContext, useState } from 'react';
54

65
interface ChatContextType {
76
messages: any[];
87
sendMessage: (data: { text: string }) => Promise<void>;
9-
status: string;
8+
status: 'idle' | 'submitted' | 'streaming' | 'ready';
109
}
1110

1211
const ChatContext = createContext<ChatContextType | undefined>(undefined);
1312

1413
export function ChatProvider({ children }: { children: React.ReactNode }) {
15-
const { messages, sendMessage, status } = useChat();
14+
const [messages, setMessages] = useState<any[]>([]);
15+
const [status, setStatus] = useState<
16+
'idle' | 'submitted' | 'streaming' | 'ready'
17+
>('idle');
18+
19+
const sendMessage = async ({ text }: { text: string }) => {
20+
if (!text.trim()) return;
21+
22+
const userMessage = {
23+
id: crypto.randomUUID(),
24+
role: 'user',
25+
content: text,
26+
createdAt: new Date(),
27+
};
28+
29+
setMessages((prev) => [...prev, userMessage]);
30+
setStatus('submitted');
31+
32+
try {
33+
const response = await fetch('/api/chat', {
34+
method: 'POST',
35+
headers: { 'Content-Type': 'application/json' },
36+
body: JSON.stringify({
37+
messages: [...messages, userMessage].map((m) => ({
38+
id: m.id || crypto.randomUUID(),
39+
role: m.role,
40+
content: m.content,
41+
})),
42+
}),
43+
});
44+
45+
if (!response.ok) throw new Error('Failed to send message');
46+
47+
const data = await response.json();
48+
49+
const assistantMessage = {
50+
id: data.id || crypto.randomUUID(),
51+
role: 'assistant',
52+
content: data.content || data.message,
53+
createdAt: new Date(),
54+
};
55+
56+
setMessages((prev) => [...prev, assistantMessage]);
57+
setStatus('ready');
58+
} catch (error) {
59+
console.error('Error in sendMessage:', error);
60+
setStatus('idle');
61+
throw error;
62+
}
63+
};
1664

1765
return (
1866
<ChatContext.Provider value={{ messages, sendMessage, status }}>
@@ -24,7 +72,7 @@ export function ChatProvider({ children }: { children: React.ReactNode }) {
2472
export function useChatContext() {
2573
const context = useContext(ChatContext);
2674
if (context === undefined) {
27-
throw new Error("useChatContext must be used within a ChatProvider");
75+
throw new Error('useChatContext must be used within a ChatProvider');
2876
}
2977
return context;
3078
}

src/services/RagAgent.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import { OpenAIEmbeddings } from "@langchain/openai";
2-
import { MemoryVectorStore } from "@langchain/classic/vectorstores/memory";
3-
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
4-
import { Document } from "@langchain/core/documents";
5-
import { tool } from "@langchain/core/tools";
6-
import { z } from "zod";
7-
import { SystemMessage } from "@langchain/core/messages";
8-
import { createAgent } from "langchain";
9-
import { ChatOpenAI } from "@langchain/openai";
1+
import { OpenAIEmbeddings } from '@langchain/openai';
2+
import { MemoryVectorStore } from '@langchain/classic/vectorstores/memory';
3+
import { RecursiveCharacterTextSplitter } from '@langchain/textsplitters';
4+
import { Document } from '@langchain/core/documents';
5+
import { tool } from '@langchain/core/tools';
6+
import { z } from 'zod';
7+
import { SystemMessage } from '@langchain/core/messages';
8+
import { createAgent } from 'langchain';
9+
import { ChatOpenAI } from '@langchain/openai';
1010

1111
// Importamos la documentación generada por Velite
12-
import { docs as veliteDocs } from "@/velite";
12+
import { docs as veliteDocs } from '@/velite';
1313

1414
let agentInstance: any = null;
1515

@@ -18,22 +18,44 @@ export const getAgent = async () => {
1818
return agentInstance;
1919
}
2020

21-
const openAiApiKey = process.env.OPENAI_API_KEY;
22-
if (!openAiApiKey) {
23-
throw new Error("OPENAI_API_KEY is not set");
21+
const openrouterApiKey = process.env.OPENROUTER_API_KEY;
22+
if (!openrouterApiKey) {
23+
throw new Error('OPENROUTER_API_KEY is not set');
2424
}
2525

26+
// const openAiApiKey = process.env.OPENAI_API_KEY;
27+
// if (!openAiApiKey) {
28+
// throw new Error('OPENAI_API_KEY is not set');
29+
// }
30+
2631
// Implementación Óptima: gpt-4o-mini
32+
// const model = new ChatOpenAI({
33+
// model: "gpt-4o-mini",
34+
// apiKey: openAiApiKey,
35+
// temperature: 0,
36+
// });
37+
38+
// Implementación con OpenRouter
2739
const model = new ChatOpenAI({
28-
model: "gpt-4o-mini",
29-
apiKey: openAiApiKey,
40+
model: 'openai/gpt-4o-mini',
3041
temperature: 0,
42+
apiKey: openrouterApiKey,
43+
configuration: {
44+
baseURL: 'https://openrouter.ai/api/v1',
45+
defaultHeaders: {
46+
'HTTP-Referer': 'https://www.bylogos.io', // Opcional, para el ranking de OpenRouter
47+
'X-OpenRouter-Title': 'Logos Cloud Agent', // Opcional
48+
},
49+
},
3150
});
3251

3352
// carga embeddings
3453
const embeddings = new OpenAIEmbeddings({
35-
model: "text-embedding-3-large",
36-
apiKey: openAiApiKey,
54+
model: 'text-embedding-3-large',
55+
apiKey: openrouterApiKey,
56+
configuration: {
57+
baseURL: 'https://openrouter.ai/api/v1',
58+
},
3759
});
3860

3961
// carga vector store
@@ -75,27 +97,27 @@ export const getAgent = async () => {
7597
(doc: Document) =>
7698
`Title: ${doc.metadata.title}\nSource: ${doc.metadata.source}\nContent: ${doc.pageContent}`,
7799
)
78-
.join("\n\n");
100+
.join('\n\n');
79101
return serialized;
80102
},
81103
{
82-
name: "retrieve",
104+
name: 'retrieve',
83105
description:
84-
"Retrieve information from the official LogOS documentation.",
106+
'Retrieve information from the official LogOS documentation.',
85107
schema: retrieveSchema,
86-
responseFormat: "content_and_artifact",
108+
responseFormat: 'content_and_artifact',
87109
},
88110
);
89111

90112
const tools = [retrieve];
91113

92114
const systemPrompt = new SystemMessage(
93-
"Eres LogOS AI, el asistente oficial de la plataforma LogOS. Tu objetivo es ayudar exclusivamente con temas relacionados a la plataforma." +
94-
"\n\nDIRECTRICES DE RESPUESTA:" +
95-
"\n1. Identidad y Saludos: Puedes responder saludos cordiales e identificarte como LogOS AI. Explica que tu función es asistir con la documentación de LogOS." +
96-
"\n2. Búsqueda Obligatoria: Para cualquier consulta sobre LogOS (hardware, protocolos, configuración), DEBES usar siempre 'retrieve'." +
97-
"\n3. Rigidez Externa: Si te preguntan por temas ajenos a LogOS (ej. Google, clima, cultura general), responde amablemente que solo tienes información sobre la plataforma LogOS." +
98-
"\n4. Fallos de Contexto: Si 'retrieve' no devuelve información para un tema de LogOS, indica que esa información específica no está en la documentación oficial, pero no inventes datos."
115+
'Eres LogOS AI, el asistente oficial de la plataforma LogOS. Tu objetivo es ayudar exclusivamente con temas relacionados a la plataforma.' +
116+
'\n\nREGLAS DE RESPUESTA Y FLUJO:' +
117+
"\n1. Primera Interacción (Saludo Obligatorio): Si es el primer mensaje de la conversación, DEBES saludar cordialmente, identificarte como LogOS AI y usar el tool 'retrieve' para responder técnicamente a la consulta. Ejemplo: '¡Hola! Soy LogOS AI... [información técnica del retrieve]'" +
118+
"\n2. Uso de 'retrieve': Para CUALQUIER consulta técnica (qué es, hardware, protocolos), usa SIEMPRE 'retrieve' antes de responder." +
119+
'\n3. Mensajes Posteriores: En el segundo mensaje en adelante, ve directamente a la respuesta técnica solicitada sin repetir el saludo inicial o tu identidad, para mantener la fluidez.' +
120+
'\n4. Rigidez de Alcance: Solo responde sobre LogOS. Si consultan temas ajenos, indica amablemente que tu especialidad es exclusivamente LogOS.',
99121
);
100122

101123
// 5. AGENT CREATION

0 commit comments

Comments
 (0)