@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,234 @@
1
+ # SecureLangChain Reference
2
+
3
+ Drop-in replacements for LangChain functions with optional MACAW security. Security is transparent - no code changes required beyond adding a `security_policy` parameter.
4
+
5
+ ## Installation
6
+
7
+ ```python
8
+ from macaw_adapters.langchain.agents import (
9
+ create_react_agent,
10
+ create_openai_functions_agent,
11
+ AgentExecutor,
12
+ cleanup
13
+ )
14
+ ```
15
+
16
+ Install the package:
17
+ ```bash
18
+ pip install -e /path/to/secureAI
19
+ ```
20
+
21
+ ## Functions
22
+
23
+ ### create_react_agent
24
+
25
+ Drop-in replacement for LangChain's `create_react_agent`.
26
+
27
+ ```python
28
+ agent = create_react_agent(
29
+ llm, # Language model
30
+ tools, # List of tools
31
+ prompt, # Prompt template
32
+ security_policy={...} # Optional: security policy
33
+ )
34
+ ```
35
+
36
+ | Parameter | Type | Required | Description |
37
+ |-----------|------|----------|-------------|
38
+ | `llm` | BaseLLM | Yes | Language model to use |
39
+ | `tools` | List[BaseTool] | Yes | Available tools |
40
+ | `prompt` | PromptTemplate | Yes | Agent prompt template |
41
+ | `security_policy` | dict | No | Simple security policy format |
42
+ | `**kwargs` | dict | No | Additional args for original function |
43
+
44
+ ### create_openai_functions_agent
45
+
46
+ Drop-in replacement for LangChain's `create_openai_functions_agent`.
47
+
48
+ ```python
49
+ agent = create_openai_functions_agent(
50
+ llm, # OpenAI-compatible model
51
+ tools, # List of tools
52
+ prompt, # Prompt template
53
+ security_policy={...} # Optional: security policy
54
+ )
55
+ ```
56
+
57
+ ### AgentExecutor
58
+
59
+ Drop-in replacement for LangChain's `AgentExecutor`.
60
+
61
+ ```python
62
+ executor = AgentExecutor(
63
+ agent, # Agent instance
64
+ tools, # List of tools
65
+ security_policy={...} # Optional: security policy
66
+ )
67
+
68
+ result = executor.invoke({"input": "What's the weather?"})
69
+ ```
70
+
71
+ | Parameter | Type | Required | Description |
72
+ |-----------|------|----------|-------------|
73
+ | `agent` | Runnable | Yes | The agent to use |
74
+ | `tools` | List[BaseTool] | Yes | Available tools |
75
+ | `security_policy` | dict | No | Simple security policy format |
76
+ | `**kwargs` | dict | No | Additional args for original class |
77
+
78
+ ### cleanup
79
+
80
+ Clean up all active MACAW clients.
81
+
82
+ ```python
83
+ cleanup()
84
+ ```
85
+
86
+ ## Security Policy Format (Simple)
87
+
88
+ The adapter accepts a simplified policy format and converts it to MAPL:
89
+
90
+ ```python
91
+ security_policy = {
92
+ # Allowed tools (whitelist)
93
+ "allowed_tools": ["calculator", "search", "weather"],
94
+
95
+ # Blocked tools (blacklist)
96
+ "blocked_tools": ["dangerous_tool", "admin_tool"],
97
+
98
+ # Allowed file paths (patterns)
99
+ "allowed_files": ["*reports*", "*public*", "*temp*"],
100
+
101
+ # Blocked query patterns
102
+ "blocked_queries": ["*password*", "*credentials*", "*secret*"]
103
+ }
104
+ ```
105
+
106
+ ### Policy Conversion
107
+
108
+ The simple format is automatically converted to MAPL:
109
+
110
+ | Simple Format | MAPL Format |
111
+ |---------------|-------------|
112
+ | `allowed_tools: ["calc"]` | `resources: ["tool:calc"]` |
113
+ | `blocked_tools: ["admin"]` | `denied_resources: ["tool:admin"]` |
114
+ | `allowed_files: ["*public*"]` | `constraints.parameters.tool:file_reader.path` |
115
+ | `blocked_queries: ["*pass*"]` | `constraints.denied_parameters.tool:*.query` |
116
+
117
+ ## Examples
118
+
119
+ ### Basic Usage (No Security)
120
+
121
+ ```python
122
+ from langchain.chat_models import ChatOpenAI
123
+ from langchain.tools import Tool
124
+ from langchain.agents import create_react_agent
125
+ from langchain import hub
126
+
127
+ # Same as original LangChain - no security
128
+ llm = ChatOpenAI()
129
+ tools = [Tool(name="calculator", func=lambda x: eval(x), description="Calculator")]
130
+ prompt = hub.pull("hwchase17/react")
131
+
132
+ agent = create_react_agent(llm, tools, prompt)
133
+ ```
134
+
135
+ ### With Security Policy
136
+
137
+ ```python
138
+ from macaw_adapters.langchain.agents import create_react_agent, AgentExecutor
139
+
140
+ llm = ChatOpenAI()
141
+ tools = [
142
+ Tool(name="calculator", func=lambda x: eval(x), description="Calculator"),
143
+ Tool(name="search", func=lambda q: f"Results for {q}", description="Search")
144
+ ]
145
+ prompt = hub.pull("hwchase17/react")
146
+
147
+ # Add security - just one parameter
148
+ agent = create_react_agent(
149
+ llm, tools, prompt,
150
+ security_policy={
151
+ "allowed_tools": ["calculator", "search"],
152
+ "blocked_queries": ["*password*", "*admin*"]
153
+ }
154
+ )
155
+
156
+ executor = AgentExecutor(
157
+ agent, tools,
158
+ security_policy={
159
+ "allowed_tools": ["calculator", "search"],
160
+ "blocked_queries": ["*password*", "*admin*"]
161
+ }
162
+ )
163
+
164
+ # Tool calls now routed through MACAW PEP
165
+ result = executor.invoke({"input": "Calculate 2+2"})
166
+ ```
167
+
168
+ ### Authenticated Prompts
169
+
170
+ LLM prompts are automatically authenticated when security is enabled:
171
+
172
+ ```python
173
+ # When security_policy is provided:
174
+ # 1. LLM is invisibly wrapped with authenticated prompt creation
175
+ # 2. All prompts get cryptographic signatures
176
+ # 3. Audit trail captures prompt lineage
177
+ ```
178
+
179
+ ## Security Features
180
+
181
+ ### Tool Wrapping
182
+
183
+ When `security_policy` is provided:
184
+
185
+ 1. **SecureToolWrapper**: Each tool is wrapped with MACAW routing
186
+ 2. **PEP Enforcement**: Tool calls go through Policy Enforcement Point
187
+ 3. **Audit Logging**: All tool invocations are logged
188
+ 4. **Policy Violations**: Blocked calls return "Access denied by security policy"
189
+
190
+ ### LLM Wrapping
191
+
192
+ When `security_policy` is provided:
193
+
194
+ 1. **_AuthenticatedLLMWrapper**: LLM invisibly wrapped
195
+ 2. **Prompt Authentication**: All prompts get authenticated
196
+ 3. **Security Context**: Policy and agent ID bound to prompts
197
+ 4. **Transparent**: LLM interface unchanged
198
+
199
+ ## Migration Guide
200
+
201
+ ```python
202
+ # Before (original LangChain)
203
+ from langchain.agents import create_react_agent, AgentExecutor
204
+
205
+ agent = create_react_agent(llm, tools, prompt)
206
+ executor = AgentExecutor(agent=agent, tools=tools)
207
+
208
+ # After (with security)
209
+ from macaw_adapters.langchain.agents import create_react_agent, AgentExecutor
210
+
211
+ agent = create_react_agent(
212
+ llm, tools, prompt,
213
+ security_policy={"blocked_queries": ["*password*"]}
214
+ )
215
+ executor = AgentExecutor(
216
+ agent=agent, tools=tools,
217
+ security_policy={"blocked_queries": ["*password*"]}
218
+ )
219
+
220
+ # Same API - security is optional and transparent
221
+ ```
222
+
223
+ ## Cleanup
224
+
225
+ Always clean up MACAW clients when done:
226
+
227
+ ```python
228
+ from macaw_adapters.langchain.agents import cleanup
229
+
230
+ # Manual cleanup
231
+ cleanup()
232
+
233
+ # Auto-cleanup on module exit is registered via atexit
234
+ ```
@@ -0,0 +1,100 @@
1
+ """
2
+ MACAW LangChain Adapter - Drop-in replacements for LangChain with MACAW protection.
3
+
4
+ Usage:
5
+ # LLM providers (mirrors langchain_openai / langchain_anthropic)
6
+ from macaw_adapters.langchain import ChatOpenAI, ChatAnthropic
7
+
8
+ # Or use explicit "Secure" names
9
+ from macaw_adapters.langchain import SecureChatOpenAI, SecureChatAnthropic
10
+
11
+ # Memory classes (mirrors langchain.memory)
12
+ from macaw_adapters.langchain.memory import ConversationBufferMemory
13
+
14
+ # Agent functions (mirrors langchain.agents)
15
+ from macaw_adapters.langchain.agents import create_react_agent, AgentExecutor
16
+
17
+ # Tool wrappers
18
+ from macaw_adapters.langchain.tools import SecureToolWrapper, wrap_tools
19
+
20
+ Features:
21
+ - Drop-in replacement for LangChain components
22
+ - Policy-enforced LLM calls via SecureOpenAI/SecureAnthropic internally
23
+ - Policy-enforced tool execution via invoke_tool
24
+ - Per-user identity propagation via bind_to_user
25
+ - Cryptographic audit trail
26
+ """
27
+
28
+ # Submodules (enable namespace imports)
29
+ from . import openai
30
+ from . import anthropic
31
+ from . import memory
32
+ from . import agents
33
+ from . import tools
34
+ from . import callbacks
35
+
36
+ # Convenience re-exports
37
+ from .openai import ChatOpenAI
38
+ from .anthropic import ChatAnthropic
39
+
40
+ # Explicit "Secure" aliases (same classes, clearer naming)
41
+ SecureChatOpenAI = ChatOpenAI
42
+ SecureChatAnthropic = ChatAnthropic
43
+ from .memory import (
44
+ ConversationBufferMemory,
45
+ ConversationBufferWindowMemory,
46
+ ConversationSummaryMemory
47
+ )
48
+ from .agents import (
49
+ create_react_agent,
50
+ create_openai_functions_agent,
51
+ AgentExecutor
52
+ )
53
+ from .tools import SecureToolWrapper, wrap_tools, secure_tool
54
+ from .callbacks import MACAWCallbackHandler
55
+ from ._utils import cleanup_all
56
+
57
+
58
+ def cleanup():
59
+ """Clean up all MACAW resources."""
60
+ cleanup_all()
61
+
62
+
63
+ __all__ = [
64
+ # Submodules
65
+ 'openai',
66
+ 'anthropic',
67
+ 'memory',
68
+ 'agents',
69
+ 'tools',
70
+ 'callbacks',
71
+
72
+ # LLM classes (drop-in names)
73
+ 'ChatOpenAI',
74
+ 'ChatAnthropic',
75
+
76
+ # LLM classes (explicit Secure names - same classes)
77
+ 'SecureChatOpenAI',
78
+ 'SecureChatAnthropic',
79
+
80
+ # Memory classes
81
+ 'ConversationBufferMemory',
82
+ 'ConversationBufferWindowMemory',
83
+ 'ConversationSummaryMemory',
84
+
85
+ # Agent functions
86
+ 'create_react_agent',
87
+ 'create_openai_functions_agent',
88
+ 'AgentExecutor',
89
+
90
+ # Tool wrappers
91
+ 'SecureToolWrapper',
92
+ 'wrap_tools',
93
+ 'secure_tool',
94
+
95
+ # Callback handler
96
+ 'MACAWCallbackHandler',
97
+
98
+ # Cleanup
99
+ 'cleanup'
100
+ ]
@@ -0,0 +1,83 @@
1
+ """
2
+ Shared utilities for SecureLangChain adapter.
3
+ """
4
+
5
+ import logging
6
+ from typing import Any, Callable, Dict, List, Optional
7
+
8
+ from macaw_client import MACAWClient
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ # Global registry of active MACAW clients (for cleanup)
13
+ _active_clients: Dict[str, MACAWClient] = {}
14
+
15
+
16
+ def get_or_create_client(
17
+ client_id: str,
18
+ intent_policy: Optional[Dict[str, Any]] = None,
19
+ tools: Optional[Dict[str, Any]] = None
20
+ ) -> Optional[MACAWClient]:
21
+ """
22
+ Get existing MACAW client or create a new one.
23
+
24
+ Args:
25
+ client_id: Unique identifier for the client
26
+ intent_policy: Optional policy for the client
27
+ tools: Optional unified tools config {name: {handler, prompts, ...}}
28
+
29
+ Returns:
30
+ MACAWClient instance or None if registration fails
31
+ """
32
+ if client_id not in _active_clients:
33
+ client = MACAWClient(
34
+ app_name=client_id,
35
+ app_version="1.0.0",
36
+ agent_type="agent",
37
+ intent_policy=intent_policy,
38
+ tools=tools
39
+ )
40
+ if client.register():
41
+ _active_clients[client_id] = client
42
+ logger.debug(f"Created MACAWClient: {client_id}")
43
+ else:
44
+ logger.warning(f"Failed to register MACAWClient: {client_id}")
45
+ return None
46
+ return _active_clients.get(client_id)
47
+
48
+
49
+ def register_client(client_id: str, client: MACAWClient) -> None:
50
+ """Register an existing client for cleanup tracking."""
51
+ _active_clients[client_id] = client
52
+
53
+
54
+ def _is_shutdown_error(e: Exception) -> bool:
55
+ """Check if error is due to Python interpreter shutdown."""
56
+ msg = str(e).lower()
57
+ return "interpreter shutdown" in msg or "cannot schedule" in msg
58
+
59
+
60
+ def cleanup_all() -> None:
61
+ """Clean up all active MACAW clients."""
62
+ for client_id, client in list(_active_clients.items()):
63
+ try:
64
+ client.unregister()
65
+ logger.debug(f"Cleaned up MACAWClient: {client_id}")
66
+ except Exception as e:
67
+ # Ignore errors during Python shutdown - process is ending anyway
68
+ if not _is_shutdown_error(e):
69
+ logger.error(f"Error cleaning up {client_id}: {e}")
70
+ _active_clients.clear()
71
+
72
+
73
+ def cleanup_client(client_id: str) -> None:
74
+ """Clean up a specific MACAW client."""
75
+ if client_id in _active_clients:
76
+ try:
77
+ _active_clients[client_id].unregister()
78
+ del _active_clients[client_id]
79
+ logger.debug(f"Cleaned up MACAWClient: {client_id}")
80
+ except Exception as e:
81
+ # Ignore errors during Python shutdown - process is ending anyway
82
+ if not _is_shutdown_error(e):
83
+ logger.error(f"Error cleaning up {client_id}: {e}")
@@ -0,0 +1,296 @@
1
+ """
2
+ MACAW Agent Functions - Drop-in replacements for LangChain agent creation.
3
+
4
+ Usage:
5
+ # Instead of: from langchain.agents import create_react_agent, AgentExecutor
6
+ from macaw_adapters.langchain.agents import create_react_agent, AgentExecutor
7
+
8
+ agent = create_react_agent(llm, tools, prompt, security_policy={...})
9
+ executor = AgentExecutor(agent=agent, tools=tools, security_policy={...})
10
+ """
11
+
12
+ import logging
13
+ from typing import Any, Callable, Dict, List, Optional
14
+
15
+ from macaw_client import MACAWClient
16
+ from ._utils import get_or_create_client, register_client, cleanup_all
17
+ from .tools import SecureToolWrapper, wrap_tools
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ class _AuthenticatedLLMWrapper:
23
+ """
24
+ Transparent wrapper that routes LLM prompts through MACAW authentication.
25
+
26
+ Invisible to developers - presents same interface as original LLM.
27
+ """
28
+
29
+ def __init__(self, original_llm: Any, macaw_client: MACAWClient):
30
+ self._llm = original_llm
31
+ self._macaw = macaw_client
32
+
33
+ # Copy non-callable attributes
34
+ for attr in dir(original_llm):
35
+ if not attr.startswith('_') and not callable(getattr(original_llm, attr)):
36
+ try:
37
+ setattr(self, attr, getattr(original_llm, attr))
38
+ except AttributeError:
39
+ pass
40
+
41
+ def invoke(self, prompt, **kwargs):
42
+ """Intercept and authenticate prompts."""
43
+ try:
44
+ auth_prompt = self._macaw.create_authenticated_prompt(
45
+ prompt_text=str(prompt),
46
+ metadata={
47
+ "agent_id": self._macaw.agent_id,
48
+ "source": "langchain_agent"
49
+ }
50
+ )
51
+ logger.debug("[SecureAgent] Created authenticated prompt")
52
+ return self._llm.invoke(auth_prompt, **kwargs)
53
+ except Exception as e:
54
+ logger.warning(f"Auth prompt failed, using original: {e}")
55
+ return self._llm.invoke(prompt, **kwargs)
56
+
57
+ def predict(self, text: str, **kwargs):
58
+ """Handle predict interface."""
59
+ try:
60
+ auth_text = self._macaw.create_authenticated_prompt(
61
+ prompt_text=text,
62
+ metadata={"agent_id": self._macaw.agent_id}
63
+ )
64
+ return self._llm.predict(auth_text, **kwargs)
65
+ except Exception as e:
66
+ logger.warning(f"Auth prompt failed, using original: {e}")
67
+ return self._llm.predict(text, **kwargs)
68
+
69
+ def __getattr__(self, name):
70
+ """Delegate to original LLM."""
71
+ return getattr(self._llm, name)
72
+
73
+
74
+ def _setup_security(
75
+ tools: List[Any],
76
+ security_policy: Optional[Dict[str, Any]]
77
+ ) -> tuple:
78
+ """
79
+ Set up MACAW security for tools.
80
+
81
+ Args:
82
+ tools: List of LangChain tools
83
+ security_policy: MAPL format policy dict with resources, denied_resources, constraints
84
+
85
+ Returns:
86
+ (processed_tools, macaw_client) - wrapped tools and client, or originals if no policy
87
+
88
+ Example policy (MAPL format):
89
+ {
90
+ "resources": ["tool:calculator", "tool:weather"],
91
+ "denied_resources": ["tool:admin"],
92
+ "constraints": {
93
+ "denied_parameters": {"tool:*": {"query": ["*password*"]}}
94
+ }
95
+ }
96
+ """
97
+ if not security_policy:
98
+ return tools, None
99
+
100
+ # Filter tools based on policy - LLM should only see tools it can use
101
+ # This prevents wasted iterations on blocked tools
102
+ allowed_tools = tools
103
+ if "resources" in security_policy:
104
+ allowed_names = {r.replace("tool:", "") for r in security_policy["resources"]}
105
+ allowed_tools = [t for t in tools if t.name in allowed_names]
106
+ logger.debug(f"Filtered tools to allowed resources: {[t.name for t in allowed_tools]}")
107
+
108
+ # Also exclude explicitly denied tools
109
+ if "denied_resources" in security_policy:
110
+ denied_names = {r.replace("tool:", "") for r in security_policy["denied_resources"]}
111
+ allowed_tools = [t for t in allowed_tools if t.name not in denied_names]
112
+ logger.debug(f"Excluded denied resources: {denied_names}")
113
+
114
+ # Build tools dict with handlers and prompts declaration
115
+ # Following the same pattern as SecureOpenAI
116
+ tools_config = {}
117
+ for tool in allowed_tools:
118
+ handler = None
119
+ if hasattr(tool, 'func'):
120
+ # Wrap func to handle params as dict or string
121
+ def make_handler(func):
122
+ def handler(params):
123
+ if isinstance(params, dict):
124
+ # Try 'input' key first (standard), then unpack as kwargs
125
+ if 'input' in params:
126
+ return func(params['input'])
127
+ return func(**params)
128
+ return func(params)
129
+ return handler
130
+ handler = make_handler(tool.func)
131
+ elif hasattr(tool, '_run'):
132
+ def make_run_handler(t):
133
+ def handler(params):
134
+ if isinstance(params, dict):
135
+ return t._run(**params)
136
+ return t._run(params)
137
+ return handler
138
+ handler = make_run_handler(tool)
139
+
140
+ if handler:
141
+ # Check if security_policy specifies prompts for this tool
142
+ # Default: LangChain tools take 'input' as the prompt parameter
143
+ tool_prompts = security_policy.get('tool_prompts', {}).get(tool.name, ['input'])
144
+ tools_config[tool.name] = {
145
+ "handler": handler,
146
+ "prompts": tool_prompts
147
+ }
148
+
149
+ # Create MACAW client with unified tools config
150
+ client = MACAWClient(
151
+ app_name="secure-langchain-agent",
152
+ app_version="1.0.0",
153
+ intent_policy=security_policy, # MAPL format policy passed directly
154
+ tools=tools_config # Unified: {name: {handler, prompts, ...}}
155
+ )
156
+
157
+ if client.register():
158
+ register_client(client.agent_id, client)
159
+ logger.info(f"SecureAgent registered: {client.agent_id}")
160
+ return wrap_tools(allowed_tools, client), client
161
+ else:
162
+ logger.error("Failed to register with MACAW")
163
+ return allowed_tools, None
164
+
165
+
166
+ def create_react_agent(
167
+ llm: Any,
168
+ tools: List[Any],
169
+ prompt: Any,
170
+ security_policy: Optional[Dict[str, Any]] = None,
171
+ **kwargs
172
+ ) -> Any:
173
+ """
174
+ Drop-in replacement for LangChain's create_react_agent.
175
+
176
+ Args:
177
+ llm: Language model
178
+ tools: List of tools
179
+ prompt: Prompt template
180
+ security_policy: Optional security policy (simple format)
181
+ **kwargs: Additional arguments for original function
182
+
183
+ Returns:
184
+ Agent runnable
185
+
186
+ Example:
187
+ agent = create_react_agent(
188
+ llm, tools, prompt,
189
+ security_policy={"allowed_tools": ["calculator", "weather"]}
190
+ )
191
+ """
192
+ from langchain.agents import create_react_agent as _create_react_agent
193
+
194
+ wrapped_tools, client = _setup_security(tools, security_policy)
195
+
196
+ # Wrap LLM if we have a client
197
+ wrapped_llm = _AuthenticatedLLMWrapper(llm, client) if client else llm
198
+
199
+ return _create_react_agent(wrapped_llm, wrapped_tools, prompt, **kwargs)
200
+
201
+
202
+ def create_openai_functions_agent(
203
+ llm: Any,
204
+ tools: List[Any],
205
+ prompt: Any,
206
+ security_policy: Optional[Dict[str, Any]] = None,
207
+ **kwargs
208
+ ) -> Any:
209
+ """
210
+ Drop-in replacement for LangChain's create_openai_functions_agent.
211
+
212
+ Args:
213
+ llm: Language model
214
+ tools: List of tools
215
+ prompt: Prompt template
216
+ security_policy: Optional security policy
217
+ **kwargs: Additional arguments
218
+
219
+ Returns:
220
+ Agent runnable
221
+ """
222
+ from langchain.agents import create_openai_functions_agent as _create_openai_functions_agent
223
+
224
+ wrapped_tools, client = _setup_security(tools, security_policy)
225
+ wrapped_llm = _AuthenticatedLLMWrapper(llm, client) if client else llm
226
+
227
+ return _create_openai_functions_agent(wrapped_llm, wrapped_tools, prompt, **kwargs)
228
+
229
+
230
+ class AgentExecutor:
231
+ """
232
+ Drop-in replacement for LangChain's AgentExecutor with security support.
233
+
234
+ Uses composition to wrap the original while enforcing policies.
235
+ """
236
+
237
+ def __init__(
238
+ self,
239
+ agent: Any,
240
+ tools: List[Any],
241
+ security_policy: Optional[Dict[str, Any]] = None,
242
+ **kwargs
243
+ ):
244
+ """
245
+ Initialize with optional security policy.
246
+
247
+ Args:
248
+ agent: The agent to use
249
+ tools: List of tools
250
+ security_policy: Optional security policy
251
+ **kwargs: Arguments for original AgentExecutor
252
+ """
253
+ from langchain.agents import AgentExecutor as _AgentExecutor
254
+
255
+ wrapped_tools, self._macaw = _setup_security(tools, security_policy)
256
+
257
+ # Wrap agent's LLM if present
258
+ if self._macaw and hasattr(agent, 'llm'):
259
+ try:
260
+ agent.llm = _AuthenticatedLLMWrapper(agent.llm, self._macaw)
261
+ except Exception:
262
+ pass
263
+
264
+ self._executor = _AgentExecutor(agent=agent, tools=wrapped_tools, **kwargs)
265
+
266
+ def invoke(self, input_data: Any, **kwargs) -> Any:
267
+ return self._executor.invoke(input_data, **kwargs)
268
+
269
+ def batch(self, inputs: List[Any], **kwargs) -> List:
270
+ return self._executor.batch(inputs, **kwargs)
271
+
272
+ def stream(self, input_data: Any, **kwargs):
273
+ return self._executor.stream(input_data, **kwargs)
274
+
275
+ async def ainvoke(self, input_data: Any, **kwargs) -> Any:
276
+ return await self._executor.ainvoke(input_data, **kwargs)
277
+
278
+ async def abatch(self, inputs: List[Any], **kwargs) -> List:
279
+ return await self._executor.abatch(inputs, **kwargs)
280
+
281
+ async def astream(self, input_data: Any, **kwargs):
282
+ async for chunk in self._executor.astream(input_data, **kwargs):
283
+ yield chunk
284
+
285
+ def __getattr__(self, name: str) -> Any:
286
+ return getattr(self._executor, name)
287
+
288
+
289
+ def cleanup():
290
+ """Clean up all MACAW resources."""
291
+ cleanup_all()
292
+
293
+
294
+ # Auto-cleanup on module exit
295
+ import atexit
296
+ atexit.register(cleanup)