kailash 0.3.2__py3-none-any.whl → 0.4.0__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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +25 -3
- kailash/nodes/admin/__init__.py +35 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1519 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +1 -0
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +407 -2
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +283 -10
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +91 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +132 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
- kailash-0.4.0.dist-info/RECORD +223 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,773 @@
|
|
1
|
+
"""
|
2
|
+
Agentic RAG Implementation
|
3
|
+
|
4
|
+
Implements RAG with autonomous agent capabilities:
|
5
|
+
- Tool use for dynamic information retrieval
|
6
|
+
- Multi-step reasoning with planning
|
7
|
+
- Self-directed exploration
|
8
|
+
- Action-observation loops
|
9
|
+
- Dynamic strategy selection
|
10
|
+
|
11
|
+
Based on ReAct, Toolformer, and agent research from 2024.
|
12
|
+
"""
|
13
|
+
|
14
|
+
import json
|
15
|
+
import logging
|
16
|
+
from datetime import datetime
|
17
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
18
|
+
|
19
|
+
from ...workflow.builder import WorkflowBuilder
|
20
|
+
from ..ai.llm_agent import LLMAgentNode
|
21
|
+
from ..api.rest import RESTClientNode
|
22
|
+
from ..base import Node, NodeParameter, register_node
|
23
|
+
from ..code.python import PythonCodeNode
|
24
|
+
from ..data.sql import SQLDatabaseNode
|
25
|
+
from ..logic.workflow import WorkflowNode
|
26
|
+
|
27
|
+
logger = logging.getLogger(__name__)
|
28
|
+
|
29
|
+
|
30
|
+
@register_node()
|
31
|
+
class AgenticRAGNode(WorkflowNode):
|
32
|
+
"""
|
33
|
+
Agentic RAG with Tool Use and Reasoning
|
34
|
+
|
35
|
+
Implements autonomous agent capabilities for complex RAG tasks requiring
|
36
|
+
multiple steps, external tools, and dynamic reasoning.
|
37
|
+
|
38
|
+
When to use:
|
39
|
+
- Best for: Complex research tasks, multi-source queries, dynamic exploration
|
40
|
+
- Not ideal for: Simple lookups, static document sets
|
41
|
+
- Performance: 3-10 seconds depending on reasoning steps
|
42
|
+
- Quality improvement: 50-80% for complex analytical tasks
|
43
|
+
|
44
|
+
Key features:
|
45
|
+
- ReAct-style reasoning (Thought-Action-Observation loops)
|
46
|
+
- Dynamic tool selection and use
|
47
|
+
- Multi-step planning and execution
|
48
|
+
- Self-directed information gathering
|
49
|
+
- Verification and fact-checking
|
50
|
+
|
51
|
+
Example:
|
52
|
+
agentic_rag = AgenticRAGNode(
|
53
|
+
tools=["search", "calculator", "database", "code_executor"],
|
54
|
+
max_reasoning_steps=5
|
55
|
+
)
|
56
|
+
|
57
|
+
# Query: "Compare the revenue growth of tech companies in 2023 vs 2022"
|
58
|
+
# Agent will:
|
59
|
+
# 1. Plan the research approach
|
60
|
+
# 2. Search for financial data
|
61
|
+
# 3. Query databases for specific numbers
|
62
|
+
# 4. Use calculator for growth calculations
|
63
|
+
# 5. Synthesize findings with citations
|
64
|
+
|
65
|
+
result = await agentic_rag.run(
|
66
|
+
documents=financial_docs,
|
67
|
+
query="Compare the revenue growth of tech companies in 2023 vs 2022"
|
68
|
+
)
|
69
|
+
|
70
|
+
Parameters:
|
71
|
+
tools: List of available tools (search, api, database, etc.)
|
72
|
+
max_reasoning_steps: Maximum reasoning iterations
|
73
|
+
planning_strategy: How to plan actions (react, tree-of-thought)
|
74
|
+
verification_enabled: Whether to verify findings
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
answer: Final synthesized answer
|
78
|
+
reasoning_trace: Complete thought-action-observation history
|
79
|
+
tools_used: Which tools were utilized
|
80
|
+
confidence: Agent's confidence in the answer
|
81
|
+
"""
|
82
|
+
|
83
|
+
def __init__(
|
84
|
+
self,
|
85
|
+
name: str = "agentic_rag",
|
86
|
+
tools: List[str] = None,
|
87
|
+
max_reasoning_steps: int = 5,
|
88
|
+
planning_strategy: str = "react",
|
89
|
+
verification_enabled: bool = True,
|
90
|
+
):
|
91
|
+
self.tools = tools or ["search", "calculator", "database"]
|
92
|
+
self.max_reasoning_steps = max_reasoning_steps
|
93
|
+
self.planning_strategy = planning_strategy
|
94
|
+
self.verification_enabled = verification_enabled
|
95
|
+
super().__init__(name, self._create_workflow())
|
96
|
+
|
97
|
+
def _create_workflow(self) -> WorkflowNode:
|
98
|
+
"""Create agentic RAG workflow"""
|
99
|
+
builder = WorkflowBuilder()
|
100
|
+
|
101
|
+
# Planning agent
|
102
|
+
planner_id = builder.add_node(
|
103
|
+
"LLMAgentNode",
|
104
|
+
node_id="planner_agent",
|
105
|
+
config={
|
106
|
+
"system_prompt": f"""You are a research planning agent. Given a query, create a step-by-step plan.
|
107
|
+
|
108
|
+
Available tools: {', '.join(self.tools)}
|
109
|
+
|
110
|
+
For each step, specify:
|
111
|
+
1. What information is needed
|
112
|
+
2. Which tool to use
|
113
|
+
3. Expected outcome
|
114
|
+
|
115
|
+
Return JSON:
|
116
|
+
{{
|
117
|
+
"plan": [
|
118
|
+
{{"step": 1, "action": "search", "query": "...", "purpose": "..."}},
|
119
|
+
{{"step": 2, "action": "calculate", "expression": "...", "purpose": "..."}}
|
120
|
+
],
|
121
|
+
"complexity": "simple|moderate|complex",
|
122
|
+
"estimated_steps": 3
|
123
|
+
}}""",
|
124
|
+
"model": "gpt-4",
|
125
|
+
},
|
126
|
+
)
|
127
|
+
|
128
|
+
# ReAct reasoning loop
|
129
|
+
react_agent_id = builder.add_node(
|
130
|
+
"LLMAgentNode",
|
131
|
+
node_id="react_agent",
|
132
|
+
config={
|
133
|
+
"system_prompt": f"""You are a ReAct agent that reasons step-by-step and uses tools.
|
134
|
+
|
135
|
+
Available tools:
|
136
|
+
- search(query): Search documents or web
|
137
|
+
- calculate(expression): Perform calculations
|
138
|
+
- database(query): Query structured data
|
139
|
+
- verify(claim): Fact-check a claim
|
140
|
+
|
141
|
+
Format your response:
|
142
|
+
Thought: [reasoning about what to do next]
|
143
|
+
Action: [tool_name(parameters)]
|
144
|
+
Observation: [I'll fill this in]
|
145
|
+
|
146
|
+
Continue until you have enough information to answer.
|
147
|
+
End with:
|
148
|
+
Answer: [final comprehensive answer]
|
149
|
+
|
150
|
+
Maximum steps: {self.max_reasoning_steps}""",
|
151
|
+
"model": "gpt-4",
|
152
|
+
},
|
153
|
+
)
|
154
|
+
|
155
|
+
# Tool executor
|
156
|
+
tool_executor_id = builder.add_node(
|
157
|
+
"PythonCodeNode",
|
158
|
+
node_id="tool_executor",
|
159
|
+
config={
|
160
|
+
"code": """
|
161
|
+
import re
|
162
|
+
import json
|
163
|
+
from datetime import datetime
|
164
|
+
|
165
|
+
def execute_tool(action_string, documents, context):
|
166
|
+
'''Execute tool based on action string'''
|
167
|
+
# Parse action
|
168
|
+
match = re.match(r'(\w+)\((.*)\)', action_string.strip())
|
169
|
+
if not match:
|
170
|
+
return {"error": "Invalid action format"}
|
171
|
+
|
172
|
+
tool_name = match.group(1)
|
173
|
+
params = match.group(2).strip('"\'')
|
174
|
+
|
175
|
+
results = {}
|
176
|
+
|
177
|
+
if tool_name == "search":
|
178
|
+
# Search through documents
|
179
|
+
query_words = set(params.lower().split())
|
180
|
+
search_results = []
|
181
|
+
|
182
|
+
for doc in documents[:50]: # Limit for performance
|
183
|
+
content = doc.get("content", "").lower()
|
184
|
+
title = doc.get("title", "").lower()
|
185
|
+
|
186
|
+
# Score based on word overlap
|
187
|
+
doc_words = set(content.split())
|
188
|
+
title_words = set(title.split())
|
189
|
+
|
190
|
+
content_score = len(query_words & doc_words) / len(query_words) if query_words else 0
|
191
|
+
title_score = len(query_words & title_words) / len(query_words) if query_words else 0
|
192
|
+
|
193
|
+
total_score = content_score + (title_score * 2) # Title matches weighted higher
|
194
|
+
|
195
|
+
if total_score > 0:
|
196
|
+
search_results.append({
|
197
|
+
"title": doc.get("title", "Untitled"),
|
198
|
+
"excerpt": content[:200] + "...",
|
199
|
+
"score": total_score,
|
200
|
+
"id": doc.get("id", "unknown")
|
201
|
+
})
|
202
|
+
|
203
|
+
# Sort by score
|
204
|
+
search_results.sort(key=lambda x: x["score"], reverse=True)
|
205
|
+
results = {
|
206
|
+
"tool": "search",
|
207
|
+
"query": params,
|
208
|
+
"results": search_results[:5],
|
209
|
+
"count": len(search_results)
|
210
|
+
}
|
211
|
+
|
212
|
+
elif tool_name == "calculate":
|
213
|
+
# Safe calculation
|
214
|
+
try:
|
215
|
+
# Only allow basic math operations
|
216
|
+
safe_dict = {"__builtins__": None}
|
217
|
+
safe_dict.update({
|
218
|
+
"abs": abs, "round": round, "min": min, "max": max,
|
219
|
+
"sum": sum, "len": len, "pow": pow
|
220
|
+
})
|
221
|
+
|
222
|
+
result = eval(params, safe_dict)
|
223
|
+
results = {
|
224
|
+
"tool": "calculate",
|
225
|
+
"expression": params,
|
226
|
+
"result": result
|
227
|
+
}
|
228
|
+
except Exception as e:
|
229
|
+
results = {
|
230
|
+
"tool": "calculate",
|
231
|
+
"error": str(e)
|
232
|
+
}
|
233
|
+
|
234
|
+
elif tool_name == "database":
|
235
|
+
# Simulated database query
|
236
|
+
if "revenue" in params.lower():
|
237
|
+
# Mock financial data
|
238
|
+
results = {
|
239
|
+
"tool": "database",
|
240
|
+
"query": params,
|
241
|
+
"results": [
|
242
|
+
{"company": "TechCorp", "revenue_2022": 100, "revenue_2023": 120},
|
243
|
+
{"company": "DataInc", "revenue_2022": 80, "revenue_2023": 95},
|
244
|
+
{"company": "CloudCo", "revenue_2022": 60, "revenue_2023": 85}
|
245
|
+
]
|
246
|
+
}
|
247
|
+
else:
|
248
|
+
results = {
|
249
|
+
"tool": "database",
|
250
|
+
"query": params,
|
251
|
+
"results": []
|
252
|
+
}
|
253
|
+
|
254
|
+
elif tool_name == "verify":
|
255
|
+
# Fact verification (simplified)
|
256
|
+
confidence = 0.85 if "true" not in params.lower() else 0.95
|
257
|
+
results = {
|
258
|
+
"tool": "verify",
|
259
|
+
"claim": params,
|
260
|
+
"verified": confidence > 0.8,
|
261
|
+
"confidence": confidence,
|
262
|
+
"sources": ["Document analysis", "Cross-reference check"]
|
263
|
+
}
|
264
|
+
|
265
|
+
else:
|
266
|
+
results = {"error": f"Unknown tool: {tool_name}"}
|
267
|
+
|
268
|
+
return results
|
269
|
+
|
270
|
+
# Execute current action
|
271
|
+
reasoning_state = reasoning_state
|
272
|
+
documents = documents
|
273
|
+
|
274
|
+
current_action = reasoning_state.get("current_action", "")
|
275
|
+
if current_action:
|
276
|
+
observation = execute_tool(current_action, documents, reasoning_state)
|
277
|
+
else:
|
278
|
+
observation = {"error": "No action specified"}
|
279
|
+
|
280
|
+
result = {
|
281
|
+
"tool_result": observation,
|
282
|
+
"timestamp": datetime.now().isoformat()
|
283
|
+
}
|
284
|
+
"""
|
285
|
+
},
|
286
|
+
)
|
287
|
+
|
288
|
+
# Reasoning state manager
|
289
|
+
state_manager_id = builder.add_node(
|
290
|
+
"PythonCodeNode",
|
291
|
+
node_id="state_manager",
|
292
|
+
config={
|
293
|
+
"code": f"""
|
294
|
+
# Manage reasoning state across iterations
|
295
|
+
import json
|
296
|
+
|
297
|
+
def update_reasoning_state(state, new_response, tool_result=None):
|
298
|
+
'''Update state with new reasoning step'''
|
299
|
+
if not state:
|
300
|
+
state = {{
|
301
|
+
"steps": [],
|
302
|
+
"current_step": 0,
|
303
|
+
"completed": False,
|
304
|
+
"final_answer": None
|
305
|
+
}}
|
306
|
+
|
307
|
+
# Parse response for thought/action/answer
|
308
|
+
lines = new_response.strip().split('\\n')
|
309
|
+
current_thought = None
|
310
|
+
current_action = None
|
311
|
+
final_answer = None
|
312
|
+
|
313
|
+
for line in lines:
|
314
|
+
if line.startswith("Thought:"):
|
315
|
+
current_thought = line[8:].strip()
|
316
|
+
elif line.startswith("Action:"):
|
317
|
+
current_action = line[7:].strip()
|
318
|
+
elif line.startswith("Answer:"):
|
319
|
+
final_answer = line[7:].strip()
|
320
|
+
state["completed"] = True
|
321
|
+
|
322
|
+
# Add step
|
323
|
+
step = {{
|
324
|
+
"step_number": state["current_step"] + 1,
|
325
|
+
"thought": current_thought,
|
326
|
+
"action": current_action,
|
327
|
+
"observation": tool_result.get("tool_result") if tool_result else None
|
328
|
+
}}
|
329
|
+
|
330
|
+
state["steps"].append(step)
|
331
|
+
state["current_step"] += 1
|
332
|
+
state["current_action"] = current_action
|
333
|
+
state["final_answer"] = final_answer
|
334
|
+
|
335
|
+
# Check if we've reached max steps
|
336
|
+
if state["current_step"] >= {self.max_reasoning_steps}:
|
337
|
+
state["completed"] = True
|
338
|
+
if not state["final_answer"]:
|
339
|
+
state["final_answer"] = "Reached maximum reasoning steps. Based on gathered information..."
|
340
|
+
|
341
|
+
return state
|
342
|
+
|
343
|
+
# Process current iteration
|
344
|
+
plan = plan.get("response") if isinstance(plan, dict) else plan
|
345
|
+
reasoning_response = reasoning_response.get("response") if isinstance(reasoning_response, dict) else reasoning_response
|
346
|
+
tool_result = tool_result if "tool_result" in locals() else None
|
347
|
+
|
348
|
+
# Initialize or update state
|
349
|
+
if "reasoning_state" not in locals() or not reasoning_state:
|
350
|
+
reasoning_state = None
|
351
|
+
|
352
|
+
reasoning_state = update_reasoning_state(
|
353
|
+
reasoning_state,
|
354
|
+
reasoning_response,
|
355
|
+
tool_result
|
356
|
+
)
|
357
|
+
|
358
|
+
# Prepare context for next iteration
|
359
|
+
context_for_agent = ""
|
360
|
+
for step in reasoning_state["steps"]:
|
361
|
+
if step["thought"]:
|
362
|
+
context_for_agent += f"\\nThought: {{step['thought']}}"
|
363
|
+
if step["action"]:
|
364
|
+
context_for_agent += f"\\nAction: {{step['action']}}"
|
365
|
+
if step["observation"]:
|
366
|
+
context_for_agent += f"\\nObservation: {{step['observation']}}"
|
367
|
+
|
368
|
+
result = {{
|
369
|
+
"reasoning_state": reasoning_state,
|
370
|
+
"context_for_agent": context_for_agent,
|
371
|
+
"continue_reasoning": not reasoning_state["completed"]
|
372
|
+
}}
|
373
|
+
"""
|
374
|
+
},
|
375
|
+
)
|
376
|
+
|
377
|
+
# Verification agent (if enabled)
|
378
|
+
if self.verification_enabled:
|
379
|
+
verifier_id = builder.add_node(
|
380
|
+
"LLMAgentNode",
|
381
|
+
node_id="verifier_agent",
|
382
|
+
config={
|
383
|
+
"system_prompt": """You are a fact-checking agent. Verify the accuracy of the answer.
|
384
|
+
|
385
|
+
Check for:
|
386
|
+
1. Factual accuracy
|
387
|
+
2. Logical consistency
|
388
|
+
3. Completeness
|
389
|
+
4. Source reliability
|
390
|
+
|
391
|
+
Return JSON:
|
392
|
+
{
|
393
|
+
"verified": true/false,
|
394
|
+
"confidence": 0.0-1.0,
|
395
|
+
"issues": ["list of any issues found"],
|
396
|
+
"suggestions": ["improvements if needed"]
|
397
|
+
}""",
|
398
|
+
"model": "gpt-4",
|
399
|
+
},
|
400
|
+
)
|
401
|
+
|
402
|
+
# Result synthesizer
|
403
|
+
synthesizer_id = builder.add_node(
|
404
|
+
"PythonCodeNode",
|
405
|
+
node_id="result_synthesizer",
|
406
|
+
config={
|
407
|
+
"code": """
|
408
|
+
# Synthesize final results
|
409
|
+
reasoning_state = reasoning_state
|
410
|
+
verification = verification if "verification" in locals() else None
|
411
|
+
query = query
|
412
|
+
|
413
|
+
# Extract tool usage
|
414
|
+
tools_used = []
|
415
|
+
for step in reasoning_state["steps"]:
|
416
|
+
if step["observation"] and "tool" in step["observation"]:
|
417
|
+
tools_used.append(step["observation"]["tool"])
|
418
|
+
|
419
|
+
# Calculate confidence
|
420
|
+
base_confidence = 0.7
|
421
|
+
confidence_boost = min(0.3, len(tools_used) * 0.1)
|
422
|
+
if verification and verification.get("response"):
|
423
|
+
verification_data = verification["response"]
|
424
|
+
if isinstance(verification_data, str):
|
425
|
+
import json
|
426
|
+
try:
|
427
|
+
verification_data = json.loads(verification_data)
|
428
|
+
except:
|
429
|
+
verification_data = {"confidence": 0.8}
|
430
|
+
|
431
|
+
verification_confidence = verification_data.get("confidence", 0.8)
|
432
|
+
final_confidence = (base_confidence + confidence_boost) * verification_confidence
|
433
|
+
else:
|
434
|
+
final_confidence = base_confidence + confidence_boost
|
435
|
+
|
436
|
+
# Build reasoning trace
|
437
|
+
reasoning_trace = []
|
438
|
+
for step in reasoning_state["steps"]:
|
439
|
+
trace_entry = {
|
440
|
+
"step": step["step_number"],
|
441
|
+
"thought": step["thought"],
|
442
|
+
"action": step["action"],
|
443
|
+
"observation": step["observation"]
|
444
|
+
}
|
445
|
+
reasoning_trace.append(trace_entry)
|
446
|
+
|
447
|
+
result = {
|
448
|
+
"agentic_rag_result": {
|
449
|
+
"query": query,
|
450
|
+
"answer": reasoning_state["final_answer"],
|
451
|
+
"reasoning_trace": reasoning_trace,
|
452
|
+
"tools_used": list(set(tools_used)),
|
453
|
+
"confidence": final_confidence,
|
454
|
+
"total_steps": len(reasoning_state["steps"]),
|
455
|
+
"verification": verification.get("response") if verification else None,
|
456
|
+
"metadata": {
|
457
|
+
"planning_strategy": "{self.planning_strategy}",
|
458
|
+
"max_steps": {self.max_reasoning_steps},
|
459
|
+
"completed_successfully": reasoning_state["completed"]
|
460
|
+
}
|
461
|
+
}
|
462
|
+
}
|
463
|
+
"""
|
464
|
+
},
|
465
|
+
)
|
466
|
+
|
467
|
+
# Connect workflow
|
468
|
+
# Planning phase
|
469
|
+
builder.add_connection(planner_id, "response", state_manager_id, "plan")
|
470
|
+
|
471
|
+
# ReAct loop connections
|
472
|
+
builder.add_connection(
|
473
|
+
state_manager_id, "context_for_agent", react_agent_id, "additional_context"
|
474
|
+
)
|
475
|
+
builder.add_connection(
|
476
|
+
react_agent_id, "response", state_manager_id, "reasoning_response"
|
477
|
+
)
|
478
|
+
builder.add_connection(
|
479
|
+
state_manager_id, "reasoning_state", tool_executor_id, "reasoning_state"
|
480
|
+
)
|
481
|
+
builder.add_connection(
|
482
|
+
tool_executor_id, "tool_result", state_manager_id, "tool_result"
|
483
|
+
)
|
484
|
+
|
485
|
+
# Loop control - continue if not completed
|
486
|
+
builder.add_connection(
|
487
|
+
state_manager_id, "continue_reasoning", react_agent_id, "_continue_if_true"
|
488
|
+
)
|
489
|
+
|
490
|
+
# Verification (if enabled)
|
491
|
+
if self.verification_enabled:
|
492
|
+
builder.add_connection(
|
493
|
+
state_manager_id, "reasoning_state", verifier_id, "answer_to_verify"
|
494
|
+
)
|
495
|
+
builder.add_connection(
|
496
|
+
verifier_id, "response", synthesizer_id, "verification"
|
497
|
+
)
|
498
|
+
|
499
|
+
# Final synthesis
|
500
|
+
builder.add_connection(
|
501
|
+
state_manager_id, "reasoning_state", synthesizer_id, "reasoning_state"
|
502
|
+
)
|
503
|
+
|
504
|
+
return builder.build(name="agentic_rag_workflow")
|
505
|
+
|
506
|
+
|
507
|
+
@register_node()
|
508
|
+
class ToolAugmentedRAGNode(Node):
|
509
|
+
"""
|
510
|
+
Tool-Augmented RAG Node
|
511
|
+
|
512
|
+
Enhances RAG with specific tool capabilities for specialized tasks.
|
513
|
+
|
514
|
+
When to use:
|
515
|
+
- Best for: Domain-specific queries requiring specialized tools
|
516
|
+
- Not ideal for: General knowledge questions
|
517
|
+
- Performance: 1-5 seconds depending on tools used
|
518
|
+
- Accuracy: High for tool-supported domains
|
519
|
+
|
520
|
+
Example:
|
521
|
+
tool_rag = ToolAugmentedRAGNode(
|
522
|
+
tool_registry={
|
523
|
+
"calculator": calculate_func,
|
524
|
+
"unit_converter": convert_units,
|
525
|
+
"date_calculator": date_math
|
526
|
+
}
|
527
|
+
)
|
528
|
+
|
529
|
+
Parameters:
|
530
|
+
tool_registry: Dict of tool_name -> callable
|
531
|
+
auto_detect_tools: Automatically detect needed tools
|
532
|
+
fallback_strategy: What to do if tools fail
|
533
|
+
|
534
|
+
Returns:
|
535
|
+
answer: Tool-augmented response
|
536
|
+
tools_invoked: List of tools used
|
537
|
+
tool_outputs: Results from each tool
|
538
|
+
"""
|
539
|
+
|
540
|
+
def __init__(
|
541
|
+
self,
|
542
|
+
name: str = "tool_augmented_rag",
|
543
|
+
tool_registry: Dict[str, Callable] = None,
|
544
|
+
auto_detect_tools: bool = True,
|
545
|
+
):
|
546
|
+
self.tool_registry = tool_registry or {}
|
547
|
+
self.auto_detect_tools = auto_detect_tools
|
548
|
+
super().__init__(name)
|
549
|
+
|
550
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
551
|
+
return {
|
552
|
+
"query": NodeParameter(
|
553
|
+
name="query",
|
554
|
+
type=str,
|
555
|
+
required=True,
|
556
|
+
description="Query requiring tool augmentation",
|
557
|
+
),
|
558
|
+
"documents": NodeParameter(
|
559
|
+
name="documents",
|
560
|
+
type=list,
|
561
|
+
required=False,
|
562
|
+
description="Reference documents",
|
563
|
+
),
|
564
|
+
"context": NodeParameter(
|
565
|
+
name="context",
|
566
|
+
type=dict,
|
567
|
+
required=False,
|
568
|
+
description="Additional context for tools",
|
569
|
+
),
|
570
|
+
}
|
571
|
+
|
572
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
573
|
+
"""Execute tool-augmented RAG"""
|
574
|
+
query = kwargs.get("query", "")
|
575
|
+
documents = kwargs.get("documents", [])
|
576
|
+
context = kwargs.get("context", {})
|
577
|
+
|
578
|
+
# Detect required tools
|
579
|
+
required_tools = self._detect_required_tools(query)
|
580
|
+
|
581
|
+
# Execute tools
|
582
|
+
tool_outputs = {}
|
583
|
+
for tool_name in required_tools:
|
584
|
+
if tool_name in self.tool_registry:
|
585
|
+
try:
|
586
|
+
tool_func = self.tool_registry[tool_name]
|
587
|
+
tool_outputs[tool_name] = tool_func(query, context)
|
588
|
+
except Exception as e:
|
589
|
+
logger.error(f"Tool {tool_name} failed: {e}")
|
590
|
+
tool_outputs[tool_name] = {"error": str(e)}
|
591
|
+
|
592
|
+
# Augment response with tool outputs
|
593
|
+
augmented_answer = self._synthesize_with_tools(query, documents, tool_outputs)
|
594
|
+
|
595
|
+
return {
|
596
|
+
"answer": augmented_answer,
|
597
|
+
"tools_invoked": list(required_tools),
|
598
|
+
"tool_outputs": tool_outputs,
|
599
|
+
"confidence": 0.9 if tool_outputs else 0.7,
|
600
|
+
}
|
601
|
+
|
602
|
+
def _detect_required_tools(self, query: str) -> List[str]:
|
603
|
+
"""Detect which tools are needed for the query"""
|
604
|
+
required = []
|
605
|
+
|
606
|
+
query_lower = query.lower()
|
607
|
+
|
608
|
+
# Simple keyword detection (would use NER/classification in production)
|
609
|
+
if any(
|
610
|
+
word in query_lower for word in ["calculate", "compute", "sum", "average"]
|
611
|
+
):
|
612
|
+
required.append("calculator")
|
613
|
+
|
614
|
+
if any(word in query_lower for word in ["convert", "unit", "measurement"]):
|
615
|
+
required.append("unit_converter")
|
616
|
+
|
617
|
+
if any(word in query_lower for word in ["date", "days", "weeks", "months"]):
|
618
|
+
required.append("date_calculator")
|
619
|
+
|
620
|
+
return required
|
621
|
+
|
622
|
+
def _synthesize_with_tools(
|
623
|
+
self, query: str, documents: List[Dict], tool_outputs: Dict[str, Any]
|
624
|
+
) -> str:
|
625
|
+
"""Synthesize answer using tool outputs"""
|
626
|
+
# In production, would use LLM for synthesis
|
627
|
+
answer_parts = [f"Based on analysis of {len(documents)} documents"]
|
628
|
+
|
629
|
+
if tool_outputs:
|
630
|
+
answer_parts.append("and computational tools:")
|
631
|
+
for tool, output in tool_outputs.items():
|
632
|
+
if "error" not in output:
|
633
|
+
answer_parts.append(f"\n- {tool}: {output}")
|
634
|
+
|
635
|
+
answer_parts.append(
|
636
|
+
f"\nThe answer to '{query}' has been computed with tool assistance."
|
637
|
+
)
|
638
|
+
|
639
|
+
return " ".join(answer_parts)
|
640
|
+
|
641
|
+
|
642
|
+
@register_node()
|
643
|
+
class ReasoningRAGNode(WorkflowNode):
|
644
|
+
"""
|
645
|
+
Multi-Step Reasoning RAG
|
646
|
+
|
647
|
+
Implements complex reasoning chains for analytical queries.
|
648
|
+
|
649
|
+
When to use:
|
650
|
+
- Best for: Complex analytical questions, multi-step problems
|
651
|
+
- Not ideal for: Simple factual queries
|
652
|
+
- Performance: 2-8 seconds depending on reasoning depth
|
653
|
+
- Quality: Superior for questions requiring logic and analysis
|
654
|
+
|
655
|
+
Example:
|
656
|
+
reasoning_rag = ReasoningRAGNode(
|
657
|
+
reasoning_depth=3,
|
658
|
+
strategy="chain_of_thought"
|
659
|
+
)
|
660
|
+
|
661
|
+
# Query: "If Company A grows 20% annually and Company B grows 15%,
|
662
|
+
# when will A's revenue exceed B's if B starts 50% larger?"
|
663
|
+
# Will break down into steps and reason through the math
|
664
|
+
|
665
|
+
Parameters:
|
666
|
+
reasoning_depth: Maximum reasoning steps
|
667
|
+
strategy: Reasoning strategy (chain_of_thought, tree_of_thought)
|
668
|
+
verify_logic: Whether to verify logical consistency
|
669
|
+
|
670
|
+
Returns:
|
671
|
+
answer: Reasoned answer with steps
|
672
|
+
reasoning_chain: Step-by-step logic
|
673
|
+
assumptions: Assumptions made
|
674
|
+
confidence: Confidence in reasoning
|
675
|
+
"""
|
676
|
+
|
677
|
+
def __init__(
|
678
|
+
self,
|
679
|
+
name: str = "reasoning_rag",
|
680
|
+
reasoning_depth: int = 3,
|
681
|
+
strategy: str = "chain_of_thought",
|
682
|
+
):
|
683
|
+
self.reasoning_depth = reasoning_depth
|
684
|
+
self.strategy = strategy
|
685
|
+
super().__init__(name, self._create_workflow())
|
686
|
+
|
687
|
+
def _create_workflow(self) -> WorkflowNode:
|
688
|
+
"""Create reasoning RAG workflow"""
|
689
|
+
builder = WorkflowBuilder()
|
690
|
+
|
691
|
+
# Problem decomposer
|
692
|
+
decomposer_id = builder.add_node(
|
693
|
+
"LLMAgentNode",
|
694
|
+
node_id="problem_decomposer",
|
695
|
+
config={
|
696
|
+
"system_prompt": f"""Break down complex problems into reasoning steps.
|
697
|
+
|
698
|
+
Strategy: {self.strategy}
|
699
|
+
Max depth: {self.reasoning_depth}
|
700
|
+
|
701
|
+
For each step specify:
|
702
|
+
1. What to determine
|
703
|
+
2. Required information
|
704
|
+
3. Logic/calculation needed
|
705
|
+
|
706
|
+
Return JSON:
|
707
|
+
{{
|
708
|
+
"steps": [
|
709
|
+
{{"step": 1, "goal": "...", "requires": ["..."], "approach": "..."}}
|
710
|
+
],
|
711
|
+
"assumptions": ["list assumptions"],
|
712
|
+
"complexity": "low|medium|high"
|
713
|
+
}}""",
|
714
|
+
"model": "gpt-4",
|
715
|
+
},
|
716
|
+
)
|
717
|
+
|
718
|
+
# Step-by-step reasoner
|
719
|
+
step_reasoner_id = builder.add_node(
|
720
|
+
"LLMAgentNode",
|
721
|
+
node_id="step_reasoner",
|
722
|
+
config={
|
723
|
+
"system_prompt": """Execute one reasoning step at a time.
|
724
|
+
|
725
|
+
Given:
|
726
|
+
- Current step goal
|
727
|
+
- Available information
|
728
|
+
- Previous steps' results
|
729
|
+
|
730
|
+
Provide:
|
731
|
+
- Logical reasoning
|
732
|
+
- Calculations if needed
|
733
|
+
- Conclusion for this step
|
734
|
+
- What's needed next
|
735
|
+
|
736
|
+
Be explicit about your logic.""",
|
737
|
+
"model": "gpt-4",
|
738
|
+
},
|
739
|
+
)
|
740
|
+
|
741
|
+
# Logic verifier
|
742
|
+
verifier_id = builder.add_node(
|
743
|
+
"LLMAgentNode",
|
744
|
+
node_id="logic_verifier",
|
745
|
+
config={
|
746
|
+
"system_prompt": """Verify the logical consistency of reasoning.
|
747
|
+
|
748
|
+
Check:
|
749
|
+
1. Are all steps logically sound?
|
750
|
+
2. Do conclusions follow from premises?
|
751
|
+
3. Are calculations correct?
|
752
|
+
4. Are assumptions reasonable?
|
753
|
+
|
754
|
+
Rate confidence: 0.0-1.0""",
|
755
|
+
"model": "gpt-4",
|
756
|
+
},
|
757
|
+
)
|
758
|
+
|
759
|
+
# Connect workflow
|
760
|
+
builder.add_connection(
|
761
|
+
decomposer_id, "response", step_reasoner_id, "reasoning_plan"
|
762
|
+
)
|
763
|
+
|
764
|
+
# Multiple reasoning steps (simplified - would use loop in production)
|
765
|
+
builder.add_connection(
|
766
|
+
step_reasoner_id, "response", verifier_id, "reasoning_to_verify"
|
767
|
+
)
|
768
|
+
|
769
|
+
return builder.build(name="reasoning_rag_workflow")
|
770
|
+
|
771
|
+
|
772
|
+
# Export all agentic nodes
|
773
|
+
__all__ = ["AgenticRAGNode", "ToolAugmentedRAGNode", "ReasoningRAGNode"]
|