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.

@@ -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
- mcp_tools = asyncio.run(self.mcp_client.get_tools())
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
- print(f"✅ MCP initialized: {len(mcp_tools)} tools from {len(mcp_servers)} servers")
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
- print(f"⚠️ MCP initialization error: {e}")
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
- return self.language_model.model.bind_tools(self.tools)
141
- return self.language_model.model
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) ← NUEVO
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] # add_messages annotation handles proper appending
383
+ "messages": [response]
264
384
  }
265
385
 
266
386
  except Exception as e:
267
- print(f"Error in agent_node: {e}")
268
- # Graceful fallback for error scenarios
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
- return workflow.compile(checkpointer=self.checkpointer)
444
+ compiled = workflow.compile(checkpointer=self.checkpointer)
445
+ self.logger.info("✅ Workflow compilado con checkpointer")
333
446
  else:
334
- return workflow.compile()
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
- # Execute the LangGraph workflow
365
- #result = self.graph.invoke(initial_state)
366
-
367
- # Siempre usar ainvoke (funciona para ambos casos)
368
- result = asyncio.run(self.graph.ainvoke(initial_state))
369
-
370
- # Update internal conversation history
371
- self.chat_history = result["messages"]
372
-
373
- # Extract final response from the last assistant message
374
- final_response = ""
375
- total_input_tokens = 0
376
- total_output_tokens = 0
377
-
378
- for msg in reversed(result["messages"]):
379
- if isinstance(msg, AIMessage) and msg.content:
380
- final_response = msg.content
381
- break
382
-
383
- # Extract token usage from response metadata
384
- last_message = result["messages"][-1]
385
- if hasattr(last_message, 'response_metadata'):
386
- token_usage = last_message.response_metadata.get('token_usage', {})
387
- total_input_tokens = token_usage.get('prompt_tokens', 0)
388
- total_output_tokens = token_usage.get('completion_tokens', 0)
389
-
390
- return ResponseModel(
391
- user_tokens=total_input_tokens,
392
- bot_tokens=total_output_tokens,
393
- response=final_response
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
- # Stream workflow execution
421
- for chunk in self.graph.stream(initial_state):
422
- # Extract content from workflow chunks
423
- if "agent" in chunk:
424
- for message in chunk["agent"]["messages"]:
425
- if isinstance(message, AIMessage) and message.content:
426
- # Stream complete responses (can be enhanced for token-level streaming)
427
- accumulated_response = message.content
428
- yield message.content
429
-
430
- # Update conversation history after streaming completion
431
- if accumulated_response:
432
- self.chat_history.extend([
433
- HumanMessage(content=user_input),
434
- AIMessage(content=accumulated_response)
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
- document = file.getText()
483
- text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
484
- texts = text_splitter.split_documents(document)
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
- if self.vector_store is None:
487
- self.vector_store = FAISS.from_texts(
488
- [doc.page_content for doc in texts],
489
- self.embeddings
490
- )
491
- else:
492
- self.vector_store.add_texts([doc.page_content for doc in texts])
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
- return "\n".join([doc.page_content for doc in docs])
538
- return ""
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonika-langchain-bot
3
- Version: 0.0.15
3
+ Version: 0.0.17
4
4
  Summary: Agente langchain con LLM
5
5
  Author: Erley Blanco Carvajal
6
6
  License: MIT License
@@ -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=LlzrINl543dPwizkQ-tW47OWzud0sP18Uwb-ZhxMHeA,23968
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.15.dist-info/licenses/LICENSE,sha256=O8VZ4aU_rUMAArvYTm2bshcZ991huv_tpfB5BKHH9Q8,1064
12
- sonika_langchain_bot-0.0.15.dist-info/METADATA,sha256=TkIrUOf7OyjqybcPfdxsJkIAr_uKYPeh3cY1oVe8f4w,6508
13
- sonika_langchain_bot-0.0.15.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
14
- sonika_langchain_bot-0.0.15.dist-info/top_level.txt,sha256=UsTTSZFEw2wrPSVh4ufu01e2m_E7O_QVYT_k4zCQaAE,21
15
- sonika_langchain_bot-0.0.15.dist-info/RECORD,,
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,,