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