@macaw-ai/client 0.0.0-stage → 0.9.10

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.
Files changed (142) hide show
  1. package/LICENSE.md +339 -0
  2. package/bin/install.js +278 -0
  3. package/package.json +37 -4
  4. package/payload/README.md +56 -0
  5. package/payload/secureAI/LICENSE +190 -0
  6. package/payload/secureAI/NOTICE +5 -0
  7. package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
  8. package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
  9. package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
  10. package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
  11. package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
  12. package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
  13. package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
  14. package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
  15. package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
  16. package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
  17. package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
  18. package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
  19. package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
  20. package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
  21. package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
  22. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
  23. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
  24. package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
  25. package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
  26. package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
  27. package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
  28. package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
  29. package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
  30. package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
  31. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
  32. package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
  33. package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
  34. package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
  35. package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
  36. package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
  37. package/payload/secureAI/examples/README.md +244 -0
  38. package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
  39. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
  40. package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
  41. package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
  42. package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
  43. package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
  44. package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
  45. package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
  46. package/payload/secureAI/examples/attestations/README.md +128 -0
  47. package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
  48. package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
  49. package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
  50. package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
  51. package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
  52. package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
  53. package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
  54. package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
  55. package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
  56. package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
  57. package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
  58. package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
  59. package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
  60. package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
  61. package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
  62. package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
  63. package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
  64. package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
  65. package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
  66. package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
  67. package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
  68. package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
  69. package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
  70. package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
  71. package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
  72. package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
  73. package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
  74. package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
  75. package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
  76. package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
  77. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
  78. package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
  79. package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
  80. package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
  81. package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
  82. package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
  83. package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
  84. package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
  85. package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
  86. package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
  87. package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
  88. package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
  89. package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
  90. package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
  91. package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
  92. package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
  93. package/payload/secureAI/macaw_adapters/__init__.py +55 -0
  94. package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
  95. package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
  96. package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
  97. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
  98. package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
  99. package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
  100. package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
  101. package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
  102. package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
  103. package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
  104. package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
  105. package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
  106. package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
  107. package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
  108. package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
  109. package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
  110. package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
  111. package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
  112. package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
  113. package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
  114. package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
  115. package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
  116. package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
  117. package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
  118. package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
  119. package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
  120. package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
  121. package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
  122. package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
  123. package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
  124. package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
  125. package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
  126. package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
  127. package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
  128. package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
  129. package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
  130. package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
  131. package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
  132. package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
  133. package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
  134. package/payload/secureAI/test_harness_quick.py +296 -0
  135. package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
  136. package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
  137. package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
  138. package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
  139. package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
  140. package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
  141. package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
  142. package/README.md +0 -3
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ langchain_1b_multiuser.py - Multi-user Agents with role-based policies
4
+
5
+ Create different agent instances per user role, each with their own security policy.
6
+ Use this when: Different users need different tool access levels.
7
+
8
+ In this example:
9
+ - alice (Analyst): Can only use calculator
10
+ - bob (Manager): Can use calculator and weather
11
+ - carol (Admin): Can use all tools including admin
12
+
13
+ Prerequisites:
14
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
15
+ - OPENAI_API_KEY environment variable (for ChatOpenAI)
16
+
17
+ Run:
18
+ export OPENAI_API_KEY=sk-...
19
+ python langchain_1b_multiuser.py
20
+ """
21
+
22
+ import os
23
+
24
+ from macaw_adapters.langchain import create_react_agent, AgentExecutor, cleanup
25
+
26
+ from langchain_openai import ChatOpenAI
27
+ from langchain.tools import Tool
28
+ from langchain.prompts import PromptTemplate
29
+
30
+
31
+ # Define tools
32
+ def calculator(expression: str) -> str:
33
+ """Evaluate a math expression."""
34
+ try:
35
+ result = eval(expression)
36
+ return f"Result: {result}"
37
+ except Exception as e:
38
+ return f"Error: {e}"
39
+
40
+
41
+ def get_weather(city: str) -> str:
42
+ """Get weather for a city (mock)."""
43
+ weather_data = {
44
+ "new york": "72°F, Sunny",
45
+ "london": "58°F, Cloudy",
46
+ "tokyo": "68°F, Clear"
47
+ }
48
+ return weather_data.get(city.lower(), f"Weather for {city}: 65°F, Partly cloudy")
49
+
50
+
51
+ def admin_tool(command: str) -> str:
52
+ """Execute admin command."""
53
+ return f"Executed admin command: {command}"
54
+
55
+
56
+ def file_reader(path: str) -> str:
57
+ """Read a file."""
58
+ # Mock - returns different content based on path
59
+ if "report" in path.lower():
60
+ return "Q4 Revenue: $1.2M, Growth: 15%"
61
+ elif "secret" in path.lower():
62
+ return "ACCESS DENIED - Classified content"
63
+ return f"Contents of {path}: [mock data]"
64
+
65
+
66
+ # All available tools
67
+ ALL_TOOLS = [
68
+ Tool(name="calculator", func=calculator, description="Math calculations"),
69
+ Tool(name="weather", func=get_weather, description="Get weather for a city"),
70
+ Tool(name="admin", func=admin_tool, description="Execute admin commands"),
71
+ Tool(name="file_reader", func=file_reader, description="Read files"),
72
+ ]
73
+
74
+
75
+ # User role configurations with MAPL format policies
76
+ USER_CONFIGS = {
77
+ "alice": {
78
+ "role": "Financial Analyst",
79
+ "security_policy": {
80
+ "resources": ["tool:calculator", "tool:file_reader"],
81
+ "denied_resources": ["tool:admin"],
82
+ "constraints": {
83
+ "parameters": {
84
+ "tool:file_reader": {"input": ["*report*", "*public*"]}
85
+ },
86
+ "denied_parameters": {
87
+ "tool:*": {"input": ["*password*", "*secret*"]}
88
+ }
89
+ }
90
+ },
91
+ "tests": [
92
+ ("What is 100 * 12?", "ALLOWED - calculator"),
93
+ ("Read the Q4 report", "ALLOWED - file_reader for reports"),
94
+ ("Run admin command: list users", "BLOCKED - admin tool"),
95
+ ("What's the weather in London?", "BLOCKED - weather not allowed"),
96
+ ]
97
+ },
98
+ "bob": {
99
+ "role": "Finance Manager",
100
+ "security_policy": {
101
+ "resources": ["tool:calculator", "tool:weather", "tool:file_reader"],
102
+ "denied_resources": ["tool:admin"],
103
+ "constraints": {
104
+ "parameters": {
105
+ "tool:file_reader": {"input": ["*report*", "*forecast*", "*public*"]}
106
+ },
107
+ "denied_parameters": {
108
+ "tool:*": {"input": ["*password*"]}
109
+ }
110
+ }
111
+ },
112
+ "tests": [
113
+ ("What is 250 + 750?", "ALLOWED - calculator"),
114
+ ("What's the weather in Tokyo?", "ALLOWED - weather"),
115
+ ("Read the Q4 report", "ALLOWED - file_reader"),
116
+ ("Run admin command: restart", "BLOCKED - admin tool"),
117
+ ]
118
+ },
119
+ "carol": {
120
+ "role": "IT Administrator",
121
+ "security_policy": {
122
+ "resources": ["tool:calculator", "tool:weather", "tool:file_reader", "tool:admin"],
123
+ # No denied_resources - full tool access
124
+ "constraints": {
125
+ "parameters": {
126
+ "tool:file_reader": {"input": ["*"]} # All files
127
+ }
128
+ }
129
+ },
130
+ "tests": [
131
+ ("What is 999 * 111?", "ALLOWED - calculator"),
132
+ ("What's the weather in New York?", "ALLOWED - weather"),
133
+ ("Run admin command: status check", "ALLOWED - admin access"),
134
+ ("Read the secret file", "ALLOWED - full file access"),
135
+ ]
136
+ }
137
+ }
138
+
139
+
140
+ # Simple ReAct prompt
141
+ REACT_PROMPT = PromptTemplate.from_template("""Answer the following questions as best you can. You have access to the following tools:
142
+
143
+ {tools}
144
+
145
+ Use the following format:
146
+
147
+ Question: the input question you must answer
148
+ Thought: you should always think about what to do
149
+ Action: the action to take, should be one of [{tool_names}]
150
+ Action Input: the input to the action
151
+ Observation: the result of the action
152
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
153
+ Thought: I now know the final answer
154
+ Final Answer: the final answer to the original input question
155
+
156
+ Begin!
157
+
158
+ Question: {input}
159
+ Thought:{agent_scratchpad}""")
160
+
161
+
162
+ def create_user_executor(username: str, config: dict, llm) -> AgentExecutor:
163
+ """Create a security-scoped executor for a specific user."""
164
+ print(f"\n Creating executor for {username} ({config['role']})")
165
+ print(f" Policy: {config['security_policy'].get('resources', 'all')}")
166
+
167
+ agent = create_react_agent(
168
+ llm=llm,
169
+ tools=ALL_TOOLS,
170
+ prompt=REACT_PROMPT,
171
+ security_policy=config["security_policy"]
172
+ )
173
+
174
+ executor = AgentExecutor(
175
+ agent=agent,
176
+ tools=ALL_TOOLS,
177
+ security_policy=config["security_policy"],
178
+ verbose=False,
179
+ handle_parsing_errors=True,
180
+ max_iterations=3
181
+ )
182
+
183
+ return executor
184
+
185
+
186
+ def test_user(username: str, config: dict, executor: AgentExecutor):
187
+ """Test a user's executor with their test cases."""
188
+ print(f"\n{'=' * 60}")
189
+ print(f"Testing {username.upper()} ({config['role']})")
190
+ print(f"Allowed tools: {config['security_policy'].get('resources', 'all')}")
191
+ print("=" * 60)
192
+
193
+ for query, expected in config["tests"]:
194
+ print(f"\n Query: {query}")
195
+ print(f" Expected: {expected}")
196
+
197
+ try:
198
+ result = executor.invoke({"input": query})
199
+ output = result.get("output", str(result))
200
+
201
+ # Check if it was blocked
202
+ if "denied" in output.lower() or "access denied" in output.lower():
203
+ print(f" Result: BLOCKED - {output[:50]}...")
204
+ elif "cannot" in output.lower() or "don't have" in output.lower():
205
+ print(f" Result: BLOCKED (agent refused) - {output[:50]}...")
206
+ else:
207
+ print(f" Result: SUCCESS - {output[:60]}...")
208
+
209
+ except Exception as e:
210
+ error_msg = str(e).lower()
211
+ if any(word in error_msg for word in ["denied", "blocked", "policy", "not allowed"]):
212
+ print(f" Result: CORRECTLY BLOCKED - {str(e)[:50]}...")
213
+ else:
214
+ print(f" Result: ERROR - {str(e)[:50]}...")
215
+
216
+
217
+ def main():
218
+ if not os.environ.get("OPENAI_API_KEY"):
219
+ print("Set OPENAI_API_KEY environment variable")
220
+ return
221
+
222
+ print("=" * 60)
223
+ print("Example 1b: Multi-user Agents (LangChain)")
224
+ print("=" * 60)
225
+ print("\nPattern: Separate executor per user with role-based policies")
226
+ print("Use when: Different users need different tool access levels")
227
+ print("\nUsers:")
228
+ print(" alice (Analyst): calculator, file_reader (reports only)")
229
+ print(" bob (Manager): calculator, weather, file_reader")
230
+ print(" carol (Admin): all tools, all files")
231
+
232
+ # Shared LLM
233
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
234
+
235
+ # Create and test each user
236
+ for username, config in USER_CONFIGS.items():
237
+ executor = create_user_executor(username, config, llm)
238
+ test_user(username, config, executor)
239
+
240
+ # Cleanup
241
+ print("\n--- Cleanup ---")
242
+ cleanup()
243
+
244
+ print("\n" + "=" * 60)
245
+ print("Example complete!")
246
+ print("=" * 60)
247
+
248
+
249
+ if __name__ == "__main__":
250
+ try:
251
+ main()
252
+ except Exception as e:
253
+ err = str(e)
254
+ print("\n" + "=" * 60)
255
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
256
+ print("ERROR: OpenAI API key not configured")
257
+ print("Fix: export OPENAI_API_KEY=sk-...")
258
+ else:
259
+ print(f"ERROR: {e}")
260
+ print("=" * 60)
261
+ import sys
262
+ sys.exit(1)
@@ -0,0 +1,332 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ langchain_1c_orchestration.py - Agent Orchestration with supervisor routing
4
+
5
+ A supervisor agent routes tasks to specialized sub-agents, each with different
6
+ security policies. Demonstrates policy composition where chains can only
7
+ restrict, never relax permissions.
8
+
9
+ Use this when: Building multi-agent workflows with different permission levels.
10
+
11
+ In this example:
12
+ - Supervisor: Routes requests to appropriate agent
13
+ - Research Agent: Can use search, file_reader (public only)
14
+ - Finance Agent: Can use calculator, file_reader (reports only)
15
+ - Admin Agent: Can use admin tools (restricted access)
16
+
17
+ Prerequisites:
18
+ - MACAW SDK installed (pip install macaw-client macaw-adapters)
19
+ - OPENAI_API_KEY environment variable (for ChatOpenAI)
20
+
21
+ Run:
22
+ export OPENAI_API_KEY=sk-...
23
+ python langchain_1c_orchestration.py
24
+ """
25
+
26
+ import os
27
+ from typing import Dict, Any
28
+
29
+ from macaw_adapters.langchain import create_react_agent, AgentExecutor, cleanup
30
+
31
+ from langchain_openai import ChatOpenAI
32
+ from langchain.tools import Tool
33
+ from langchain.prompts import PromptTemplate
34
+
35
+
36
+ # Define specialized tools for different agents
37
+ def calculator(expression: str) -> str:
38
+ """Evaluate a math expression."""
39
+ try:
40
+ result = eval(expression)
41
+ return f"Result: {result}"
42
+ except Exception as e:
43
+ return f"Error: {e}"
44
+
45
+
46
+ def search_web(query: str) -> str:
47
+ """Search the web (mock)."""
48
+ return f"Search results for '{query}': [Top 3 relevant articles about {query}]"
49
+
50
+
51
+ def file_reader(path: str) -> str:
52
+ """Read a file."""
53
+ if "report" in path.lower():
54
+ return "Q4 Revenue: $1.2M, Growth: 15%, Expenses: $800K"
55
+ elif "public" in path.lower():
56
+ return "Public announcement: Company expanding to new markets"
57
+ elif "secret" in path.lower() or "confidential" in path.lower():
58
+ return "ACCESS DENIED - Classified content"
59
+ return f"Contents of {path}: [document data]"
60
+
61
+
62
+ def admin_tool(command: str) -> str:
63
+ """Execute admin command."""
64
+ return f"Admin executed: {command}"
65
+
66
+
67
+ def send_email(input_str: str) -> str:
68
+ """Send an email (mock). Input format: 'to@email.com: subject text'"""
69
+ # Parse simple format: "to: subject" or just treat whole thing as recipient
70
+ if ":" in input_str:
71
+ parts = input_str.split(":", 1)
72
+ to = parts[0].strip()
73
+ subject = parts[1].strip() if len(parts) > 1 else "No subject"
74
+ else:
75
+ to = input_str.strip()
76
+ subject = "Notification"
77
+ return f"Email sent to {to}: {subject}"
78
+
79
+
80
+ # All available tools
81
+ ALL_TOOLS = [
82
+ Tool(name="calculator", func=calculator, description="Math calculations"),
83
+ Tool(name="search", func=search_web, description="Search the web for information"),
84
+ Tool(name="file_reader", func=file_reader, description="Read files"),
85
+ Tool(name="admin", func=admin_tool, description="Execute admin commands"),
86
+ Tool(name="email", func=send_email, description="Send emails"),
87
+ ]
88
+
89
+
90
+ # ReAct prompt template
91
+ REACT_PROMPT = PromptTemplate.from_template("""Answer the following questions as best you can. You have access to the following tools:
92
+
93
+ {tools}
94
+
95
+ Use the following format:
96
+
97
+ Question: the input question you must answer
98
+ Thought: you should always think about what to do
99
+ Action: the action to take, should be one of [{tool_names}]
100
+ Action Input: the input to the action
101
+ Observation: the result of the action
102
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
103
+ Thought: I now know the final answer
104
+ Final Answer: the final answer to the original input question
105
+
106
+ Begin!
107
+
108
+ Question: {input}
109
+ Thought:{agent_scratchpad}""")
110
+
111
+
112
+ # Specialized agent configurations with MAPL format policies
113
+ AGENT_CONFIGS = {
114
+ "research": {
115
+ "description": "Research Agent - web search and public documents",
116
+ "policy": {
117
+ "resources": ["tool:search", "tool:file_reader"],
118
+ "denied_resources": ["tool:admin", "tool:calculator", "tool:email"],
119
+ "constraints": {
120
+ "parameters": {
121
+ "tool:file_reader": {"input": ["*public*"]}
122
+ },
123
+ "denied_parameters": {
124
+ "tool:*": {"input": ["*password*", "*secret*", "*confidential*"]}
125
+ }
126
+ }
127
+ }
128
+ },
129
+ "finance": {
130
+ "description": "Finance Agent - calculations and financial reports",
131
+ "policy": {
132
+ "resources": ["tool:calculator", "tool:file_reader"],
133
+ "denied_resources": ["tool:admin", "tool:search", "tool:email"],
134
+ "constraints": {
135
+ "parameters": {
136
+ "tool:file_reader": {"input": ["*report*", "*forecast*", "*budget*"]}
137
+ },
138
+ "denied_parameters": {
139
+ "tool:*": {"input": ["*password*"]}
140
+ }
141
+ }
142
+ }
143
+ },
144
+ "admin": {
145
+ "description": "Admin Agent - system administration (restricted)",
146
+ "policy": {
147
+ "resources": ["tool:admin", "tool:email"],
148
+ "denied_resources": ["tool:calculator", "tool:search", "tool:file_reader"],
149
+ "constraints": {
150
+ "denied_parameters": {
151
+ "tool:*": {"input": ["*delete*", "*drop*", "*truncate*"]}
152
+ }
153
+ }
154
+ }
155
+ }
156
+ }
157
+
158
+
159
+ def create_specialized_agent(name: str, config: Dict[str, Any], llm) -> AgentExecutor:
160
+ """Create a specialized agent with its security policy."""
161
+ agent = create_react_agent(
162
+ llm=llm,
163
+ tools=ALL_TOOLS,
164
+ prompt=REACT_PROMPT,
165
+ security_policy=config["policy"]
166
+ )
167
+
168
+ return AgentExecutor(
169
+ agent=agent,
170
+ tools=ALL_TOOLS,
171
+ security_policy=config["policy"],
172
+ verbose=False,
173
+ handle_parsing_errors=True,
174
+ max_iterations=3
175
+ )
176
+
177
+
178
+ class SupervisorAgent:
179
+ """
180
+ Supervisor that routes tasks to appropriate specialized agents.
181
+
182
+ Key security concept: The supervisor can only delegate to agents
183
+ with equal or more restrictive policies. It cannot grant permissions
184
+ it doesn't have (policy intersection).
185
+ """
186
+
187
+ def __init__(self, llm):
188
+ self.llm = llm
189
+ self.agents: Dict[str, AgentExecutor] = {}
190
+
191
+ # Create specialized agents
192
+ print("\n Creating specialized agents:")
193
+ for name, config in AGENT_CONFIGS.items():
194
+ print(f" - {name}: {config['description']}")
195
+ self.agents[name] = create_specialized_agent(name, config, llm)
196
+
197
+ def route(self, query: str) -> str:
198
+ """Route query to appropriate agent based on content."""
199
+ query_lower = query.lower()
200
+
201
+ # Simple routing logic (in production, could use LLM for routing)
202
+ if any(word in query_lower for word in ["calculate", "math", "revenue", "profit", "cost", "budget"]):
203
+ return "finance"
204
+ elif any(word in query_lower for word in ["search", "find", "lookup", "research", "public"]):
205
+ return "research"
206
+ elif any(word in query_lower for word in ["admin", "restart", "status", "notify", "email"]):
207
+ return "admin"
208
+ else:
209
+ return "research" # Default to research
210
+
211
+ def invoke(self, query: str) -> Dict[str, Any]:
212
+ """Route and execute query through appropriate agent."""
213
+ agent_name = self.route(query)
214
+ agent = self.agents[agent_name]
215
+
216
+ print(f" Routing to: {agent_name}")
217
+ print(f" Policy: {AGENT_CONFIGS[agent_name]['policy'].get('resources', 'all')}")
218
+
219
+ try:
220
+ result = agent.invoke({"input": query})
221
+ return {
222
+ "routed_to": agent_name,
223
+ "success": True,
224
+ "output": result.get("output", str(result))
225
+ }
226
+ except Exception as e:
227
+ error_msg = str(e).lower()
228
+ if any(word in error_msg for word in ["denied", "blocked", "policy", "not allowed"]):
229
+ return {
230
+ "routed_to": agent_name,
231
+ "success": False,
232
+ "blocked": True,
233
+ "output": f"Policy blocked: {str(e)[:80]}"
234
+ }
235
+ return {
236
+ "routed_to": agent_name,
237
+ "success": False,
238
+ "output": f"Error: {str(e)[:80]}"
239
+ }
240
+
241
+
242
+ def main():
243
+ if not os.environ.get("OPENAI_API_KEY"):
244
+ print("Set OPENAI_API_KEY environment variable")
245
+ return
246
+
247
+ print("=" * 60)
248
+ print("Example 1c: Agent Orchestration (LangChain)")
249
+ print("=" * 60)
250
+ print("\nPattern: Supervisor routes to specialized sub-agents")
251
+ print("Use when: Multi-agent workflows with different permission levels")
252
+ print("\nArchitecture:")
253
+ print(" Supervisor --> Research Agent (search, public files)")
254
+ print(" --> Finance Agent (calculator, reports)")
255
+ print(" --> Admin Agent (admin tools, email)")
256
+
257
+ # Shared LLM
258
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
259
+
260
+ # Create supervisor
261
+ print("\n--- Creating Supervisor Agent ---")
262
+ supervisor = SupervisorAgent(llm)
263
+
264
+ # Test cases demonstrating routing and policy enforcement
265
+ TEST_CASES = [
266
+ # Should route to finance, use calculator
267
+ ("Calculate the profit if revenue is 1200000 and expenses are 800000", "finance", True),
268
+
269
+ # Should route to research, use search
270
+ ("Search for public information about AI trends", "research", True),
271
+
272
+ # Should route to admin, use email
273
+ ("Send an email to team@company.com about the system status", "admin", True),
274
+
275
+ # Should route to finance, but try to access secret file (blocked - not in allowed patterns)
276
+ ("What is the profit in the secret internal file?", "finance", False),
277
+
278
+ # Should route to research, but research agent can't use calculator
279
+ ("Search for how to compute 100 * 50", "research", True), # Research will search, not calculate
280
+ ]
281
+
282
+ print("\n" + "=" * 60)
283
+ print("Running test cases")
284
+ print("=" * 60)
285
+
286
+ for query, expected_agent, should_succeed in TEST_CASES:
287
+ print(f"\n Query: {query}")
288
+ print(f" Expected: routes to '{expected_agent}', {'succeeds' if should_succeed else 'blocked'}")
289
+
290
+ result = supervisor.invoke(query)
291
+
292
+ routed = result["routed_to"]
293
+ succeeded = result["success"]
294
+ output = result["output"][:60] + "..." if len(result.get("output", "")) > 60 else result.get("output", "")
295
+
296
+ # Check if routing was correct
297
+ route_correct = routed == expected_agent
298
+ outcome_correct = succeeded == should_succeed
299
+
300
+ status = "PASS" if (route_correct and outcome_correct) else "UNEXPECTED"
301
+
302
+ print(f" Result: [{status}] Routed to '{routed}', {'succeeded' if succeeded else 'blocked/failed'}")
303
+ print(f" Output: {output}")
304
+
305
+ # Cleanup
306
+ print("\n--- Cleanup ---")
307
+ cleanup()
308
+
309
+ print("\n" + "=" * 60)
310
+ print("Example complete!")
311
+ print("=" * 60)
312
+ print("\nKey concepts demonstrated:")
313
+ print(" - Supervisor pattern for agent orchestration")
314
+ print(" - Each sub-agent has isolated security policy")
315
+ print(" - Policy enforcement at tool execution time")
316
+ print(" - Routing based on task type")
317
+
318
+
319
+ if __name__ == "__main__":
320
+ try:
321
+ main()
322
+ except Exception as e:
323
+ err = str(e)
324
+ print("\n" + "=" * 60)
325
+ if "OPENAI_API_KEY" in err or "api_key" in err.lower():
326
+ print("ERROR: OpenAI API key not configured")
327
+ print("Fix: export OPENAI_API_KEY=sk-...")
328
+ else:
329
+ print(f"ERROR: {e}")
330
+ print("=" * 60)
331
+ import sys
332
+ sys.exit(1)