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