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.
- sonika_langchain_bot/langchain_bot_agent.py +251 -191
- {sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/METADATA +1 -1
- {sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/RECORD +6 -6
- {sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/WHEEL +0 -0
- {sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/licenses/LICENSE +0 -0
- {sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Generator, List, Optional, Dict, Any, TypedDict, Annotated
|
|
2
2
|
import asyncio
|
|
3
|
+
import logging
|
|
3
4
|
from langchain.schema import AIMessage, HumanMessage, BaseMessage
|
|
4
5
|
from langchain_core.messages import ToolMessage
|
|
5
6
|
from langchain.text_splitter import CharacterTextSplitter
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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:
|
|
155
|
-
if hasattr(tool, 'args_schema') and tool.args_schema:
|
|
156
|
-
|
|
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
|
|
159
|
-
required = "**REQUIRED**" if
|
|
160
|
-
|
|
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
|
|
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]
|
|
358
|
+
"messages": [response]
|
|
256
359
|
}
|
|
257
360
|
|
|
258
361
|
except Exception as e:
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
419
|
+
compiled = workflow.compile(checkpointer=self.checkpointer)
|
|
420
|
+
self.logger.info("✅ Workflow compilado con checkpointer")
|
|
325
421
|
else:
|
|
326
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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
|
-
|
|
527
|
-
|
|
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,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=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.
|
|
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.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,,
|
|
File without changes
|
{sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{sonika_langchain_bot-0.0.14.dist-info → sonika_langchain_bot-0.0.16.dist-info}/top_level.txt
RENAMED
|
File without changes
|