sonika-langchain-bot 0.0.10__py3-none-any.whl → 0.0.12__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.
- sonika_langchain_bot/langchain_bot_agent.py +722 -0
- sonika_langchain_bot-0.0.12.dist-info/METADATA +215 -0
- {sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info}/RECORD +6 -5
- {sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info}/WHEEL +1 -1
- sonika_langchain_bot-0.0.10.dist-info/METADATA +0 -145
- {sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info/licenses}/LICENSE +0 -0
- {sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,722 @@
|
|
1
|
+
from typing import Generator, List, Optional, Dict, Any, TypedDict, Annotated
|
2
|
+
import asyncio
|
3
|
+
from langchain.schema import AIMessage, HumanMessage, BaseMessage
|
4
|
+
from langchain_core.messages import ToolMessage
|
5
|
+
from langchain.text_splitter import CharacterTextSplitter
|
6
|
+
from langchain_community.vectorstores import FAISS
|
7
|
+
from langchain_community.tools import BaseTool
|
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
|
+
|
28
|
+
|
29
|
+
class LangChainBot:
|
30
|
+
"""
|
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
|
43
|
+
"""
|
44
|
+
|
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):
|
52
|
+
"""
|
53
|
+
Initialize the modern LangGraph bot with optional MCP support.
|
54
|
+
|
55
|
+
Args:
|
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.
|
66
|
+
"""
|
67
|
+
# Core components
|
68
|
+
self.language_model = language_model
|
69
|
+
self.embeddings = embeddings
|
70
|
+
self.base_instructions = instructions
|
71
|
+
|
72
|
+
# Backward compatibility attributes
|
73
|
+
self.chat_history: List[BaseMessage] = []
|
74
|
+
self.vector_store = None
|
75
|
+
|
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
|
86
|
+
|
87
|
+
# Prepare model with bound tools for native function calling
|
88
|
+
self.model_with_tools = self._prepare_model_with_tools()
|
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
|
99
|
+
|
100
|
+
def _initialize_mcp(self, mcp_servers: Dict[str, Any]):
|
101
|
+
"""
|
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.
|
120
|
+
"""
|
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):
|
131
|
+
"""
|
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
|
+
|
137
|
+
Returns:
|
138
|
+
The language model with tools bound, or the original model if no tools are available
|
139
|
+
"""
|
140
|
+
if self.tools:
|
141
|
+
return self.language_model.model.bind_tools(self.tools)
|
142
|
+
return self.language_model.model
|
143
|
+
|
144
|
+
def _build_modern_instructions(self) -> str:
|
145
|
+
"""
|
146
|
+
Build modern system instructions with automatic tool descriptions.
|
147
|
+
|
148
|
+
This method enhances the base instructions with professional tool descriptions
|
149
|
+
that leverage native function calling capabilities, eliminating the need for
|
150
|
+
manual tool instruction formatting.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
str: Complete system instructions including tool descriptions
|
154
|
+
"""
|
155
|
+
instructions = self.base_instructions
|
156
|
+
|
157
|
+
if self.tools:
|
158
|
+
tools_description = "\n\nYou have access to the following tools:\n"
|
159
|
+
for tool in self.tools:
|
160
|
+
tools_description += f"- {tool.name}: {tool.description}\n"
|
161
|
+
|
162
|
+
tools_description += ("\nCall these tools when needed using the standard function calling format. "
|
163
|
+
"You can call multiple tools in sequence if necessary to fully answer the user's question.")
|
164
|
+
|
165
|
+
instructions += tools_description
|
166
|
+
|
167
|
+
return instructions
|
168
|
+
|
169
|
+
def _create_modern_workflow(self) -> StateGraph:
|
170
|
+
"""
|
171
|
+
Create a modern LangGraph workflow using idiomatic patterns.
|
172
|
+
|
173
|
+
This method constructs a state-based workflow that handles:
|
174
|
+
- Agent reasoning and response generation
|
175
|
+
- Automatic tool execution via ToolNode
|
176
|
+
- Context integration from processed files
|
177
|
+
- Error handling and fallback responses
|
178
|
+
|
179
|
+
Returns:
|
180
|
+
StateGraph: Compiled LangGraph workflow ready for execution
|
181
|
+
"""
|
182
|
+
|
183
|
+
def agent_node(state: ChatState) -> ChatState:
|
184
|
+
"""
|
185
|
+
Main agent node responsible for generating responses and initiating tool calls.
|
186
|
+
|
187
|
+
This node:
|
188
|
+
1. Extracts the latest user message from the conversation state
|
189
|
+
2. Retrieves relevant context from processed files
|
190
|
+
3. Constructs a complete message history for the model
|
191
|
+
4. Invokes the model with tool binding for native function calling
|
192
|
+
5. Returns updated state with the model's response
|
193
|
+
|
194
|
+
Args:
|
195
|
+
state (ChatState): Current conversation state
|
196
|
+
|
197
|
+
Returns:
|
198
|
+
ChatState: Updated state with agent response
|
199
|
+
"""
|
200
|
+
# Extract the most recent user message
|
201
|
+
last_user_message = None
|
202
|
+
for msg in reversed(state["messages"]):
|
203
|
+
if isinstance(msg, HumanMessage):
|
204
|
+
last_user_message = msg.content
|
205
|
+
break
|
206
|
+
|
207
|
+
if not last_user_message:
|
208
|
+
return state
|
209
|
+
|
210
|
+
# Retrieve contextual information from processed files
|
211
|
+
context = self._get_context(last_user_message)
|
212
|
+
|
213
|
+
# Build system prompt with optional context
|
214
|
+
system_content = self.instructions
|
215
|
+
if context:
|
216
|
+
system_content += f"\n\nContext from uploaded files:\n{context}"
|
217
|
+
|
218
|
+
# Construct message history in OpenAI format
|
219
|
+
messages = [{"role": "system", "content": system_content}]
|
220
|
+
|
221
|
+
# Add conversation history with simplified message handling
|
222
|
+
for msg in state["messages"]:
|
223
|
+
if isinstance(msg, HumanMessage):
|
224
|
+
messages.append({"role": "user", "content": msg.content})
|
225
|
+
elif isinstance(msg, AIMessage):
|
226
|
+
messages.append({"role": "assistant", "content": msg.content or ""})
|
227
|
+
elif isinstance(msg, ToolMessage):
|
228
|
+
# Convert tool results to user messages for context
|
229
|
+
messages.append({"role": "user", "content": f"Tool result: {msg.content}"})
|
230
|
+
|
231
|
+
try:
|
232
|
+
# Invoke model with native tool binding
|
233
|
+
response = self.model_with_tools.invoke(messages)
|
234
|
+
|
235
|
+
# Return updated state
|
236
|
+
return {
|
237
|
+
**state,
|
238
|
+
"context": context,
|
239
|
+
"messages": [response] # add_messages annotation handles proper appending
|
240
|
+
}
|
241
|
+
|
242
|
+
except Exception as e:
|
243
|
+
print(f"Error in agent_node: {e}")
|
244
|
+
# Graceful fallback for error scenarios
|
245
|
+
fallback_response = AIMessage(content="I apologize, but I encountered an error processing your request.")
|
246
|
+
return {
|
247
|
+
**state,
|
248
|
+
"context": context,
|
249
|
+
"messages": [fallback_response]
|
250
|
+
}
|
251
|
+
|
252
|
+
def should_continue(state: ChatState) -> str:
|
253
|
+
"""
|
254
|
+
Conditional edge function to determine workflow continuation.
|
255
|
+
|
256
|
+
Analyzes the last message to decide whether to execute tools or end the workflow.
|
257
|
+
This leverages LangGraph's native tool calling detection.
|
258
|
+
|
259
|
+
Args:
|
260
|
+
state (ChatState): Current conversation state
|
261
|
+
|
262
|
+
Returns:
|
263
|
+
str: Next node to execute ("tools" or "end")
|
264
|
+
"""
|
265
|
+
last_message = state["messages"][-1]
|
266
|
+
|
267
|
+
# Check for pending tool calls using native tool calling detection
|
268
|
+
if (isinstance(last_message, AIMessage) and
|
269
|
+
hasattr(last_message, 'tool_calls') and
|
270
|
+
last_message.tool_calls):
|
271
|
+
return "tools"
|
272
|
+
|
273
|
+
return "end"
|
274
|
+
|
275
|
+
# Construct the workflow graph
|
276
|
+
workflow = StateGraph(ChatState)
|
277
|
+
|
278
|
+
# Add primary agent node
|
279
|
+
workflow.add_node("agent", agent_node)
|
280
|
+
|
281
|
+
# Add tool execution node if tools are available
|
282
|
+
if self.tools:
|
283
|
+
# ToolNode automatically handles tool execution and result formatting
|
284
|
+
tool_node = ToolNode(self.tools)
|
285
|
+
workflow.add_node("tools", tool_node)
|
286
|
+
|
287
|
+
# Define workflow edges and entry point
|
288
|
+
workflow.set_entry_point("agent")
|
289
|
+
|
290
|
+
if self.tools:
|
291
|
+
# Conditional routing based on tool call presence
|
292
|
+
workflow.add_conditional_edges(
|
293
|
+
"agent",
|
294
|
+
should_continue,
|
295
|
+
{
|
296
|
+
"tools": "tools",
|
297
|
+
"end": END
|
298
|
+
}
|
299
|
+
)
|
300
|
+
# Return to agent after tool execution for final response formatting
|
301
|
+
workflow.add_edge("tools", "agent")
|
302
|
+
else:
|
303
|
+
# Direct termination if no tools are available
|
304
|
+
workflow.add_edge("agent", END)
|
305
|
+
|
306
|
+
# Compile workflow with optional checkpointing
|
307
|
+
if self.checkpointer:
|
308
|
+
return workflow.compile(checkpointer=self.checkpointer)
|
309
|
+
else:
|
310
|
+
return workflow.compile()
|
311
|
+
|
312
|
+
# ===== LEGACY API COMPATIBILITY =====
|
313
|
+
|
314
|
+
def get_response(self, user_input: str) -> ResponseModel:
|
315
|
+
"""
|
316
|
+
Generate a response while maintaining 100% API compatibility.
|
317
|
+
|
318
|
+
This method provides the primary interface for single-turn conversations,
|
319
|
+
maintaining backward compatibility with existing ChatService implementations.
|
320
|
+
|
321
|
+
Args:
|
322
|
+
user_input (str): The user's message or query
|
323
|
+
|
324
|
+
Returns:
|
325
|
+
ResponseModel: Structured response containing:
|
326
|
+
- user_tokens: Input token count
|
327
|
+
- bot_tokens: Output token count
|
328
|
+
- response: Generated response text
|
329
|
+
|
330
|
+
Note:
|
331
|
+
This method automatically handles tool execution and context integration
|
332
|
+
from processed files while maintaining the original API signature.
|
333
|
+
"""
|
334
|
+
# Prepare initial workflow state
|
335
|
+
initial_state = {
|
336
|
+
"messages": self.chat_history + [HumanMessage(content=user_input)],
|
337
|
+
"context": ""
|
338
|
+
}
|
339
|
+
|
340
|
+
# Execute the LangGraph workflow
|
341
|
+
result = self.graph.invoke(initial_state)
|
342
|
+
|
343
|
+
# Update internal conversation history
|
344
|
+
self.chat_history = result["messages"]
|
345
|
+
|
346
|
+
# Extract final response from the last assistant message
|
347
|
+
final_response = ""
|
348
|
+
total_input_tokens = 0
|
349
|
+
total_output_tokens = 0
|
350
|
+
|
351
|
+
for msg in reversed(result["messages"]):
|
352
|
+
if isinstance(msg, AIMessage) and msg.content:
|
353
|
+
final_response = msg.content
|
354
|
+
break
|
355
|
+
|
356
|
+
# Extract token usage from response metadata
|
357
|
+
last_message = result["messages"][-1]
|
358
|
+
if hasattr(last_message, 'response_metadata'):
|
359
|
+
token_usage = last_message.response_metadata.get('token_usage', {})
|
360
|
+
total_input_tokens = token_usage.get('prompt_tokens', 0)
|
361
|
+
total_output_tokens = token_usage.get('completion_tokens', 0)
|
362
|
+
|
363
|
+
return ResponseModel(
|
364
|
+
user_tokens=total_input_tokens,
|
365
|
+
bot_tokens=total_output_tokens,
|
366
|
+
response=final_response
|
367
|
+
)
|
368
|
+
|
369
|
+
def get_response_stream(self, user_input: str) -> Generator[str, None, None]:
|
370
|
+
"""
|
371
|
+
Generate a streaming response for real-time user interaction.
|
372
|
+
|
373
|
+
This method provides streaming capabilities while maintaining backward
|
374
|
+
compatibility with the original API.
|
375
|
+
|
376
|
+
Args:
|
377
|
+
user_input (str): The user's message or query
|
378
|
+
|
379
|
+
Yields:
|
380
|
+
str: Response chunks as they are generated
|
381
|
+
|
382
|
+
Note:
|
383
|
+
Current implementation streams complete responses. For token-level
|
384
|
+
streaming, consider using the model's native streaming capabilities.
|
385
|
+
"""
|
386
|
+
initial_state = {
|
387
|
+
"messages": self.chat_history + [HumanMessage(content=user_input)],
|
388
|
+
"context": ""
|
389
|
+
}
|
390
|
+
|
391
|
+
accumulated_response = ""
|
392
|
+
|
393
|
+
# Stream workflow execution
|
394
|
+
for chunk in self.graph.stream(initial_state):
|
395
|
+
# Extract content from workflow chunks
|
396
|
+
if "agent" in chunk:
|
397
|
+
for message in chunk["agent"]["messages"]:
|
398
|
+
if isinstance(message, AIMessage) and message.content:
|
399
|
+
# Stream complete responses (can be enhanced for token-level streaming)
|
400
|
+
accumulated_response = message.content
|
401
|
+
yield message.content
|
402
|
+
|
403
|
+
# Update conversation history after streaming completion
|
404
|
+
if accumulated_response:
|
405
|
+
self.chat_history.extend([
|
406
|
+
HumanMessage(content=user_input),
|
407
|
+
AIMessage(content=accumulated_response)
|
408
|
+
])
|
409
|
+
|
410
|
+
def load_conversation_history(self, messages: List[Message]):
|
411
|
+
"""
|
412
|
+
Load conversation history from Django model instances.
|
413
|
+
|
414
|
+
This method maintains compatibility with existing Django-based conversation
|
415
|
+
storage while preparing the history for modern LangGraph processing.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
messages (List[Message]): List of Django Message model instances
|
419
|
+
Expected to have 'content' and 'is_bot' attributes
|
420
|
+
"""
|
421
|
+
self.chat_history.clear()
|
422
|
+
for message in messages:
|
423
|
+
if message.is_bot:
|
424
|
+
self.chat_history.append(AIMessage(content=message.content))
|
425
|
+
else:
|
426
|
+
self.chat_history.append(HumanMessage(content=message.content))
|
427
|
+
|
428
|
+
def save_messages(self, user_message: str, bot_response: str):
|
429
|
+
"""
|
430
|
+
Save messages to internal conversation history.
|
431
|
+
|
432
|
+
This method provides backward compatibility for manual history management.
|
433
|
+
|
434
|
+
Args:
|
435
|
+
user_message (str): The user's input message
|
436
|
+
bot_response (str): The bot's generated response
|
437
|
+
"""
|
438
|
+
self.chat_history.append(HumanMessage(content=user_message))
|
439
|
+
self.chat_history.append(AIMessage(content=bot_response))
|
440
|
+
|
441
|
+
def process_file(self, file: FileProcessorInterface):
|
442
|
+
"""
|
443
|
+
Process and index a file for contextual retrieval.
|
444
|
+
|
445
|
+
This method maintains compatibility with existing file processing workflows
|
446
|
+
while leveraging FAISS for efficient similarity search.
|
447
|
+
|
448
|
+
Args:
|
449
|
+
file (FileProcessorInterface): File processor instance that implements getText()
|
450
|
+
|
451
|
+
Note:
|
452
|
+
Processed files are automatically available for context retrieval
|
453
|
+
in subsequent conversations without additional configuration.
|
454
|
+
"""
|
455
|
+
document = file.getText()
|
456
|
+
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
|
457
|
+
texts = text_splitter.split_documents(document)
|
458
|
+
|
459
|
+
if self.vector_store is None:
|
460
|
+
self.vector_store = FAISS.from_texts(
|
461
|
+
[doc.page_content for doc in texts],
|
462
|
+
self.embeddings
|
463
|
+
)
|
464
|
+
else:
|
465
|
+
self.vector_store.add_texts([doc.page_content for doc in texts])
|
466
|
+
|
467
|
+
def clear_memory(self):
|
468
|
+
"""
|
469
|
+
Clear conversation history and processed file context.
|
470
|
+
|
471
|
+
This method resets the bot to a clean state, removing all conversation
|
472
|
+
history and processed file context.
|
473
|
+
"""
|
474
|
+
self.chat_history.clear()
|
475
|
+
self.vector_store = None
|
476
|
+
|
477
|
+
def get_chat_history(self) -> List[BaseMessage]:
|
478
|
+
"""
|
479
|
+
Retrieve a copy of the current conversation history.
|
480
|
+
|
481
|
+
Returns:
|
482
|
+
List[BaseMessage]: Copy of the conversation history
|
483
|
+
"""
|
484
|
+
return self.chat_history.copy()
|
485
|
+
|
486
|
+
def set_chat_history(self, history: List[BaseMessage]):
|
487
|
+
"""
|
488
|
+
Set the conversation history from a list of BaseMessage instances.
|
489
|
+
|
490
|
+
Args:
|
491
|
+
history (List[BaseMessage]): New conversation history to set
|
492
|
+
"""
|
493
|
+
self.chat_history = history.copy()
|
494
|
+
|
495
|
+
def _get_context(self, query: str) -> str:
|
496
|
+
"""
|
497
|
+
Retrieve relevant context from processed files using similarity search.
|
498
|
+
|
499
|
+
This method performs semantic search over processed file content to find
|
500
|
+
the most relevant information for the current query.
|
501
|
+
|
502
|
+
Args:
|
503
|
+
query (str): The query to search for relevant context
|
504
|
+
|
505
|
+
Returns:
|
506
|
+
str: Concatenated relevant context from processed files
|
507
|
+
"""
|
508
|
+
if self.vector_store:
|
509
|
+
docs = self.vector_store.similarity_search(query, k=4)
|
510
|
+
return "\n".join([doc.page_content for doc in docs])
|
511
|
+
return ""
|
512
|
+
|
513
|
+
def process_file(self, file: FileProcessorInterface):
|
514
|
+
"""API original - Procesa archivo y lo añade al vector store"""
|
515
|
+
document = file.getText()
|
516
|
+
text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
|
517
|
+
texts = text_splitter.split_documents(document)
|
518
|
+
|
519
|
+
if self.vector_store is None:
|
520
|
+
self.vector_store = FAISS.from_texts(
|
521
|
+
[doc.page_content for doc in texts],
|
522
|
+
self.embeddings
|
523
|
+
)
|
524
|
+
else:
|
525
|
+
self.vector_store.add_texts([doc.page_content for doc in texts])
|
526
|
+
|
527
|
+
def clear_memory(self):
|
528
|
+
"""API original - Limpia la memoria de conversación"""
|
529
|
+
self.chat_history.clear()
|
530
|
+
self.vector_store = None
|
531
|
+
|
532
|
+
def get_chat_history(self) -> List[BaseMessage]:
|
533
|
+
"""API original - Obtiene el historial completo"""
|
534
|
+
return self.chat_history.copy()
|
535
|
+
|
536
|
+
def set_chat_history(self, history: List[BaseMessage]):
|
537
|
+
"""API original - Establece el historial de conversación"""
|
538
|
+
self.chat_history = history.copy()
|
539
|
+
|
540
|
+
def _get_context(self, query: str) -> str:
|
541
|
+
"""Obtiene contexto relevante de archivos procesados"""
|
542
|
+
if self.vector_store:
|
543
|
+
docs = self.vector_store.similarity_search(query, k=4)
|
544
|
+
return "\n".join([doc.page_content for doc in docs])
|
545
|
+
return ""
|
546
|
+
|
547
|
+
# ===== MODERN ENHANCED CAPABILITIES =====
|
548
|
+
|
549
|
+
def get_response_with_thread(self, user_input: str, thread_id: str) -> ResponseModel:
|
550
|
+
"""
|
551
|
+
Generate response with automatic conversation persistence using thread IDs.
|
552
|
+
|
553
|
+
This method leverages LangGraph's checkpointing system to automatically
|
554
|
+
persist and retrieve conversation state based on thread identifiers.
|
555
|
+
|
556
|
+
Args:
|
557
|
+
user_input (str): The user's message or query
|
558
|
+
thread_id (str): Unique identifier for the conversation thread
|
559
|
+
|
560
|
+
Returns:
|
561
|
+
ResponseModel: Structured response with token usage and content
|
562
|
+
|
563
|
+
Raises:
|
564
|
+
ValueError: If checkpointer is not configured during initialization
|
565
|
+
|
566
|
+
Note:
|
567
|
+
Each thread_id maintains independent conversation state, enabling
|
568
|
+
multiple concurrent conversations per user or session.
|
569
|
+
"""
|
570
|
+
if not self.checkpointer:
|
571
|
+
raise ValueError("Checkpointer not configured. Initialize with use_checkpointer=True")
|
572
|
+
|
573
|
+
config = {"configurable": {"thread_id": thread_id}}
|
574
|
+
|
575
|
+
initial_state = {
|
576
|
+
"messages": [HumanMessage(content=user_input)],
|
577
|
+
"context": ""
|
578
|
+
}
|
579
|
+
|
580
|
+
result = self.graph.invoke(initial_state, config=config)
|
581
|
+
|
582
|
+
# Extract final response
|
583
|
+
final_response = ""
|
584
|
+
for msg in reversed(result["messages"]):
|
585
|
+
if isinstance(msg, AIMessage) and msg.content:
|
586
|
+
final_response = msg.content
|
587
|
+
break
|
588
|
+
|
589
|
+
# Extract token usage
|
590
|
+
token_usage = {}
|
591
|
+
last_message = result["messages"][-1]
|
592
|
+
if hasattr(last_message, 'response_metadata'):
|
593
|
+
token_usage = last_message.response_metadata.get('token_usage', {})
|
594
|
+
|
595
|
+
return ResponseModel(
|
596
|
+
user_tokens=token_usage.get('prompt_tokens', 0),
|
597
|
+
bot_tokens=token_usage.get('completion_tokens', 0),
|
598
|
+
response=final_response
|
599
|
+
)
|
600
|
+
|
601
|
+
def stream_with_thread(self, user_input: str, thread_id: str) -> Generator[Dict[str, Any], None, None]:
|
602
|
+
"""
|
603
|
+
Stream response with automatic conversation persistence.
|
604
|
+
|
605
|
+
This method combines streaming capabilities with thread-based persistence,
|
606
|
+
allowing real-time response generation while maintaining conversation state.
|
607
|
+
|
608
|
+
Args:
|
609
|
+
user_input (str): The user's message or query
|
610
|
+
thread_id (str): Unique identifier for the conversation thread
|
611
|
+
|
612
|
+
Yields:
|
613
|
+
Dict[str, Any]: Workflow execution chunks containing intermediate states
|
614
|
+
|
615
|
+
Raises:
|
616
|
+
ValueError: If checkpointer is not configured during initialization
|
617
|
+
"""
|
618
|
+
if not self.checkpointer:
|
619
|
+
raise ValueError("Checkpointer not configured. Initialize with use_checkpointer=True")
|
620
|
+
|
621
|
+
config = {"configurable": {"thread_id": thread_id}}
|
622
|
+
|
623
|
+
initial_state = {
|
624
|
+
"messages": [HumanMessage(content=user_input)],
|
625
|
+
"context": ""
|
626
|
+
}
|
627
|
+
|
628
|
+
for chunk in self.graph.stream(initial_state, config=config):
|
629
|
+
yield chunk
|
630
|
+
|
631
|
+
def get_mcp_status(self) -> Dict[str, Any]:
|
632
|
+
"""
|
633
|
+
Retrieve the current status of MCP (Model Context Protocol) integration.
|
634
|
+
|
635
|
+
This method provides diagnostic information about MCP server connections
|
636
|
+
and tool availability for monitoring and debugging purposes.
|
637
|
+
|
638
|
+
Returns:
|
639
|
+
Dict[str, Any]: MCP status information containing:
|
640
|
+
- mcp_enabled: Whether MCP is active
|
641
|
+
- servers: List of connected server names
|
642
|
+
- tools_count: Number of MCP-sourced tools
|
643
|
+
- total_tools: Total number of available tools
|
644
|
+
"""
|
645
|
+
if not self.mcp_client:
|
646
|
+
return {"mcp_enabled": False, "servers": [], "tools_count": 0}
|
647
|
+
|
648
|
+
mcp_tools_count = len([
|
649
|
+
tool for tool in self.tools
|
650
|
+
if hasattr(tool, '__module__') and tool.__module__ and 'mcp' in tool.__module__
|
651
|
+
])
|
652
|
+
|
653
|
+
return {
|
654
|
+
"mcp_enabled": True,
|
655
|
+
"servers": list(getattr(self.mcp_client, '_servers', {}).keys()),
|
656
|
+
"tools_count": mcp_tools_count,
|
657
|
+
"total_tools": len(self.tools)
|
658
|
+
}
|
659
|
+
|
660
|
+
def add_tool_dynamically(self, tool: BaseTool):
|
661
|
+
"""
|
662
|
+
Add a tool to the bot's capabilities at runtime.
|
663
|
+
|
664
|
+
This method allows dynamic tool addition after initialization, automatically
|
665
|
+
updating the model binding and workflow configuration.
|
666
|
+
|
667
|
+
Args:
|
668
|
+
tool (BaseTool): The LangChain tool to add to the bot's capabilities
|
669
|
+
|
670
|
+
Note:
|
671
|
+
Adding tools dynamically triggers a complete workflow reconstruction
|
672
|
+
to ensure proper tool integration and binding.
|
673
|
+
"""
|
674
|
+
self.tools.append(tool)
|
675
|
+
# Reconstruct model binding and workflow with new tool
|
676
|
+
self.model_with_tools = self._prepare_model_with_tools()
|
677
|
+
self.instructions = self._build_modern_instructions()
|
678
|
+
self.graph = self._create_modern_workflow()
|
679
|
+
|
680
|
+
# ===== UTILITY AND DIAGNOSTIC METHODS =====
|
681
|
+
|
682
|
+
def get_workflow_state(self) -> Dict[str, Any]:
|
683
|
+
"""
|
684
|
+
Get current workflow configuration for debugging and monitoring.
|
685
|
+
|
686
|
+
Returns:
|
687
|
+
Dict[str, Any]: Workflow state information including:
|
688
|
+
- tools_count: Number of available tools
|
689
|
+
- has_checkpointer: Whether persistence is enabled
|
690
|
+
- has_vector_store: Whether file processing is active
|
691
|
+
- chat_history_length: Current conversation length
|
692
|
+
"""
|
693
|
+
return {
|
694
|
+
"tools_count": len(self.tools),
|
695
|
+
"has_checkpointer": self.checkpointer is not None,
|
696
|
+
"has_vector_store": self.vector_store is not None,
|
697
|
+
"chat_history_length": len(self.chat_history),
|
698
|
+
"mcp_enabled": self.mcp_client is not None
|
699
|
+
}
|
700
|
+
|
701
|
+
def reset_conversation(self):
|
702
|
+
"""
|
703
|
+
Reset conversation state while preserving configuration and processed files.
|
704
|
+
|
705
|
+
This method clears only the conversation history while maintaining
|
706
|
+
tool configurations, file context, and other persistent settings.
|
707
|
+
"""
|
708
|
+
self.chat_history.clear()
|
709
|
+
|
710
|
+
def get_tool_names(self) -> List[str]:
|
711
|
+
"""
|
712
|
+
Get list of available tool names for diagnostic purposes.
|
713
|
+
|
714
|
+
Returns:
|
715
|
+
List[str]: Names of all currently available tools
|
716
|
+
"""
|
717
|
+
return [tool.name for tool in self.tools]
|
718
|
+
|
719
|
+
# ===== FIN DE LA CLASE =====
|
720
|
+
# No hay métodos legacy innecesarios
|
721
|
+
|
722
|
+
|
@@ -0,0 +1,215 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: sonika-langchain-bot
|
3
|
+
Version: 0.0.12
|
4
|
+
Summary: Agente langchain con LLM
|
5
|
+
Author: Erley Blanco Carvajal
|
6
|
+
License: MIT License
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
9
|
+
Classifier: Operating System :: OS Independent
|
10
|
+
Requires-Python: >=3.6
|
11
|
+
Description-Content-Type: text/markdown
|
12
|
+
License-File: LICENSE
|
13
|
+
Requires-Dist: langchain-mcp-adapters==0.1.9
|
14
|
+
Requires-Dist: langchain-community==0.3.26
|
15
|
+
Requires-Dist: langchain-core==0.3.66
|
16
|
+
Requires-Dist: langchain-openai==0.3.24
|
17
|
+
Requires-Dist: langgraph==0.4.8
|
18
|
+
Requires-Dist: langgraph-checkpoint==2.1.0
|
19
|
+
Requires-Dist: langgraph-sdk==0.1.70
|
20
|
+
Requires-Dist: dataclasses-json==0.6.7
|
21
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
22
|
+
Requires-Dist: pydantic==2.11.7
|
23
|
+
Requires-Dist: faiss-cpu==1.11.0
|
24
|
+
Requires-Dist: pypdf==5.6.1
|
25
|
+
Requires-Dist: python-dotenv==1.0.1
|
26
|
+
Requires-Dist: typing_extensions==4.14.0
|
27
|
+
Requires-Dist: typing-inspect==0.9.0
|
28
|
+
Provides-Extra: dev
|
29
|
+
Requires-Dist: sphinx<9.0.0,>=8.1.3; extra == "dev"
|
30
|
+
Requires-Dist: sphinx-rtd-theme<4.0.0,>=3.0.1; extra == "dev"
|
31
|
+
Dynamic: author
|
32
|
+
Dynamic: classifier
|
33
|
+
Dynamic: description
|
34
|
+
Dynamic: description-content-type
|
35
|
+
Dynamic: license
|
36
|
+
Dynamic: license-file
|
37
|
+
Dynamic: provides-extra
|
38
|
+
Dynamic: requires-dist
|
39
|
+
Dynamic: requires-python
|
40
|
+
Dynamic: summary
|
41
|
+
|
42
|
+
# Sonika LangChain Bot <a href="https://pepy.tech/projects/sonika-langchain-bot"><img src="https://static.pepy.tech/badge/sonika-langchain-bot" alt="PyPI Downloads"></a>
|
43
|
+
|
44
|
+
A Python library that implements a conversational agent using LangChain with tool execution capabilities and text classification.
|
45
|
+
|
46
|
+
## Installation
|
47
|
+
|
48
|
+
```bash
|
49
|
+
pip install sonika-langchain-bot
|
50
|
+
```
|
51
|
+
|
52
|
+
## Prerequisites
|
53
|
+
|
54
|
+
You'll need the following API keys:
|
55
|
+
|
56
|
+
- OpenAI API Key
|
57
|
+
|
58
|
+
Create a `.env` file in the root of your project with the following variables:
|
59
|
+
|
60
|
+
```env
|
61
|
+
OPENAI_API_KEY=your_api_key_here
|
62
|
+
```
|
63
|
+
|
64
|
+
## Key Features
|
65
|
+
|
66
|
+
- Conversational agent with tool execution capabilities
|
67
|
+
- Text classification with structured output
|
68
|
+
- Custom tool integration
|
69
|
+
- Streaming responses
|
70
|
+
- Conversation history management
|
71
|
+
- Flexible instruction-based behavior
|
72
|
+
|
73
|
+
## Basic Usage
|
74
|
+
|
75
|
+
### Agent with Tools Example
|
76
|
+
|
77
|
+
```python
|
78
|
+
import os
|
79
|
+
from dotenv import load_dotenv
|
80
|
+
from langchain_openai import OpenAIEmbeddings
|
81
|
+
from sonika_langchain_bot.langchain_tools import EmailTool
|
82
|
+
from sonika_langchain_bot.langchain_bot_agent import LangChainBot
|
83
|
+
from sonika_langchain_bot.langchain_class import Message, ResponseModel
|
84
|
+
from sonika_langchain_bot.langchain_models import OpenAILanguageModel
|
85
|
+
|
86
|
+
# Load environment variables
|
87
|
+
load_dotenv()
|
88
|
+
|
89
|
+
# Get API key from .env file
|
90
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
91
|
+
|
92
|
+
# Initialize language model and embeddings
|
93
|
+
language_model = OpenAILanguageModel(api_key, model_name='gpt-4o-mini-2024-07-18', temperature=1)
|
94
|
+
embeddings = OpenAIEmbeddings(api_key=api_key)
|
95
|
+
|
96
|
+
# Configure tools
|
97
|
+
tools = [EmailTool()]
|
98
|
+
|
99
|
+
# Create agent instance
|
100
|
+
bot = LangChainBot(language_model, embeddings, instructions="You are an agent", tools=tools)
|
101
|
+
|
102
|
+
# Load conversation history
|
103
|
+
bot.load_conversation_history([Message(content="My name is Erley", is_bot=False)])
|
104
|
+
|
105
|
+
# Get response
|
106
|
+
user_message = 'Send an email with the tool to erley@gmail.com with subject Hello and message Hello Erley'
|
107
|
+
response_model: ResponseModel = bot.get_response(user_message)
|
108
|
+
|
109
|
+
print(response_model)
|
110
|
+
```
|
111
|
+
|
112
|
+
### Streaming Response Example
|
113
|
+
|
114
|
+
```python
|
115
|
+
import os
|
116
|
+
from dotenv import load_dotenv
|
117
|
+
from langchain_openai import OpenAIEmbeddings
|
118
|
+
from sonika_langchain_bot.langchain_bot_agent import LangChainBot
|
119
|
+
from sonika_langchain_bot.langchain_class import Message
|
120
|
+
from sonika_langchain_bot.langchain_models import OpenAILanguageModel
|
121
|
+
|
122
|
+
# Load environment variables
|
123
|
+
load_dotenv()
|
124
|
+
|
125
|
+
# Get API key from .env file
|
126
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
127
|
+
|
128
|
+
# Initialize language model and embeddings
|
129
|
+
language_model = OpenAILanguageModel(api_key, model_name='gpt-4o-mini-2024-07-18', temperature=1)
|
130
|
+
embeddings = OpenAIEmbeddings(api_key=api_key)
|
131
|
+
|
132
|
+
# Create agent instance
|
133
|
+
bot = LangChainBot(language_model, embeddings, instructions="Only answers in english", tools=[])
|
134
|
+
|
135
|
+
# Load conversation history
|
136
|
+
bot.load_conversation_history([Message(content="My name is Erley", is_bot=False)])
|
137
|
+
|
138
|
+
# Get streaming response
|
139
|
+
user_message = 'Hello, what is my name?'
|
140
|
+
for chunk in bot.get_response_stream(user_message):
|
141
|
+
print(chunk)
|
142
|
+
```
|
143
|
+
|
144
|
+
### Text Classification Example
|
145
|
+
|
146
|
+
```python
|
147
|
+
import os
|
148
|
+
from dotenv import load_dotenv
|
149
|
+
from sonika_langchain_bot.langchain_clasificator import TextClassifier
|
150
|
+
from sonika_langchain_bot.langchain_models import OpenAILanguageModel
|
151
|
+
from pydantic import BaseModel, Field
|
152
|
+
|
153
|
+
# Load environment variables
|
154
|
+
load_dotenv()
|
155
|
+
|
156
|
+
# Define classification structure with Pydantic
|
157
|
+
class Classification(BaseModel):
|
158
|
+
intention: str = Field()
|
159
|
+
sentiment: str = Field(..., enum=["happy", "neutral", "sad", "excited"])
|
160
|
+
aggressiveness: int = Field(
|
161
|
+
...,
|
162
|
+
description="describes how aggressive the statement is, the higher the number the more aggressive",
|
163
|
+
enum=[1, 2, 3, 4, 5],
|
164
|
+
)
|
165
|
+
language: str = Field(
|
166
|
+
..., enum=["spanish", "english", "french", "german", "italian"]
|
167
|
+
)
|
168
|
+
|
169
|
+
# Initialize classifier
|
170
|
+
api_key = os.getenv("OPENAI_API_KEY")
|
171
|
+
model = OpenAILanguageModel(api_key=api_key)
|
172
|
+
classifier = TextClassifier(llm=model, validation_class=Classification)
|
173
|
+
|
174
|
+
# Classify text
|
175
|
+
result = classifier.classify("how are you?")
|
176
|
+
print(result)
|
177
|
+
```
|
178
|
+
|
179
|
+
## Available Classes and Components
|
180
|
+
|
181
|
+
### Core Classes
|
182
|
+
|
183
|
+
- **LangChainBot**: Main conversational agent for task execution with tools
|
184
|
+
- **OpenAILanguageModel**: Wrapper for OpenAI language models
|
185
|
+
- **TextClassifier**: Text classification using structured output
|
186
|
+
- **Message**: Message structure for conversation history
|
187
|
+
- **ResponseModel**: Response structure from agent interactions
|
188
|
+
|
189
|
+
### Tools
|
190
|
+
|
191
|
+
- **EmailTool**: Tool for sending emails through the agent
|
192
|
+
|
193
|
+
## Project Structure
|
194
|
+
|
195
|
+
```
|
196
|
+
your_project/
|
197
|
+
├── .env # Environment variables
|
198
|
+
├── src/
|
199
|
+
│ └── sonika_langchain_bot/
|
200
|
+
│ ├── langchain_bot_agent.py
|
201
|
+
│ ├── langchain_clasificator.py
|
202
|
+
│ ├── langchain_class.py
|
203
|
+
│ ├── langchain_models.py
|
204
|
+
│ └── langchain_tools.py
|
205
|
+
└── tests/
|
206
|
+
└── test_bot.py
|
207
|
+
```
|
208
|
+
|
209
|
+
## Contributing
|
210
|
+
|
211
|
+
Contributions are welcome. Please open an issue to discuss major changes you'd like to make.
|
212
|
+
|
213
|
+
## License
|
214
|
+
|
215
|
+
This project is licensed under the MIT License.
|
@@ -1,13 +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=SBqiLWWTpSHi_v_pC6XelHyMpiSC-g2n1fGipZbgUQk,28631
|
3
4
|
sonika_langchain_bot/langchain_bot_agent_bdi.py,sha256=Ev0hhRQYe6kyGAHiFDhFsfu6QnTwUFaA9oB8DfNV7u4,8613
|
4
5
|
sonika_langchain_bot/langchain_clasificator.py,sha256=GR85ZAliymBSoDa5PXB31BvJkuiokGjS2v3RLdXnzzk,1381
|
5
6
|
sonika_langchain_bot/langchain_class.py,sha256=5anB6v_wCzEoAJRb8fV9lPPS72E7-k51y_aeiip8RAw,1114
|
6
7
|
sonika_langchain_bot/langchain_files.py,sha256=SEyqnJgBc_nbCIG31eypunBbO33T5AHFOhQZcghTks4,381
|
7
8
|
sonika_langchain_bot/langchain_models.py,sha256=vqSSZ48tNofrTMLv1QugDdyey2MuIeSdlLSD37AnzkI,2235
|
8
9
|
sonika_langchain_bot/langchain_tools.py,sha256=y7wLf1DbUua3QIvz938Ek-JIMOuQhrOIptJadW8OIsU,466
|
9
|
-
sonika_langchain_bot-0.0.
|
10
|
-
sonika_langchain_bot-0.0.
|
11
|
-
sonika_langchain_bot-0.0.
|
12
|
-
sonika_langchain_bot-0.0.
|
13
|
-
sonika_langchain_bot-0.0.
|
10
|
+
sonika_langchain_bot-0.0.12.dist-info/licenses/LICENSE,sha256=O8VZ4aU_rUMAArvYTm2bshcZ991huv_tpfB5BKHH9Q8,1064
|
11
|
+
sonika_langchain_bot-0.0.12.dist-info/METADATA,sha256=hgBZ7RuN4683itsMD6gggfJrOtg-IrqV5tNbcIgnWb0,6380
|
12
|
+
sonika_langchain_bot-0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
+
sonika_langchain_bot-0.0.12.dist-info/top_level.txt,sha256=UsTTSZFEw2wrPSVh4ufu01e2m_E7O_QVYT_k4zCQaAE,21
|
14
|
+
sonika_langchain_bot-0.0.12.dist-info/RECORD,,
|
@@ -1,145 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: sonika-langchain-bot
|
3
|
-
Version: 0.0.10
|
4
|
-
Summary: Agente langchain con LLM
|
5
|
-
Author: Erley Blanco Carvajal
|
6
|
-
License: MIT License
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
8
|
-
Classifier: License :: OSI Approved :: MIT License
|
9
|
-
Classifier: Operating System :: OS Independent
|
10
|
-
Requires-Python: >=3.6
|
11
|
-
Description-Content-Type: text/markdown
|
12
|
-
License-File: LICENSE
|
13
|
-
Requires-Dist: langchain <1.0.0,>=0.3.0
|
14
|
-
Requires-Dist: langchain-community <1.0.0,>=0.3.0
|
15
|
-
Requires-Dist: langchain-core <1.0.0,>=0.3.5
|
16
|
-
Requires-Dist: langchain-openai <1.0.0,>=0.2.0
|
17
|
-
Requires-Dist: langgraph <1.0.0,>=0.2.39
|
18
|
-
Requires-Dist: langgraph-checkpoint <3.0.0,>=2.0.2
|
19
|
-
Requires-Dist: langgraph-sdk <2.0.0,>=0.1.34
|
20
|
-
Requires-Dist: dataclasses-json <1.0.0,>=0.6.7
|
21
|
-
Requires-Dist: python-dateutil <3.0.0,>=2.9.0
|
22
|
-
Requires-Dist: tiktoken <1.0.0,>=0.7.0
|
23
|
-
Requires-Dist: pydantic <3.0.0,>=2.9.2
|
24
|
-
Requires-Dist: faiss-cpu <2.0.0,>=1.8.0
|
25
|
-
Requires-Dist: pypdf <6.0.0,>=5.0.0
|
26
|
-
Requires-Dist: python-dotenv <2.0.0,>=1.0.1
|
27
|
-
Requires-Dist: typing-extensions <5.0.0,>=4.12.0
|
28
|
-
Requires-Dist: typing-inspect <1.0.0,>=0.9.0
|
29
|
-
Provides-Extra: dev
|
30
|
-
Requires-Dist: sphinx <9.0.0,>=8.1.3 ; extra == 'dev'
|
31
|
-
Requires-Dist: sphinx-rtd-theme <4.0.0,>=3.0.1 ; extra == 'dev'
|
32
|
-
|
33
|
-
# Sonika LangChain Bot <a href="https://pepy.tech/projects/sonika-langchain-bot"><img src="https://static.pepy.tech/badge/sonika-langchain-bot" alt="PyPI Downloads"></a>
|
34
|
-
|
35
|
-
|
36
|
-
Una librería Python que implementa un bot conversacional utilizando LangChain con capacidades BDI (Belief-Desire-Intention) y clasificación de texto.
|
37
|
-
|
38
|
-
## Instalación
|
39
|
-
|
40
|
-
```bash
|
41
|
-
pip install sonika-langchain-bot
|
42
|
-
```
|
43
|
-
|
44
|
-
## Requisitos previos
|
45
|
-
|
46
|
-
Necesitarás las siguientes API keys:
|
47
|
-
|
48
|
-
- OpenAI API Key
|
49
|
-
|
50
|
-
Crea un archivo `.env` en la raíz de tu proyecto con las siguientes variables:
|
51
|
-
|
52
|
-
```env
|
53
|
-
OPENAI_API_KEY=tu_api_key_aqui
|
54
|
-
```
|
55
|
-
|
56
|
-
## Características principales
|
57
|
-
|
58
|
-
- Bot conversacional con arquitectura BDI
|
59
|
-
- Clasificación de texto
|
60
|
-
- Ejecución de código personalizado por medio de tools
|
61
|
-
|
62
|
-
## Uso básico
|
63
|
-
|
64
|
-
### Ejemplo de Bot BDI
|
65
|
-
|
66
|
-
```python
|
67
|
-
from sonika_langchain_bot.langchain_bdi import Belief, BeliefType
|
68
|
-
from sonika_langchain_bot.langchain_bot_agent_bdi import LangChainBot
|
69
|
-
from sonika_langchain_bot.langchain_models import OpenAILanguageModel
|
70
|
-
from langchain_openai import OpenAIEmbeddings
|
71
|
-
|
72
|
-
# Inicializar el modelo de lenguaje
|
73
|
-
language_model = OpenAILanguageModel(api_key, model_name='gpt-4o-mini', temperature=1)
|
74
|
-
embeddings = OpenAIEmbeddings(api_key=api_key)
|
75
|
-
|
76
|
-
# Configurar herramientas propias o de terceros
|
77
|
-
search = TavilySearchResults(max_results=2, api_key=api_key_tavily)
|
78
|
-
tools = [search]
|
79
|
-
|
80
|
-
# Configurar creencias
|
81
|
-
beliefs = [
|
82
|
-
Belief(
|
83
|
-
content="Eres un asistente de chat",
|
84
|
-
type=BeliefType.PERSONALITY,
|
85
|
-
confidence=1,
|
86
|
-
source='personality'
|
87
|
-
)
|
88
|
-
]
|
89
|
-
|
90
|
-
# Crear instancia del bot
|
91
|
-
bot = LangChainBot(language_model, embeddings, beliefs=beliefs, tools=tools)
|
92
|
-
|
93
|
-
# Obtener respuesta
|
94
|
-
response = bot.get_response("Hola como te llamas?")
|
95
|
-
|
96
|
-
bot = LangChainBot(language_model, embeddings, beliefs=beliefs, tools=tools)
|
97
|
-
|
98
|
-
user_message = 'Hola como me llamo?'
|
99
|
-
|
100
|
-
#Cargas la conversacion previa con el bot
|
101
|
-
bot.load_conversation_history([Message(content="Mi nombre es Erley", is_bot=False)])
|
102
|
-
# Obtener la respuesta del bot
|
103
|
-
response_model: ResponseModel = bot.get_response(user_message)
|
104
|
-
bot_response = response_model
|
105
|
-
|
106
|
-
print(bot_response)
|
107
|
-
|
108
|
-
#o por streaming
|
109
|
-
for chunk in bot.get_response_stream(user_message):
|
110
|
-
print(chunk)
|
111
|
-
|
112
|
-
```
|
113
|
-
|
114
|
-
### Ejemplo de Clasificación de Texto
|
115
|
-
|
116
|
-
```python
|
117
|
-
from sonika_langchain_bot.langchain_clasificator import TextClassifier
|
118
|
-
from sonika_langchain_bot.langchain_models import OpenAILanguageModel
|
119
|
-
from pydantic import BaseModel, Field
|
120
|
-
|
121
|
-
# Definir estructura de clasificación
|
122
|
-
class Classification(BaseModel):
|
123
|
-
intention: str = Field()
|
124
|
-
sentiment: str = Field(..., enum=["feliz", "neutral", "triste", "excitado"])
|
125
|
-
aggressiveness: int = Field(
|
126
|
-
...,
|
127
|
-
description="describes how aggressive the statement is",
|
128
|
-
enum=[1, 2, 3, 4, 5],
|
129
|
-
)
|
130
|
-
language: str = Field(
|
131
|
-
..., enum=["español", "ingles", "frances", "aleman", "italiano"]
|
132
|
-
)
|
133
|
-
|
134
|
-
# Inicializar clasificador
|
135
|
-
model = OpenAILanguageModel(api_key=api_key)
|
136
|
-
classifier = TextClassifier(llm=model, validation_class=Classification)
|
137
|
-
|
138
|
-
# Clasificar texto
|
139
|
-
result = classifier.classify("Tu texto aquí")
|
140
|
-
```
|
141
|
-
|
142
|
-
## Contribución
|
143
|
-
|
144
|
-
Las contribuciones son bienvenidas. Por favor, abre un issue para discutir los cambios importantes que te gustaría hacer.
|
145
|
-
|
{sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info/licenses}/LICENSE
RENAMED
File without changes
|
{sonika_langchain_bot-0.0.10.dist-info → sonika_langchain_bot-0.0.12.dist-info}/top_level.txt
RENAMED
File without changes
|