sonika-langchain-bot 0.0.11__py3-none-any.whl → 0.0.13__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.
@@ -1,232 +1,438 @@
1
- from typing import Generator, List
2
- from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, HumanMessagePromptTemplate
1
+ from typing import Generator, List, Optional, Dict, Any, TypedDict, Annotated
2
+ import asyncio
3
3
  from langchain.schema import AIMessage, HumanMessage, BaseMessage
4
+ from langchain_core.messages import ToolMessage
4
5
  from langchain.text_splitter import CharacterTextSplitter
5
6
  from langchain_community.vectorstores import FAISS
6
- from sonika_langchain_bot.langchain_class import FileProcessorInterface, IEmbeddings, ILanguageModel, Message, ResponseModel
7
- from langgraph.checkpoint.memory import MemorySaver
8
- from langgraph.prebuilt import create_react_agent
9
7
  from langchain_community.tools import BaseTool
10
- import re
8
+ from langgraph.graph import StateGraph, END, add_messages
9
+ from langgraph.prebuilt import ToolNode
10
+ from langgraph.checkpoint.memory import MemorySaver
11
+ from langchain_mcp_adapters.client import MultiServerMCPClient
12
+
13
+ # Import your existing interfaces
14
+ from sonika_langchain_bot.langchain_class import FileProcessorInterface, IEmbeddings, ILanguageModel, Message, ResponseModel
15
+
16
+
17
+ class ChatState(TypedDict):
18
+ """
19
+ Modern chat state for LangGraph workflow.
20
+
21
+ Attributes:
22
+ messages: List of conversation messages with automatic message handling
23
+ context: Contextual information from processed files
24
+ """
25
+ messages: Annotated[List[BaseMessage], add_messages]
26
+ context: str
27
+
11
28
 
12
29
  class LangChainBot:
13
30
  """
14
- Implementación principal del bot conversacional con capacidades de procesamiento de archivos,
15
- memoria de conversación y uso de herramientas personalizadas.
31
+ Modern LangGraph-based conversational bot with MCP support.
32
+
33
+ This implementation provides 100% API compatibility with existing ChatService
34
+ while using modern LangGraph workflows and native tool calling internally.
35
+
36
+ Features:
37
+ - Native tool calling (no manual parsing)
38
+ - MCP (Model Context Protocol) support
39
+ - File processing with vector search
40
+ - Thread-based conversation persistence
41
+ - Streaming responses
42
+ - Backward compatibility with legacy APIs
16
43
  """
17
44
 
18
- def __init__(self, language_model: ILanguageModel, embeddings: IEmbeddings, instructions: str, tools: List[BaseTool]):
45
+ def __init__(self,
46
+ language_model: ILanguageModel,
47
+ embeddings: IEmbeddings,
48
+ instructions: str,
49
+ tools: Optional[List[BaseTool]] = None,
50
+ mcp_servers: Optional[Dict[str, Any]] = None,
51
+ use_checkpointer: bool = False):
19
52
  """
20
- Inicializa el bot con el modelo de lenguaje, embeddings y herramientas necesarias.
53
+ Initialize the modern LangGraph bot with optional MCP support.
21
54
 
22
55
  Args:
23
- language_model (ILanguageModel): Modelo de lenguaje a utilizar
24
- embeddings (IEmbeddings): Modelo de embeddings para procesamiento de texto
25
- instructions (str): Instrucciones del sistema
26
- tools (List[BaseTool]): Lista de herramientas disponibles
56
+ language_model (ILanguageModel): The language model to use for generation
57
+ embeddings (IEmbeddings): Embedding model for file processing and context retrieval
58
+ instructions (str): System instructions that will be modernized automatically
59
+ tools (List[BaseTool], optional): Traditional LangChain tools to bind to the model
60
+ mcp_servers (Dict[str, Any], optional): MCP server configurations for dynamic tool loading
61
+ use_checkpointer (bool): Enable automatic conversation persistence using LangGraph checkpoints
62
+
63
+ Note:
64
+ The instructions will be automatically enhanced with tool descriptions
65
+ when tools are provided, eliminating the need for manual tool instruction formatting.
27
66
  """
67
+ # Core components
28
68
  self.language_model = language_model
29
69
  self.embeddings = embeddings
30
- # Reemplazamos ConversationBufferMemory con una lista simple de mensajes
70
+ self.base_instructions = instructions
71
+
72
+ # Backward compatibility attributes
31
73
  self.chat_history: List[BaseMessage] = []
32
- self.memory_agent = MemorySaver()
33
74
  self.vector_store = None
34
- self.tools = tools
35
- self.instructions = instructions
36
- self.add_tools_to_instructions(tools)
37
- self.conversation = self._create_conversation_chain()
38
- self.agent_executor = self._create_agent_executor()
39
-
40
- def add_tools_to_instructions(self, tools: List[BaseTool]):
41
- """Agrega información de las herramientas a las instrucciones base del sistema."""
42
- if len(tools) == 0:
43
- return
44
-
45
- # Instrucciones sobre el uso de herramientas
46
- tools_instructions = '''\n\nWhen you want to execute a tool, enclose the command with three asterisks and provide all parameters needed.
47
- Ensure you gather all relevant information from the conversation to use the parameters.
48
- If information is missing, search online.
49
-
50
- This is a list of the tools you can execute:
51
- '''
52
75
 
53
- # Procesar cada herramienta y agregarla a las instrucciones
54
- for tool in tools:
55
- tool_name = tool.name
56
- tool_description = tool.description
57
-
58
- tools_instructions += f"\nTool Name: {tool_name}\n"
59
- tools_instructions += f"Description: {tool_description}\n"
60
-
61
- # Intentar obtener información de parámetros
62
- run_method = getattr(tool, '_run', None)
63
- if run_method:
64
- try:
65
- import inspect
66
- params = inspect.signature(run_method)
67
- tools_instructions += f"Parameters: {params}\n"
68
- except:
69
- tools_instructions += "Parameters: Not available\n"
70
- else:
71
- tools_instructions += "Parameters: Not available\n"
72
-
73
- tools_instructions += "---\n"
76
+ # Tool configuration
77
+ self.tools = tools or []
78
+ self.mcp_client = None
79
+
80
+ # Initialize MCP servers if provided
81
+ if mcp_servers:
82
+ self._initialize_mcp(mcp_servers)
83
+
84
+ # Configure persistence layer
85
+ self.checkpointer = MemorySaver() if use_checkpointer else None
74
86
 
75
- # Agregar las instrucciones de herramientas a las instrucciones base
76
- self.instructions += tools_instructions
87
+ # Prepare model with bound tools for native function calling
88
+ self.model_with_tools = self._prepare_model_with_tools()
77
89
 
90
+ # Build modern instruction set with tool descriptions
91
+ self.instructions = self._build_modern_instructions()
92
+
93
+ # Create the LangGraph workflow
94
+ self.graph = self._create_modern_workflow()
95
+
96
+ # Legacy compatibility attributes (maintained for API compatibility)
97
+ self.conversation = None
98
+ self.agent_executor = None
78
99
 
79
- def _create_conversation_chain(self):
100
+ def _initialize_mcp(self, mcp_servers: Dict[str, Any]):
80
101
  """
81
- Crea la cadena de conversación con el prompt template y la memoria.
102
+ Initialize MCP (Model Context Protocol) connections and load available tools.
103
+
104
+ This method establishes connections to configured MCP servers and automatically
105
+ imports their tools into the bot's tool collection.
106
+
107
+ Args:
108
+ mcp_servers (Dict[str, Any]): Dictionary of MCP server configurations
109
+ Example: {
110
+ "server_name": {
111
+ "command": "python",
112
+ "args": ["/path/to/server.py"],
113
+ "transport": "stdio"
114
+ }
115
+ }
116
+
117
+ Note:
118
+ MCP tools are automatically appended to the existing tools list and
119
+ will be included in the model's tool binding process.
82
120
  """
83
- full_system_prompt = f"{self.instructions}\n\n"
84
-
85
- prompt = ChatPromptTemplate.from_messages([
86
- SystemMessagePromptTemplate.from_template(full_system_prompt),
87
- MessagesPlaceholder(variable_name="history"),
88
- HumanMessagePromptTemplate.from_template("{input}")
89
- ])
90
-
91
- # Usando RunnableSequence para reemplazar LLMChain
92
- return prompt | self.language_model.model
93
-
94
- def _create_agent_executor(self):
121
+ try:
122
+ self.mcp_client = MultiServerMCPClient(mcp_servers)
123
+ mcp_tools = asyncio.run(self.mcp_client.get_tools())
124
+ self.tools.extend(mcp_tools)
125
+ print(f"✅ MCP initialized: {len(mcp_tools)} tools from {len(mcp_servers)} servers")
126
+ except Exception as e:
127
+ print(f"⚠️ MCP initialization error: {e}")
128
+ self.mcp_client = None
129
+
130
+ def _prepare_model_with_tools(self):
95
131
  """
96
- Crea el ejecutor del agente con las herramientas configuradas.
97
-
132
+ Prepare the language model with bound tools for native function calling.
133
+
134
+ This method binds all available tools (both traditional and MCP) to the language model,
135
+ enabling native function calling without manual parsing or instruction formatting.
136
+
98
137
  Returns:
99
- Agent: Agente configurado con las herramientas
138
+ The language model with tools bound, or the original model if no tools are available
100
139
  """
101
- return create_react_agent(self.language_model.model, self.tools, checkpointer=self.memory_agent)
140
+ if self.tools:
141
+ return self.language_model.model.bind_tools(self.tools)
142
+ return self.language_model.model
102
143
 
103
- def _getInstruccionTool(self, bot_response):
104
- """
105
- Extrae las instrucciones para herramientas del texto de respuesta del bot.
144
+ def _build_modern_instructions(self) -> str:
145
+ instructions = self.base_instructions
146
+
147
+ if self.tools:
148
+ tools_description = "\n\n# Available Tools\n\n"
149
+
150
+ for tool in self.tools:
151
+ tools_description += f"## {tool.name}\n"
152
+ tools_description += f"**Description:** {tool.description}\n\n"
153
+
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__'):
157
+ 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"
161
+
162
+ # Opción 2: Tool básico sin args_schema (EmailTool)
163
+ elif hasattr(tool, '_run'):
164
+ tools_description += f"**Parameters:**\n"
165
+ import inspect
166
+ sig = inspect.signature(tool._run)
167
+ for param_name, param in sig.parameters.items():
168
+ if param_name != 'self':
169
+ param_type = param.annotation.__name__ if param.annotation != inspect.Parameter.empty else 'any'
170
+ required = "*optional*" if param.default != inspect.Parameter.empty else "**REQUIRED**"
171
+ default_info = f" (default: {param.default})" if param.default != inspect.Parameter.empty else ""
172
+ tools_description += f"- `{param_name}` ({param_type}, {required}){default_info}\n"
173
+
174
+ tools_description += "\n"
175
+
176
+ tools_description += ("## Usage Instructions\n"
177
+ "- Use the standard function calling format\n"
178
+ "- **MUST** provide all REQUIRED parameters\n"
179
+ "- Do NOT call tools with empty arguments\n")
180
+
181
+ instructions += tools_description
106
182
 
107
- Args:
108
- bot_response (str): Respuesta del bot a analizar
183
+ return instructions
109
184
 
185
+ def _create_modern_workflow(self) -> StateGraph:
186
+ """
187
+ Create a modern LangGraph workflow using idiomatic patterns.
188
+
189
+ This method constructs a state-based workflow that handles:
190
+ - Agent reasoning and response generation
191
+ - Automatic tool execution via ToolNode
192
+ - Context integration from processed files
193
+ - Error handling and fallback responses
194
+
110
195
  Returns:
111
- str: Instrucción extraída o cadena vacía si no se encuentra
196
+ StateGraph: Compiled LangGraph workflow ready for execution
112
197
  """
113
- patron = r'\*\*\*(.*?)\*\*\*'
114
- coincidencia = re.search(patron, bot_response)
115
- return coincidencia.group(1).strip() if coincidencia else ''
198
+
199
+ def agent_node(state: ChatState) -> ChatState:
200
+ """
201
+ 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
+ """
216
+ # Extract the most recent user message
217
+ last_user_message = None
218
+ for msg in reversed(state["messages"]):
219
+ if isinstance(msg, HumanMessage):
220
+ last_user_message = msg.content
221
+ break
222
+
223
+ if not last_user_message:
224
+ return state
225
+
226
+ # Retrieve contextual information from processed files
227
+ context = self._get_context(last_user_message)
228
+
229
+ # Build system prompt with optional context
230
+ system_content = self.instructions
231
+ if context:
232
+ system_content += f"\n\nContext from uploaded files:\n{context}"
233
+
234
+ # Construct message history in OpenAI format
235
+ messages = [{"role": "system", "content": system_content}]
236
+
237
+ # Add conversation history with simplified message handling
238
+ for msg in state["messages"]:
239
+ if isinstance(msg, HumanMessage):
240
+ messages.append({"role": "user", "content": msg.content})
241
+ elif isinstance(msg, AIMessage):
242
+ messages.append({"role": "assistant", "content": msg.content or ""})
243
+ elif isinstance(msg, ToolMessage):
244
+ # Convert tool results to user messages for context
245
+ messages.append({"role": "user", "content": f"Tool result: {msg.content}"})
246
+
247
+ try:
248
+ # Invoke model with native tool binding
249
+ response = self.model_with_tools.invoke(messages)
250
+
251
+ # Return updated state
252
+ return {
253
+ **state,
254
+ "context": context,
255
+ "messages": [response] # add_messages annotation handles proper appending
256
+ }
257
+
258
+ except Exception as e:
259
+ print(f"Error in agent_node: {e}")
260
+ # Graceful fallback for error scenarios
261
+ fallback_response = AIMessage(content="I apologize, but I encountered an error processing your request.")
262
+ return {
263
+ **state,
264
+ "context": context,
265
+ "messages": [fallback_response]
266
+ }
267
+
268
+ def should_continue(state: ChatState) -> str:
269
+ """
270
+ 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
+ """
281
+ last_message = state["messages"][-1]
282
+
283
+ # Check for pending tool calls using native tool calling detection
284
+ if (isinstance(last_message, AIMessage) and
285
+ hasattr(last_message, 'tool_calls') and
286
+ last_message.tool_calls):
287
+ return "tools"
288
+
289
+ return "end"
116
290
 
291
+ # Construct the workflow graph
292
+ workflow = StateGraph(ChatState)
293
+
294
+ # Add primary agent node
295
+ workflow.add_node("agent", agent_node)
296
+
297
+ # Add tool execution node if tools are available
298
+ if self.tools:
299
+ # ToolNode automatically handles tool execution and result formatting
300
+ tool_node = ToolNode(self.tools)
301
+ workflow.add_node("tools", tool_node)
302
+
303
+ # Define workflow edges and entry point
304
+ workflow.set_entry_point("agent")
305
+
306
+ if self.tools:
307
+ # Conditional routing based on tool call presence
308
+ workflow.add_conditional_edges(
309
+ "agent",
310
+ should_continue,
311
+ {
312
+ "tools": "tools",
313
+ "end": END
314
+ }
315
+ )
316
+ # Return to agent after tool execution for final response formatting
317
+ workflow.add_edge("tools", "agent")
318
+ else:
319
+ # Direct termination if no tools are available
320
+ workflow.add_edge("agent", END)
321
+
322
+ # Compile workflow with optional checkpointing
323
+ if self.checkpointer:
324
+ return workflow.compile(checkpointer=self.checkpointer)
325
+ else:
326
+ return workflow.compile()
327
+
328
+ # ===== LEGACY API COMPATIBILITY =====
329
+
117
330
  def get_response(self, user_input: str) -> ResponseModel:
118
331
  """
119
- Genera una respuesta para la entrada del usuario, procesando el contexto y ejecutando herramientas si es necesario.
120
-
332
+ Generate a response while maintaining 100% API compatibility.
333
+
334
+ This method provides the primary interface for single-turn conversations,
335
+ maintaining backward compatibility with existing ChatService implementations.
336
+
121
337
  Args:
122
- user_input (str): Entrada del usuario
123
-
338
+ user_input (str): The user's message or query
339
+
124
340
  Returns:
125
- ResponseModel: Modelo de respuesta con tokens y texto
341
+ ResponseModel: Structured response containing:
342
+ - user_tokens: Input token count
343
+ - bot_tokens: Output token count
344
+ - response: Generated response text
345
+
346
+ Note:
347
+ This method automatically handles tool execution and context integration
348
+ from processed files while maintaining the original API signature.
126
349
  """
127
- context = self._get_context(user_input)
128
- augmented_input = f"User question: {user_input}"
129
- if context:
130
- augmented_input = f"Context from attached files:\n{context}\n\nUser question: {user_input}"
131
-
132
- # Usamos el historial de chat directamente
133
- bot_response = self.conversation.invoke({
134
- "input": augmented_input,
135
- "history": self.chat_history
136
- })
137
-
138
- token_usage = bot_response.response_metadata.get('token_usage', {})
139
- bot_response_content = bot_response.content
350
+ # Prepare initial workflow state
351
+ initial_state = {
352
+ "messages": self.chat_history + [HumanMessage(content=user_input)],
353
+ "context": ""
354
+ }
140
355
 
141
- instruction_tool = self._getInstruccionTool(bot_response_content)
142
-
143
- if instruction_tool:
144
- messages = [HumanMessage(content=instruction_tool)]
145
- thread_id = "abc123"
146
- config = {"configurable": {"thread_id": thread_id}}
147
-
148
- result_stream = self.agent_executor.stream(
149
- {"messages": messages}, config
150
- )
151
-
152
- tool_response = ""
153
- agent_response = ""
154
-
155
- for response in result_stream:
156
- if 'tools' in response:
157
- for message in response['tools']['messages']:
158
- tool_response = message.content
159
- if 'agent' in response:
160
- for message in response['agent']['messages']:
161
- agent_response = message.content
162
-
163
- bot_response_content = agent_response if agent_response else tool_response
164
-
165
- user_tokens = token_usage.get('prompt_tokens', 0)
166
- bot_tokens = token_usage.get('completion_tokens', 0)
167
-
168
- self.save_messages(user_input, bot_response_content)
169
-
170
- return ResponseModel(user_tokens=user_tokens, bot_tokens=bot_tokens, response=bot_response_content)
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
+ )
171
384
 
172
385
  def get_response_stream(self, user_input: str) -> Generator[str, None, None]:
173
386
  """
174
- Genera una respuesta en streaming para la entrada del usuario, procesando el contexto.
175
-
387
+ 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
+
176
392
  Args:
177
- user_input (str): Entrada del usuario
178
-
393
+ user_input (str): The user's message or query
394
+
179
395
  Yields:
180
- str: Fragmentos de la respuesta generada por el modelo en tiempo real
181
- """
182
- context = self._get_context(user_input)
183
- augmented_input = f"User question: {user_input}"
184
- if context:
185
- augmented_input = f"Context from attached files:\n{context}\n\nUser question: {user_input}"
186
-
187
- # Usamos el historial de chat directamente
188
- result_stream = self.conversation.stream({
189
- "input": augmented_input,
190
- "history": self.chat_history
191
- })
192
-
193
- full_response = ""
194
- for response in result_stream:
195
- content = response.content
196
- full_response += content
197
- yield content
198
-
199
- # Guardamos los mensajes después del streaming
200
- self.save_messages(user_input, full_response)
201
-
202
- def _get_context(self, query: str) -> str:
203
- """
204
- Obtiene el contexto relevante para una consulta del almacén de vectores.
205
-
206
- Args:
207
- query (str): Consulta para buscar contexto
208
-
209
- Returns:
210
- str: Contexto encontrado o cadena vacía
211
- """
212
- if self.vector_store:
213
- docs = self.vector_store.similarity_search(query)
214
- return "\n".join([doc.page_content for doc in docs])
215
- return ""
216
-
217
- def clear_memory(self):
218
- """
219
- Limpia la memoria de conversación y el almacén de vectores.
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.
220
401
  """
221
- self.chat_history.clear()
222
- self.vector_store = None
402
+ initial_state = {
403
+ "messages": self.chat_history + [HumanMessage(content=user_input)],
404
+ "context": ""
405
+ }
406
+
407
+ accumulated_response = ""
408
+
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
+ ])
223
425
 
224
426
  def load_conversation_history(self, messages: List[Message]):
225
427
  """
226
- Carga el historial de conversación previo usando la estructura de mensajes simplificada.
227
-
428
+ 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
+
228
433
  Args:
229
- messages: Lista de objetos Message que representan cada mensaje.
434
+ messages (List[Message]): List of Django Message model instances
435
+ Expected to have 'content' and 'is_bot' attributes
230
436
  """
231
437
  self.chat_history.clear()
232
438
  for message in messages:
@@ -237,45 +443,119 @@ This is a list of the tools you can execute:
237
443
 
238
444
  def save_messages(self, user_message: str, bot_response: str):
239
445
  """
240
- Guarda los mensajes en el historial de conversación.
241
-
446
+ Save messages to internal conversation history.
447
+
448
+ This method provides backward compatibility for manual history management.
449
+
242
450
  Args:
243
- user_message (str): Mensaje del usuario
244
- bot_response (str): Respuesta del bot
451
+ user_message (str): The user's input message
452
+ bot_response (str): The bot's generated response
245
453
  """
246
454
  self.chat_history.append(HumanMessage(content=user_message))
247
455
  self.chat_history.append(AIMessage(content=bot_response))
248
456
 
249
457
  def process_file(self, file: FileProcessorInterface):
250
458
  """
251
- Procesa un archivo y lo añade al almacén de vectores.
252
-
459
+ 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
+
253
464
  Args:
254
- file (FileProcessorInterface): Archivo a procesar
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.
255
470
  """
256
471
  document = file.getText()
257
472
  text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
258
473
  texts = text_splitter.split_documents(document)
259
474
 
260
475
  if self.vector_store is None:
261
- self.vector_store = FAISS.from_texts([doc.page_content for doc in texts], self.embeddings)
476
+ self.vector_store = FAISS.from_texts(
477
+ [doc.page_content for doc in texts],
478
+ self.embeddings
479
+ )
262
480
  else:
263
481
  self.vector_store.add_texts([doc.page_content for doc in texts])
264
482
 
265
- def get_chat_history(self) -> List[BaseMessage]:
483
+ def clear_memory(self):
484
+ """
485
+ 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.
266
489
  """
267
- Obtiene el historial completo de la conversación.
490
+ self.chat_history.clear()
491
+ self.vector_store = None
268
492
 
493
+ def get_chat_history(self) -> List[BaseMessage]:
494
+ """
495
+ Retrieve a copy of the current conversation history.
496
+
269
497
  Returns:
270
- List[BaseMessage]: Lista de mensajes de la conversación
498
+ List[BaseMessage]: Copy of the conversation history
271
499
  """
272
500
  return self.chat_history.copy()
273
501
 
274
502
  def set_chat_history(self, history: List[BaseMessage]):
275
503
  """
276
- Establece el historial de conversación.
504
+ Set the conversation history from a list of BaseMessage instances.
505
+
506
+ Args:
507
+ history (List[BaseMessage]): New conversation history to set
508
+ """
509
+ self.chat_history = history.copy()
277
510
 
511
+ def _get_context(self, query: str) -> str:
512
+ """
513
+ 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
+
278
518
  Args:
279
- history (List[BaseMessage]): Lista de mensajes a establecer
519
+ query (str): The query to search for relevant context
520
+
521
+ Returns:
522
+ str: Concatenated relevant context from processed files
280
523
  """
281
- self.chat_history = history.copy()
524
+ if self.vector_store:
525
+ 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])
561
+ return ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sonika-langchain-bot
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: Agente langchain con LLM
5
5
  Author: Erley Blanco Carvajal
6
6
  License: MIT License
@@ -10,7 +10,7 @@ Classifier: Operating System :: OS Independent
10
10
  Requires-Python: >=3.6
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
- Requires-Dist: langchain==0.3.26
13
+ Requires-Dist: langchain-mcp-adapters==0.1.9
14
14
  Requires-Dist: langchain-community==0.3.26
15
15
  Requires-Dist: langchain-core==0.3.66
16
16
  Requires-Dist: langchain-openai==0.3.24
@@ -1,14 +1,14 @@
1
1
  sonika_langchain_bot/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  sonika_langchain_bot/langchain_bdi.py,sha256=ithc55azP5XSPb8AGRUrDGYnVI6I4IqpqElLNat4BAQ,7024
3
- sonika_langchain_bot/langchain_bot_agent.py,sha256=z7jamAwZqQJEt7jWFZo2nMGWPS4dgTCghpKQdkR3BT4,10890
3
+ sonika_langchain_bot/langchain_bot_agent.py,sha256=3K8HiUzizIz7v_KmTFX9geOqiXTEwEqlm5jPXdPQeaM,23072
4
4
  sonika_langchain_bot/langchain_bot_agent_bdi.py,sha256=Ev0hhRQYe6kyGAHiFDhFsfu6QnTwUFaA9oB8DfNV7u4,8613
5
5
  sonika_langchain_bot/langchain_clasificator.py,sha256=GR85ZAliymBSoDa5PXB31BvJkuiokGjS2v3RLdXnzzk,1381
6
6
  sonika_langchain_bot/langchain_class.py,sha256=5anB6v_wCzEoAJRb8fV9lPPS72E7-k51y_aeiip8RAw,1114
7
7
  sonika_langchain_bot/langchain_files.py,sha256=SEyqnJgBc_nbCIG31eypunBbO33T5AHFOhQZcghTks4,381
8
8
  sonika_langchain_bot/langchain_models.py,sha256=vqSSZ48tNofrTMLv1QugDdyey2MuIeSdlLSD37AnzkI,2235
9
9
  sonika_langchain_bot/langchain_tools.py,sha256=y7wLf1DbUua3QIvz938Ek-JIMOuQhrOIptJadW8OIsU,466
10
- sonika_langchain_bot-0.0.11.dist-info/licenses/LICENSE,sha256=O8VZ4aU_rUMAArvYTm2bshcZ991huv_tpfB5BKHH9Q8,1064
11
- sonika_langchain_bot-0.0.11.dist-info/METADATA,sha256=QBEkIYZbUqtflj8Lx-VBfTAY-3l6u4Mht0Dr1u1K3C8,6368
12
- sonika_langchain_bot-0.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- sonika_langchain_bot-0.0.11.dist-info/top_level.txt,sha256=UsTTSZFEw2wrPSVh4ufu01e2m_E7O_QVYT_k4zCQaAE,21
14
- sonika_langchain_bot-0.0.11.dist-info/RECORD,,
10
+ sonika_langchain_bot-0.0.13.dist-info/licenses/LICENSE,sha256=O8VZ4aU_rUMAArvYTm2bshcZ991huv_tpfB5BKHH9Q8,1064
11
+ sonika_langchain_bot-0.0.13.dist-info/METADATA,sha256=RyRtV63QD_s53I3GCCa8uuJasHU1811CoORRIYDmmuY,6380
12
+ sonika_langchain_bot-0.0.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ sonika_langchain_bot-0.0.13.dist-info/top_level.txt,sha256=UsTTSZFEw2wrPSVh4ufu01e2m_E7O_QVYT_k4zCQaAE,21
14
+ sonika_langchain_bot-0.0.13.dist-info/RECORD,,