sonika-langchain-bot 0.0.15__py3-none-any.whl → 0.0.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sonika-langchain-bot might be problematic. Click here for more details.
- sonika_langchain_bot/langchain_bot_agent.py +260 -186
- {sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/METADATA +1 -1
- {sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/RECORD +6 -6
- {sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/WHEEL +0 -0
- {sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/licenses/LICENSE +0 -0
- {sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Generator, List, Optional, Dict, Any, TypedDict, Annotated
|
|
2
2
|
import asyncio
|
|
3
|
+
import logging
|
|
3
4
|
from langchain.schema import AIMessage, HumanMessage, BaseMessage
|
|
4
5
|
from langchain_core.messages import ToolMessage
|
|
5
6
|
from langchain.text_splitter import CharacterTextSplitter
|
|
@@ -39,6 +40,7 @@ class LangChainBot:
|
|
|
39
40
|
- Thread-based conversation persistence
|
|
40
41
|
- Streaming responses
|
|
41
42
|
- Backward compatibility with legacy APIs
|
|
43
|
+
- Debug logging injection for production troubleshooting
|
|
42
44
|
"""
|
|
43
45
|
|
|
44
46
|
def __init__(self,
|
|
@@ -47,7 +49,8 @@ class LangChainBot:
|
|
|
47
49
|
instructions: str,
|
|
48
50
|
tools: Optional[List[BaseTool]] = None,
|
|
49
51
|
mcp_servers: Optional[Dict[str, Any]] = None,
|
|
50
|
-
use_checkpointer: bool = False
|
|
52
|
+
use_checkpointer: bool = False,
|
|
53
|
+
logger: Optional[logging.Logger] = None):
|
|
51
54
|
"""
|
|
52
55
|
Initialize the modern LangGraph bot with optional MCP support.
|
|
53
56
|
|
|
@@ -58,16 +61,28 @@ class LangChainBot:
|
|
|
58
61
|
tools (List[BaseTool], optional): Traditional LangChain tools to bind to the model
|
|
59
62
|
mcp_servers (Dict[str, Any], optional): MCP server configurations for dynamic tool loading
|
|
60
63
|
use_checkpointer (bool): Enable automatic conversation persistence using LangGraph checkpoints
|
|
64
|
+
logger (logging.Logger, optional): Logger instance for debugging. If None, uses silent NullHandler
|
|
61
65
|
|
|
62
66
|
Note:
|
|
63
67
|
The instructions will be automatically enhanced with tool descriptions
|
|
64
68
|
when tools are provided, eliminating the need for manual tool instruction formatting.
|
|
65
69
|
"""
|
|
70
|
+
# Configure logger (silent by default if not provided)
|
|
71
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
72
|
+
if logger is None:
|
|
73
|
+
self.logger.addHandler(logging.NullHandler())
|
|
74
|
+
|
|
75
|
+
self.logger.info("="*80)
|
|
76
|
+
self.logger.info("🚀 Inicializando LangChainBot")
|
|
77
|
+
self.logger.info("="*80)
|
|
78
|
+
|
|
66
79
|
# Core components
|
|
67
80
|
self.language_model = language_model
|
|
68
81
|
self.embeddings = embeddings
|
|
69
82
|
self.base_instructions = instructions
|
|
70
83
|
|
|
84
|
+
self.logger.debug(f"📋 Instrucciones base: {len(instructions)} caracteres")
|
|
85
|
+
|
|
71
86
|
# Backward compatibility attributes
|
|
72
87
|
self.chat_history: List[BaseMessage] = []
|
|
73
88
|
self.vector_store = None
|
|
@@ -76,25 +91,39 @@ class LangChainBot:
|
|
|
76
91
|
self.tools = tools or []
|
|
77
92
|
self.mcp_client = None
|
|
78
93
|
|
|
94
|
+
self.logger.info(f"🔧 Herramientas iniciales: {len(self.tools)}")
|
|
95
|
+
|
|
79
96
|
# Initialize MCP servers if provided
|
|
80
97
|
if mcp_servers:
|
|
98
|
+
self.logger.info(f"🌐 Servidores MCP detectados: {len(mcp_servers)}")
|
|
81
99
|
self._initialize_mcp(mcp_servers)
|
|
100
|
+
else:
|
|
101
|
+
self.logger.debug("⚪ Sin servidores MCP configurados")
|
|
82
102
|
|
|
83
103
|
# Configure persistence layer
|
|
84
104
|
self.checkpointer = MemorySaver() if use_checkpointer else None
|
|
105
|
+
self.logger.debug(f"💾 Checkpointer: {'Habilitado' if use_checkpointer else 'Deshabilitado'}")
|
|
85
106
|
|
|
86
107
|
# Prepare model with bound tools for native function calling
|
|
108
|
+
self.logger.info("🤖 Preparando modelo con herramientas...")
|
|
87
109
|
self.model_with_tools = self._prepare_model_with_tools()
|
|
88
110
|
|
|
89
111
|
# Build modern instruction set with tool descriptions
|
|
112
|
+
self.logger.info("📝 Construyendo instrucciones modernas...")
|
|
90
113
|
self.instructions = self._build_modern_instructions()
|
|
114
|
+
self.logger.debug(f"📋 Instrucciones finales: {len(self.instructions)} caracteres")
|
|
91
115
|
|
|
92
116
|
# Create the LangGraph workflow
|
|
117
|
+
self.logger.info("🔄 Creando workflow de LangGraph...")
|
|
93
118
|
self.graph = self._create_modern_workflow()
|
|
94
119
|
|
|
95
120
|
# Legacy compatibility attributes (maintained for API compatibility)
|
|
96
121
|
self.conversation = None
|
|
97
122
|
self.agent_executor = None
|
|
123
|
+
|
|
124
|
+
self.logger.info("✅ LangChainBot inicializado correctamente")
|
|
125
|
+
self.logger.info(f"📊 Resumen: {len(self.tools)} herramientas, {len(self.chat_history)} mensajes en historial")
|
|
126
|
+
self.logger.info("="*80 + "\n")
|
|
98
127
|
|
|
99
128
|
def _initialize_mcp(self, mcp_servers: Dict[str, Any]):
|
|
100
129
|
"""
|
|
@@ -117,14 +146,81 @@ class LangChainBot:
|
|
|
117
146
|
MCP tools are automatically appended to the existing tools list and
|
|
118
147
|
will be included in the model's tool binding process.
|
|
119
148
|
"""
|
|
149
|
+
self.logger.info("="*80)
|
|
150
|
+
self.logger.info("🌐 INICIALIZANDO MCP (Model Context Protocol)")
|
|
151
|
+
self.logger.info("="*80)
|
|
152
|
+
|
|
120
153
|
try:
|
|
154
|
+
self.logger.info(f"📋 Servidores a inicializar: {len(mcp_servers)}")
|
|
155
|
+
|
|
156
|
+
for server_name, server_config in mcp_servers.items():
|
|
157
|
+
self.logger.info(f"\n🔌 Servidor: {server_name}")
|
|
158
|
+
self.logger.debug(f" Command: {server_config.get('command')}")
|
|
159
|
+
self.logger.debug(f" Args: {server_config.get('args')}")
|
|
160
|
+
self.logger.debug(f" Transport: {server_config.get('transport')}")
|
|
161
|
+
|
|
162
|
+
self.logger.info("\n🔄 Creando MultiServerMCPClient...")
|
|
121
163
|
self.mcp_client = MultiServerMCPClient(mcp_servers)
|
|
122
|
-
|
|
164
|
+
self.logger.info("✅ MultiServerMCPClient creado")
|
|
165
|
+
|
|
166
|
+
# ===== FIX PARA APACHE/MOD_WSGI =====
|
|
167
|
+
self.logger.info("🔧 Aplicando fix para compatibilidad Apache/mod_wsgi...")
|
|
168
|
+
|
|
169
|
+
import subprocess
|
|
170
|
+
original_create = asyncio.create_subprocess_exec
|
|
171
|
+
|
|
172
|
+
async def fixed_create(*args, stdin=None, stdout=None, stderr=None, **kwargs):
|
|
173
|
+
"""Forzar PIPE para evitar heredar sys.stderr de Apache"""
|
|
174
|
+
return await original_create(
|
|
175
|
+
*args,
|
|
176
|
+
stdin=stdin or subprocess.PIPE,
|
|
177
|
+
stdout=stdout or subprocess.PIPE,
|
|
178
|
+
stderr=stderr or subprocess.PIPE,
|
|
179
|
+
**kwargs
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# Aplicar parche temporalmente
|
|
183
|
+
asyncio.create_subprocess_exec = fixed_create
|
|
184
|
+
self.logger.debug("✅ Parche temporal aplicado a asyncio.create_subprocess_exec")
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
self.logger.info("🔄 Obteniendo herramientas desde servidores MCP...")
|
|
188
|
+
mcp_tools = asyncio.run(self.mcp_client.get_tools())
|
|
189
|
+
self.logger.info(f"📥 Herramientas MCP recibidas: {len(mcp_tools)}")
|
|
190
|
+
finally:
|
|
191
|
+
# Restaurar original
|
|
192
|
+
asyncio.create_subprocess_exec = original_create
|
|
193
|
+
self.logger.debug("✅ Parche temporal removido, asyncio restaurado")
|
|
194
|
+
# =====================================
|
|
195
|
+
|
|
196
|
+
if mcp_tools:
|
|
197
|
+
for i, tool in enumerate(mcp_tools, 1):
|
|
198
|
+
tool_name = getattr(tool, 'name', 'Unknown')
|
|
199
|
+
tool_desc = getattr(tool, 'description', 'Sin descripción')
|
|
200
|
+
self.logger.debug(f" {i}. {tool_name}: {tool_desc[:100]}...")
|
|
201
|
+
|
|
123
202
|
self.tools.extend(mcp_tools)
|
|
124
|
-
|
|
203
|
+
|
|
204
|
+
self.logger.info(f"✅ MCP inicializado exitosamente")
|
|
205
|
+
self.logger.info(f"📊 Total herramientas disponibles: {len(self.tools)}")
|
|
206
|
+
self.logger.info(f" - Herramientas MCP: {len(mcp_tools)}")
|
|
207
|
+
self.logger.info(f" - Herramientas previas: {len(self.tools) - len(mcp_tools)}")
|
|
208
|
+
self.logger.info("="*80 + "\n")
|
|
209
|
+
|
|
125
210
|
except Exception as e:
|
|
126
|
-
|
|
211
|
+
self.logger.error("="*80)
|
|
212
|
+
self.logger.error("❌ ERROR EN INICIALIZACIÓN MCP")
|
|
213
|
+
self.logger.error("="*80)
|
|
214
|
+
self.logger.error(f"Tipo de error: {type(e).__name__}")
|
|
215
|
+
self.logger.error(f"Mensaje: {str(e)}")
|
|
216
|
+
self.logger.exception("Traceback completo:")
|
|
217
|
+
self.logger.error("="*80 + "\n")
|
|
218
|
+
|
|
127
219
|
self.mcp_client = None
|
|
220
|
+
|
|
221
|
+
# Mensaje de diagnóstico
|
|
222
|
+
self.logger.warning("⚠️ Continuando sin MCP - solo herramientas locales disponibles")
|
|
223
|
+
self.logger.warning(f" Herramientas disponibles: {len(self.tools)}")
|
|
128
224
|
|
|
129
225
|
def _prepare_model_with_tools(self):
|
|
130
226
|
"""
|
|
@@ -137,13 +233,31 @@ class LangChainBot:
|
|
|
137
233
|
The language model with tools bound, or the original model if no tools are available
|
|
138
234
|
"""
|
|
139
235
|
if self.tools:
|
|
140
|
-
|
|
141
|
-
|
|
236
|
+
self.logger.info(f"🔗 Vinculando {len(self.tools)} herramientas al modelo")
|
|
237
|
+
try:
|
|
238
|
+
bound_model = self.language_model.model.bind_tools(self.tools)
|
|
239
|
+
self.logger.info("✅ Herramientas vinculadas correctamente")
|
|
240
|
+
return bound_model
|
|
241
|
+
except Exception as e:
|
|
242
|
+
self.logger.error(f"❌ Error vinculando herramientas: {e}")
|
|
243
|
+
self.logger.exception("Traceback:")
|
|
244
|
+
return self.language_model.model
|
|
245
|
+
else:
|
|
246
|
+
self.logger.debug("⚪ Sin herramientas para vincular, usando modelo base")
|
|
247
|
+
return self.language_model.model
|
|
142
248
|
|
|
143
249
|
def _build_modern_instructions(self) -> str:
|
|
250
|
+
"""
|
|
251
|
+
Build modern instructions with automatic tool documentation.
|
|
252
|
+
|
|
253
|
+
Returns:
|
|
254
|
+
str: Enhanced instructions with tool descriptions
|
|
255
|
+
"""
|
|
144
256
|
instructions = self.base_instructions
|
|
145
257
|
|
|
146
258
|
if self.tools:
|
|
259
|
+
self.logger.info(f"📝 Generando documentación para {len(self.tools)} herramientas")
|
|
260
|
+
|
|
147
261
|
tools_description = "\n\n# Available Tools\n\n"
|
|
148
262
|
|
|
149
263
|
for tool in self.tools:
|
|
@@ -157,7 +271,7 @@ class LangChainBot:
|
|
|
157
271
|
required = "**REQUIRED**" if field_info.is_required() else "*optional*"
|
|
158
272
|
tools_description += f"- `{field_name}` ({field_info.annotation.__name__}, {required}): {field_info.description}\n"
|
|
159
273
|
|
|
160
|
-
# Opción 2: args_schema es un dict (MCP Tools)
|
|
274
|
+
# Opción 2: args_schema es un dict (MCP Tools)
|
|
161
275
|
elif hasattr(tool, 'args_schema') and isinstance(tool.args_schema, dict):
|
|
162
276
|
if 'properties' in tool.args_schema:
|
|
163
277
|
tools_description += f"**Parameters:**\n"
|
|
@@ -187,6 +301,7 @@ class LangChainBot:
|
|
|
187
301
|
"- Do NOT call tools with empty arguments\n")
|
|
188
302
|
|
|
189
303
|
instructions += tools_description
|
|
304
|
+
self.logger.info(f"✅ Documentación de herramientas agregada ({len(tools_description)} caracteres)")
|
|
190
305
|
|
|
191
306
|
return instructions
|
|
192
307
|
|
|
@@ -203,24 +318,14 @@ class LangChainBot:
|
|
|
203
318
|
Returns:
|
|
204
319
|
StateGraph: Compiled LangGraph workflow ready for execution
|
|
205
320
|
"""
|
|
321
|
+
self.logger.info("🔄 Construyendo workflow de LangGraph")
|
|
206
322
|
|
|
207
323
|
def agent_node(state: ChatState) -> ChatState:
|
|
208
324
|
"""
|
|
209
325
|
Main agent node responsible for generating responses and initiating tool calls.
|
|
210
|
-
|
|
211
|
-
This node:
|
|
212
|
-
1. Extracts the latest user message from the conversation state
|
|
213
|
-
2. Retrieves relevant context from processed files
|
|
214
|
-
3. Constructs a complete message history for the model
|
|
215
|
-
4. Invokes the model with tool binding for native function calling
|
|
216
|
-
5. Returns updated state with the model's response
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
state (ChatState): Current conversation state
|
|
220
|
-
|
|
221
|
-
Returns:
|
|
222
|
-
ChatState: Updated state with agent response
|
|
223
326
|
"""
|
|
327
|
+
self.logger.debug("🤖 Ejecutando agent_node")
|
|
328
|
+
|
|
224
329
|
# Extract the most recent user message
|
|
225
330
|
last_user_message = None
|
|
226
331
|
for msg in reversed(state["messages"]):
|
|
@@ -229,10 +334,15 @@ class LangChainBot:
|
|
|
229
334
|
break
|
|
230
335
|
|
|
231
336
|
if not last_user_message:
|
|
337
|
+
self.logger.warning("⚠️ No se encontró mensaje de usuario")
|
|
232
338
|
return state
|
|
233
339
|
|
|
340
|
+
self.logger.debug(f"💬 Mensaje usuario: {last_user_message[:100]}...")
|
|
341
|
+
|
|
234
342
|
# Retrieve contextual information from processed files
|
|
235
343
|
context = self._get_context(last_user_message)
|
|
344
|
+
if context:
|
|
345
|
+
self.logger.debug(f"📚 Contexto recuperado: {len(context)} caracteres")
|
|
236
346
|
|
|
237
347
|
# Build system prompt with optional context
|
|
238
348
|
system_content = self.instructions
|
|
@@ -249,23 +359,33 @@ class LangChainBot:
|
|
|
249
359
|
elif isinstance(msg, AIMessage):
|
|
250
360
|
messages.append({"role": "assistant", "content": msg.content or ""})
|
|
251
361
|
elif isinstance(msg, ToolMessage):
|
|
252
|
-
# Convert tool results to user messages for context
|
|
253
362
|
messages.append({"role": "user", "content": f"Tool result: {msg.content}"})
|
|
254
363
|
|
|
364
|
+
self.logger.debug(f"📨 Enviando {len(messages)} mensajes al modelo")
|
|
365
|
+
|
|
255
366
|
try:
|
|
256
367
|
# Invoke model with native tool binding
|
|
257
368
|
response = self.model_with_tools.invoke(messages)
|
|
258
369
|
|
|
370
|
+
self.logger.debug(f"✅ Respuesta recibida del modelo")
|
|
371
|
+
|
|
372
|
+
# Check for tool calls
|
|
373
|
+
if hasattr(response, 'tool_calls') and response.tool_calls:
|
|
374
|
+
self.logger.info(f"🔧 Llamadas a herramientas detectadas: {len(response.tool_calls)}")
|
|
375
|
+
for i, tc in enumerate(response.tool_calls, 1):
|
|
376
|
+
tool_name = tc.get('name', 'Unknown')
|
|
377
|
+
self.logger.debug(f" {i}. {tool_name}")
|
|
378
|
+
|
|
259
379
|
# Return updated state
|
|
260
380
|
return {
|
|
261
381
|
**state,
|
|
262
382
|
"context": context,
|
|
263
|
-
"messages": [response]
|
|
383
|
+
"messages": [response]
|
|
264
384
|
}
|
|
265
385
|
|
|
266
386
|
except Exception as e:
|
|
267
|
-
|
|
268
|
-
|
|
387
|
+
self.logger.error(f"❌ Error en agent_node: {e}")
|
|
388
|
+
self.logger.exception("Traceback:")
|
|
269
389
|
fallback_response = AIMessage(content="I apologize, but I encountered an error processing your request.")
|
|
270
390
|
return {
|
|
271
391
|
**state,
|
|
@@ -276,24 +396,16 @@ class LangChainBot:
|
|
|
276
396
|
def should_continue(state: ChatState) -> str:
|
|
277
397
|
"""
|
|
278
398
|
Conditional edge function to determine workflow continuation.
|
|
279
|
-
|
|
280
|
-
Analyzes the last message to decide whether to execute tools or end the workflow.
|
|
281
|
-
This leverages LangGraph's native tool calling detection.
|
|
282
|
-
|
|
283
|
-
Args:
|
|
284
|
-
state (ChatState): Current conversation state
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
str: Next node to execute ("tools" or "end")
|
|
288
399
|
"""
|
|
289
400
|
last_message = state["messages"][-1]
|
|
290
401
|
|
|
291
|
-
# Check for pending tool calls using native tool calling detection
|
|
292
402
|
if (isinstance(last_message, AIMessage) and
|
|
293
403
|
hasattr(last_message, 'tool_calls') and
|
|
294
404
|
last_message.tool_calls):
|
|
405
|
+
self.logger.debug("➡️ Continuando a ejecución de herramientas")
|
|
295
406
|
return "tools"
|
|
296
407
|
|
|
408
|
+
self.logger.debug("🏁 Finalizando workflow")
|
|
297
409
|
return "end"
|
|
298
410
|
|
|
299
411
|
# Construct the workflow graph
|
|
@@ -301,18 +413,18 @@ class LangChainBot:
|
|
|
301
413
|
|
|
302
414
|
# Add primary agent node
|
|
303
415
|
workflow.add_node("agent", agent_node)
|
|
416
|
+
self.logger.debug("✅ Nodo 'agent' agregado")
|
|
304
417
|
|
|
305
418
|
# Add tool execution node if tools are available
|
|
306
419
|
if self.tools:
|
|
307
|
-
# ToolNode automatically handles tool execution and result formatting
|
|
308
420
|
tool_node = ToolNode(self.tools)
|
|
309
421
|
workflow.add_node("tools", tool_node)
|
|
422
|
+
self.logger.debug("✅ Nodo 'tools' agregado")
|
|
310
423
|
|
|
311
424
|
# Define workflow edges and entry point
|
|
312
425
|
workflow.set_entry_point("agent")
|
|
313
426
|
|
|
314
427
|
if self.tools:
|
|
315
|
-
# Conditional routing based on tool call presence
|
|
316
428
|
workflow.add_conditional_edges(
|
|
317
429
|
"agent",
|
|
318
430
|
should_continue,
|
|
@@ -321,17 +433,21 @@ class LangChainBot:
|
|
|
321
433
|
"end": END
|
|
322
434
|
}
|
|
323
435
|
)
|
|
324
|
-
# Return to agent after tool execution for final response formatting
|
|
325
436
|
workflow.add_edge("tools", "agent")
|
|
437
|
+
self.logger.debug("✅ Edges condicionales configurados")
|
|
326
438
|
else:
|
|
327
|
-
# Direct termination if no tools are available
|
|
328
439
|
workflow.add_edge("agent", END)
|
|
440
|
+
self.logger.debug("✅ Edge directo a END configurado")
|
|
329
441
|
|
|
330
442
|
# Compile workflow with optional checkpointing
|
|
331
443
|
if self.checkpointer:
|
|
332
|
-
|
|
444
|
+
compiled = workflow.compile(checkpointer=self.checkpointer)
|
|
445
|
+
self.logger.info("✅ Workflow compilado con checkpointer")
|
|
333
446
|
else:
|
|
334
|
-
|
|
447
|
+
compiled = workflow.compile()
|
|
448
|
+
self.logger.info("✅ Workflow compilado sin checkpointer")
|
|
449
|
+
|
|
450
|
+
return compiled
|
|
335
451
|
|
|
336
452
|
# ===== LEGACY API COMPATIBILITY =====
|
|
337
453
|
|
|
@@ -355,61 +471,70 @@ class LangChainBot:
|
|
|
355
471
|
This method automatically handles tool execution and context integration
|
|
356
472
|
from processed files while maintaining the original API signature.
|
|
357
473
|
"""
|
|
474
|
+
self.logger.info("="*80)
|
|
475
|
+
self.logger.info("📨 GET_RESPONSE llamado")
|
|
476
|
+
self.logger.debug(f"💬 Input: {user_input[:200]}...")
|
|
477
|
+
|
|
358
478
|
# Prepare initial workflow state
|
|
359
479
|
initial_state = {
|
|
360
480
|
"messages": self.chat_history + [HumanMessage(content=user_input)],
|
|
361
481
|
"context": ""
|
|
362
482
|
}
|
|
363
483
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
484
|
+
self.logger.debug(f"📊 Estado inicial: {len(initial_state['messages'])} mensajes")
|
|
485
|
+
|
|
486
|
+
try:
|
|
487
|
+
# Execute the LangGraph workflow
|
|
488
|
+
self.logger.info("🔄 Ejecutando workflow...")
|
|
489
|
+
result = asyncio.run(self.graph.ainvoke(initial_state))
|
|
490
|
+
self.logger.info("✅ Workflow completado")
|
|
491
|
+
|
|
492
|
+
# Update internal conversation history
|
|
493
|
+
self.chat_history = result["messages"]
|
|
494
|
+
self.logger.debug(f"💾 Historial actualizado: {len(self.chat_history)} mensajes")
|
|
495
|
+
|
|
496
|
+
# Extract final response from the last assistant message
|
|
497
|
+
final_response = ""
|
|
498
|
+
total_input_tokens = 0
|
|
499
|
+
total_output_tokens = 0
|
|
500
|
+
|
|
501
|
+
for msg in reversed(result["messages"]):
|
|
502
|
+
if isinstance(msg, AIMessage) and msg.content:
|
|
503
|
+
final_response = msg.content
|
|
504
|
+
break
|
|
505
|
+
|
|
506
|
+
# Extract token usage from response metadata
|
|
507
|
+
last_message = result["messages"][-1]
|
|
508
|
+
if hasattr(last_message, 'response_metadata'):
|
|
509
|
+
token_usage = last_message.response_metadata.get('token_usage', {})
|
|
510
|
+
total_input_tokens = token_usage.get('prompt_tokens', 0)
|
|
511
|
+
total_output_tokens = token_usage.get('completion_tokens', 0)
|
|
512
|
+
|
|
513
|
+
self.logger.info(f"📊 Tokens: input={total_input_tokens}, output={total_output_tokens}")
|
|
514
|
+
self.logger.info(f"📝 Respuesta: {len(final_response)} caracteres")
|
|
515
|
+
self.logger.info("="*80 + "\n")
|
|
516
|
+
|
|
517
|
+
return ResponseModel(
|
|
518
|
+
user_tokens=total_input_tokens,
|
|
519
|
+
bot_tokens=total_output_tokens,
|
|
520
|
+
response=final_response
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
except Exception as e:
|
|
524
|
+
self.logger.error("="*80)
|
|
525
|
+
self.logger.error("❌ ERROR EN GET_RESPONSE")
|
|
526
|
+
self.logger.error(f"Mensaje: {str(e)}")
|
|
527
|
+
self.logger.exception("Traceback:")
|
|
528
|
+
self.logger.error("="*80 + "\n")
|
|
529
|
+
raise
|
|
395
530
|
|
|
396
531
|
def get_response_stream(self, user_input: str) -> Generator[str, None, None]:
|
|
397
532
|
"""
|
|
398
533
|
Generate a streaming response for real-time user interaction.
|
|
399
|
-
|
|
400
|
-
This method provides streaming capabilities while maintaining backward
|
|
401
|
-
compatibility with the original API.
|
|
402
|
-
|
|
403
|
-
Args:
|
|
404
|
-
user_input (str): The user's message or query
|
|
405
|
-
|
|
406
|
-
Yields:
|
|
407
|
-
str: Response chunks as they are generated
|
|
408
|
-
|
|
409
|
-
Note:
|
|
410
|
-
Current implementation streams complete responses. For token-level
|
|
411
|
-
streaming, consider using the model's native streaming capabilities.
|
|
412
534
|
"""
|
|
535
|
+
self.logger.info("📨 GET_RESPONSE_STREAM llamado")
|
|
536
|
+
self.logger.debug(f"💬 Input: {user_input[:200]}...")
|
|
537
|
+
|
|
413
538
|
initial_state = {
|
|
414
539
|
"messages": self.chat_history + [HumanMessage(content=user_input)],
|
|
415
540
|
"context": ""
|
|
@@ -417,156 +542,105 @@ class LangChainBot:
|
|
|
417
542
|
|
|
418
543
|
accumulated_response = ""
|
|
419
544
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
545
|
+
try:
|
|
546
|
+
for chunk in self.graph.stream(initial_state):
|
|
547
|
+
if "agent" in chunk:
|
|
548
|
+
for message in chunk["agent"]["messages"]:
|
|
549
|
+
if isinstance(message, AIMessage) and message.content:
|
|
550
|
+
accumulated_response = message.content
|
|
551
|
+
yield message.content
|
|
552
|
+
|
|
553
|
+
if accumulated_response:
|
|
554
|
+
self.chat_history.extend([
|
|
555
|
+
HumanMessage(content=user_input),
|
|
556
|
+
AIMessage(content=accumulated_response)
|
|
557
|
+
])
|
|
558
|
+
|
|
559
|
+
self.logger.info(f"✅ Stream completado: {len(accumulated_response)} caracteres")
|
|
560
|
+
|
|
561
|
+
except Exception as e:
|
|
562
|
+
self.logger.error(f"❌ Error en stream: {e}")
|
|
563
|
+
self.logger.exception("Traceback:")
|
|
564
|
+
raise
|
|
436
565
|
|
|
437
566
|
def load_conversation_history(self, messages: List[Message]):
|
|
438
567
|
"""
|
|
439
568
|
Load conversation history from Django model instances.
|
|
440
|
-
|
|
441
|
-
This method maintains compatibility with existing Django-based conversation
|
|
442
|
-
storage while preparing the history for modern LangGraph processing.
|
|
443
|
-
|
|
444
|
-
Args:
|
|
445
|
-
messages (List[Message]): List of Django Message model instances
|
|
446
|
-
Expected to have 'content' and 'is_bot' attributes
|
|
447
569
|
"""
|
|
570
|
+
self.logger.info(f"📥 Cargando historial: {len(messages)} mensajes")
|
|
448
571
|
self.chat_history.clear()
|
|
449
572
|
for message in messages:
|
|
450
573
|
if message.is_bot:
|
|
451
574
|
self.chat_history.append(AIMessage(content=message.content))
|
|
452
575
|
else:
|
|
453
576
|
self.chat_history.append(HumanMessage(content=message.content))
|
|
577
|
+
self.logger.debug("✅ Historial cargado")
|
|
454
578
|
|
|
455
579
|
def save_messages(self, user_message: str, bot_response: str):
|
|
456
580
|
"""
|
|
457
581
|
Save messages to internal conversation history.
|
|
458
|
-
|
|
459
|
-
This method provides backward compatibility for manual history management.
|
|
460
|
-
|
|
461
|
-
Args:
|
|
462
|
-
user_message (str): The user's input message
|
|
463
|
-
bot_response (str): The bot's generated response
|
|
464
582
|
"""
|
|
583
|
+
self.logger.debug("💾 Guardando mensajes en historial interno")
|
|
465
584
|
self.chat_history.append(HumanMessage(content=user_message))
|
|
466
585
|
self.chat_history.append(AIMessage(content=bot_response))
|
|
467
586
|
|
|
468
587
|
def process_file(self, file: FileProcessorInterface):
|
|
469
588
|
"""
|
|
470
589
|
Process and index a file for contextual retrieval.
|
|
471
|
-
|
|
472
|
-
This method maintains compatibility with existing file processing workflows
|
|
473
|
-
while leveraging FAISS for efficient similarity search.
|
|
474
|
-
|
|
475
|
-
Args:
|
|
476
|
-
file (FileProcessorInterface): File processor instance that implements getText()
|
|
477
|
-
|
|
478
|
-
Note:
|
|
479
|
-
Processed files are automatically available for context retrieval
|
|
480
|
-
in subsequent conversations without additional configuration.
|
|
481
590
|
"""
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
591
|
+
self.logger.info("📄 Procesando archivo para indexación")
|
|
592
|
+
try:
|
|
593
|
+
document = file.getText()
|
|
594
|
+
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
|
|
595
|
+
texts = text_splitter.split_documents(document)
|
|
596
|
+
|
|
597
|
+
self.logger.debug(f"✂️ Documento dividido en {len(texts)} chunks")
|
|
485
598
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
599
|
+
if self.vector_store is None:
|
|
600
|
+
self.vector_store = FAISS.from_texts(
|
|
601
|
+
[doc.page_content for doc in texts],
|
|
602
|
+
self.embeddings
|
|
603
|
+
)
|
|
604
|
+
self.logger.info("✅ Vector store creado")
|
|
605
|
+
else:
|
|
606
|
+
self.vector_store.add_texts([doc.page_content for doc in texts])
|
|
607
|
+
self.logger.info("✅ Textos agregados a vector store existente")
|
|
608
|
+
|
|
609
|
+
except Exception as e:
|
|
610
|
+
self.logger.error(f"❌ Error procesando archivo: {e}")
|
|
611
|
+
self.logger.exception("Traceback:")
|
|
612
|
+
raise
|
|
493
613
|
|
|
494
614
|
def clear_memory(self):
|
|
495
615
|
"""
|
|
496
616
|
Clear conversation history and processed file context.
|
|
497
|
-
|
|
498
|
-
This method resets the bot to a clean state, removing all conversation
|
|
499
|
-
history and processed file context.
|
|
500
617
|
"""
|
|
618
|
+
self.logger.info("🗑️ Limpiando memoria")
|
|
501
619
|
self.chat_history.clear()
|
|
502
620
|
self.vector_store = None
|
|
621
|
+
self.logger.debug("✅ Memoria limpiada")
|
|
503
622
|
|
|
504
623
|
def get_chat_history(self) -> List[BaseMessage]:
|
|
505
624
|
"""
|
|
506
625
|
Retrieve a copy of the current conversation history.
|
|
507
|
-
|
|
508
|
-
Returns:
|
|
509
|
-
List[BaseMessage]: Copy of the conversation history
|
|
510
626
|
"""
|
|
511
627
|
return self.chat_history.copy()
|
|
512
628
|
|
|
513
629
|
def set_chat_history(self, history: List[BaseMessage]):
|
|
514
630
|
"""
|
|
515
631
|
Set the conversation history from a list of BaseMessage instances.
|
|
516
|
-
|
|
517
|
-
Args:
|
|
518
|
-
history (List[BaseMessage]): New conversation history to set
|
|
519
632
|
"""
|
|
633
|
+
self.logger.info(f"📝 Estableciendo historial: {len(history)} mensajes")
|
|
520
634
|
self.chat_history = history.copy()
|
|
521
635
|
|
|
522
636
|
def _get_context(self, query: str) -> str:
|
|
523
637
|
"""
|
|
524
638
|
Retrieve relevant context from processed files using similarity search.
|
|
525
|
-
|
|
526
|
-
This method performs semantic search over processed file content to find
|
|
527
|
-
the most relevant information for the current query.
|
|
528
|
-
|
|
529
|
-
Args:
|
|
530
|
-
query (str): The query to search for relevant context
|
|
531
|
-
|
|
532
|
-
Returns:
|
|
533
|
-
str: Concatenated relevant context from processed files
|
|
534
639
|
"""
|
|
535
640
|
if self.vector_store:
|
|
641
|
+
self.logger.debug(f"🔍 Buscando contexto para query: {query[:100]}...")
|
|
536
642
|
docs = self.vector_store.similarity_search(query, k=4)
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
def process_file(self, file: FileProcessorInterface):
|
|
541
|
-
"""API original - Procesa archivo y lo añade al vector store"""
|
|
542
|
-
document = file.getText()
|
|
543
|
-
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
|
|
544
|
-
texts = text_splitter.split_documents(document)
|
|
545
|
-
|
|
546
|
-
if self.vector_store is None:
|
|
547
|
-
self.vector_store = FAISS.from_texts(
|
|
548
|
-
[doc.page_content for doc in texts],
|
|
549
|
-
self.embeddings
|
|
550
|
-
)
|
|
551
|
-
else:
|
|
552
|
-
self.vector_store.add_texts([doc.page_content for doc in texts])
|
|
553
|
-
|
|
554
|
-
def clear_memory(self):
|
|
555
|
-
"""API original - Limpia la memoria de conversación"""
|
|
556
|
-
self.chat_history.clear()
|
|
557
|
-
self.vector_store = None
|
|
558
|
-
|
|
559
|
-
def get_chat_history(self) -> List[BaseMessage]:
|
|
560
|
-
"""API original - Obtiene el historial completo"""
|
|
561
|
-
return self.chat_history.copy()
|
|
562
|
-
|
|
563
|
-
def set_chat_history(self, history: List[BaseMessage]):
|
|
564
|
-
"""API original - Establece el historial de conversación"""
|
|
565
|
-
self.chat_history = history.copy()
|
|
566
|
-
|
|
567
|
-
def _get_context(self, query: str) -> str:
|
|
568
|
-
"""Obtiene contexto relevante de archivos procesados"""
|
|
569
|
-
if self.vector_store:
|
|
570
|
-
docs = self.vector_store.similarity_search(query, k=4)
|
|
571
|
-
return "\n".join([doc.page_content for doc in docs])
|
|
643
|
+
context = "\n".join([doc.page_content for doc in docs])
|
|
644
|
+
self.logger.debug(f"✅ Contexto encontrado: {len(context)} caracteres")
|
|
645
|
+
return context
|
|
572
646
|
return ""
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
sonika_langchain_bot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
2
|
sonika_langchain_bot/document_processor.py,sha256=RuHT22Zt-psoe4adFWKwBJ0gi638fq8r2S5WZoDK8fY,10979
|
|
3
3
|
sonika_langchain_bot/langchain_bdi.py,sha256=ithc55azP5XSPb8AGRUrDGYnVI6I4IqpqElLNat4BAQ,7024
|
|
4
|
-
sonika_langchain_bot/langchain_bot_agent.py,sha256=
|
|
4
|
+
sonika_langchain_bot/langchain_bot_agent.py,sha256=UXcLkyVisyrDXfikKVYj-8l3FvGS8WJUT_G6XpPZO_w,28955
|
|
5
5
|
sonika_langchain_bot/langchain_bot_agent_bdi.py,sha256=Ev0hhRQYe6kyGAHiFDhFsfu6QnTwUFaA9oB8DfNV7u4,8613
|
|
6
6
|
sonika_langchain_bot/langchain_clasificator.py,sha256=GR85ZAliymBSoDa5PXB31BvJkuiokGjS2v3RLdXnzzk,1381
|
|
7
7
|
sonika_langchain_bot/langchain_class.py,sha256=5anB6v_wCzEoAJRb8fV9lPPS72E7-k51y_aeiip8RAw,1114
|
|
8
8
|
sonika_langchain_bot/langchain_files.py,sha256=SEyqnJgBc_nbCIG31eypunBbO33T5AHFOhQZcghTks4,381
|
|
9
9
|
sonika_langchain_bot/langchain_models.py,sha256=vqSSZ48tNofrTMLv1QugDdyey2MuIeSdlLSD37AnzkI,2235
|
|
10
10
|
sonika_langchain_bot/langchain_tools.py,sha256=y7wLf1DbUua3QIvz938Ek-JIMOuQhrOIptJadW8OIsU,466
|
|
11
|
-
sonika_langchain_bot-0.0.
|
|
12
|
-
sonika_langchain_bot-0.0.
|
|
13
|
-
sonika_langchain_bot-0.0.
|
|
14
|
-
sonika_langchain_bot-0.0.
|
|
15
|
-
sonika_langchain_bot-0.0.
|
|
11
|
+
sonika_langchain_bot-0.0.17.dist-info/licenses/LICENSE,sha256=O8VZ4aU_rUMAArvYTm2bshcZ991huv_tpfB5BKHH9Q8,1064
|
|
12
|
+
sonika_langchain_bot-0.0.17.dist-info/METADATA,sha256=q7AL7tRyc9_WhSL4bI_h0QWre1YV4qL3sQTI6v2ovd4,6508
|
|
13
|
+
sonika_langchain_bot-0.0.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
sonika_langchain_bot-0.0.17.dist-info/top_level.txt,sha256=UsTTSZFEw2wrPSVh4ufu01e2m_E7O_QVYT_k4zCQaAE,21
|
|
15
|
+
sonika_langchain_bot-0.0.17.dist-info/RECORD,,
|
|
File without changes
|
{sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{sonika_langchain_bot-0.0.15.dist-info → sonika_langchain_bot-0.0.17.dist-info}/top_level.txt
RENAMED
|
File without changes
|