@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.
- package/LICENSE.md +339 -0
- package/bin/install.js +278 -0
- package/package.json +37 -4
- package/payload/README.md +56 -0
- package/payload/secureAI/LICENSE +190 -0
- package/payload/secureAI/NOTICE +5 -0
- package/payload/secureAI/demos/policy-controls/1_uncontrolled.py +139 -0
- package/payload/secureAI/demos/policy-controls/2_with_macaw.py +139 -0
- package/payload/secureAI/demos/policy-controls/3_with_user_policies.py +162 -0
- package/payload/secureAI/demos/policy-controls/credentials.txt +12 -0
- package/payload/secureAI/demos/policy-controls/malicious_report.txt +21 -0
- package/payload/secureAI/demos/policy-controls/sales_report.txt +20 -0
- package/payload/secureAI/demos/tutorial-1-rbac/README.md +143 -0
- package/payload/secureAI/demos/tutorial-1-rbac/app.py +147 -0
- package/payload/secureAI/demos/tutorial-1-rbac/authprompts_demo.py +306 -0
- package/payload/secureAI/demos/tutorial-1-rbac/config/claims-config.yaml +152 -0
- package/payload/secureAI/demos/tutorial-1-rbac/demo_secureopenai_with_policies.py +150 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/bu_Analytics.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/company_FinTech_Corp.json +70 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/llm-base.json +69 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/team_Reporting.json +8 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_alice.json +32 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_bob.json +28 -0
- package/payload/secureAI/demos/tutorial-1-rbac/policies/user_carol.json +34 -0
- package/payload/secureAI/demos/tutorial-1-rbac/requirements.txt +6 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/auth0_complete_setup.sh +392 -0
- package/payload/secureAI/demos/tutorial-1-rbac/setup/keycloak_complete_setup.sh +320 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/alation-remote-proxy.json +155 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/bu.json +22 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/company.json +17 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_alice.json +48 -0
- package/payload/secureAI/demos/tutorial-2-intent/Policies/user_bob.json +83 -0
- package/payload/secureAI/demos/tutorial-2-intent/README.md +213 -0
- package/payload/secureAI/demos/tutorial-2-intent/script.py +188 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/alation_verifier.py +177 -0
- package/payload/secureAI/demos/tutorial-2-intent/setup/get_alation_user_token.py +180 -0
- package/payload/secureAI/examples/README.md +244 -0
- package/payload/secureAI/examples/anthropic/anthropic_1a_dropin_simple.py +103 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1b_multiuser_bind_streaming.py +190 -0
- package/payload/secureAI/examples/anthropic/anthropic_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/attestations/1a_trade_alice.py +215 -0
- package/payload/secureAI/examples/attestations/1a_trade_bob.py +164 -0
- package/payload/secureAI/examples/attestations/1b_grant_admin.py +170 -0
- package/payload/secureAI/examples/attestations/1b_grant_researcher.py +231 -0
- package/payload/secureAI/examples/attestations/README.md +128 -0
- package/payload/secureAI/examples/langchain/langchain_1a_dropin_simple.py +199 -0
- package/payload/secureAI/examples/langchain/langchain_1b_multiuser.py +262 -0
- package/payload/secureAI/examples/langchain/langchain_1c_orchestration.py +332 -0
- package/payload/secureAI/examples/langchain/langchain_1d_llm_openai.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1e_llm_anthropic.py +130 -0
- package/payload/secureAI/examples/langchain/langchain_1f_memory.py +214 -0
- package/payload/secureAI/examples/litellm/litellm_1a_dropin_simple.py +131 -0
- package/payload/secureAI/examples/mcp/1a_simple_invocation.py +115 -0
- package/payload/secureAI/examples/mcp/1b_discovery_and_resources.py +142 -0
- package/payload/secureAI/examples/mcp/1c_logging_client.py +100 -0
- package/payload/secureAI/examples/mcp/1d_progress_client.py +100 -0
- package/payload/secureAI/examples/mcp/1e_sampling_client.py +150 -0
- package/payload/secureAI/examples/mcp/1e_sampling_server.py +90 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_client.py +127 -0
- package/payload/secureAI/examples/mcp/1f_elicitation_server.py +73 -0
- package/payload/secureAI/examples/mcp/1g_roots_client.py +122 -0
- package/payload/secureAI/examples/mcp/1g_roots_server.py +85 -0
- package/payload/secureAI/examples/mcp/2a_proxy_snowflake.py +212 -0
- package/payload/secureAI/examples/mcp/2b_proxy_databricks.py +299 -0
- package/payload/secureAI/examples/mcp/2c_proxy_stdio_fetch.py +91 -0
- package/payload/secureAI/examples/mcp/2d_proxy_stdio_salesforce.py +119 -0
- package/payload/secureAI/examples/mcp/2e_proxy_multiuser.py +136 -0
- package/payload/secureAI/examples/mcp/2f_proxy_stdio_databricks.py +144 -0
- package/payload/secureAI/examples/mcp/3a_native_mcp_client.py +104 -0
- package/payload/secureAI/examples/mcp/3b_native_mcp_proxy.py +114 -0
- package/payload/secureAI/examples/mcp/3c_native_mcp_sampling.py +180 -0
- package/payload/secureAI/examples/mcp/3d_native_mcp_2026.py +135 -0
- package/payload/secureAI/examples/mcp/securemcp_calculator.py +248 -0
- package/payload/secureAI/examples/mcp/securemcp_server.py +75 -0
- package/payload/secureAI/examples/openai/openai_1a_dropin_simple.py +102 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind.py +193 -0
- package/payload/secureAI/examples/openai/openai_1b_multiuser_bind_streaming.py +199 -0
- package/payload/secureAI/examples/openai/openai_1c_a2a_invoke.py +156 -0
- package/payload/secureAI/examples/openai/openai_1d_invocation_patterns.py +337 -0
- package/payload/secureAI/examples/phishing-resistance/1a_login_alice.py +180 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_alice.py +241 -0
- package/payload/secureAI/examples/phishing-resistance/1b_trade_bob.py +164 -0
- package/payload/secureAI/examples/phishing-resistance/README.md +124 -0
- package/payload/secureAI/examples/phishing-resistance/get_token.py +114 -0
- package/payload/secureAI/examples/pydantic_ai/README.md +88 -0
- package/payload/secureAI/examples/pydantic_ai/policies/app_pydantic-agent.json +10 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1a_dropin_simple.py +90 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1b_model_routing.py +85 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1c_native_tools.py +83 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1d_multiuser_bind.py +104 -0
- package/payload/secureAI/examples/pydantic_ai/pydanticai_1e_mcp_compose.py +92 -0
- package/payload/secureAI/macaw_adapters/__init__.py +55 -0
- package/payload/secureAI/macaw_adapters/anthropic/REFERENCE.md +374 -0
- package/payload/secureAI/macaw_adapters/anthropic/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/anthropic/secure_anthropic.py +792 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/anthropic_ext_tools/secure_anthropic.py +840 -0
- package/payload/secureAI/macaw_adapters/langchain/REFERENCE.md +234 -0
- package/payload/secureAI/macaw_adapters/langchain/__init__.py +100 -0
- package/payload/secureAI/macaw_adapters/langchain/_utils.py +83 -0
- package/payload/secureAI/macaw_adapters/langchain/agents.py +296 -0
- package/payload/secureAI/macaw_adapters/langchain/anthropic.py +456 -0
- package/payload/secureAI/macaw_adapters/langchain/callbacks.py +306 -0
- package/payload/secureAI/macaw_adapters/langchain/memory.py +352 -0
- package/payload/secureAI/macaw_adapters/langchain/openai.py +410 -0
- package/payload/secureAI/macaw_adapters/langchain/tools.py +174 -0
- package/payload/secureAI/macaw_adapters/litellm/README.md +216 -0
- package/payload/secureAI/macaw_adapters/litellm/__init__.py +143 -0
- package/payload/secureAI/macaw_adapters/litellm/secure_litellm.py +686 -0
- package/payload/secureAI/macaw_adapters/mcp/REFERENCE.md +460 -0
- package/payload/secureAI/macaw_adapters/mcp/__init__.py +65 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint.py +193 -0
- package/payload/secureAI/macaw_adapters/mcp/_endpoint2.py +225 -0
- package/payload/secureAI/macaw_adapters/mcp/_serve.py +25 -0
- package/payload/secureAI/macaw_adapters/mcp/client.py +721 -0
- package/payload/secureAI/macaw_adapters/mcp/decorators.py +228 -0
- package/payload/secureAI/macaw_adapters/mcp/fastmcp_compat.py +353 -0
- package/payload/secureAI/macaw_adapters/mcp/mcp.py +785 -0
- package/payload/secureAI/macaw_adapters/mcp/proxy.py +626 -0
- package/payload/secureAI/macaw_adapters/mcp/server.py +317 -0
- package/payload/secureAI/macaw_adapters/mcp/subscriptions.py +450 -0
- package/payload/secureAI/macaw_adapters/openai/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai/__init__.py +22 -0
- package/payload/secureAI/macaw_adapters/openai/secure_openai.py +833 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/REFERENCE.md +368 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/__init__.py +25 -0
- package/payload/secureAI/macaw_adapters/openai_ext_tools/secure_openai.py +876 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/__init__.py +36 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/_core.py +257 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/agent.py +218 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/model.py +99 -0
- package/payload/secureAI/macaw_adapters/pydantic_ai/toolset.py +86 -0
- package/payload/secureAI/test_harness_quick.py +296 -0
- package/wheels/macaw_adapters-0.9.10.1-py3-none-any.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp312-cp312-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-macosx_11_0_arm64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-manylinux_2_17_x86_64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp314-cp314-win_amd64.whl +0 -0
- package/wheels/macaw_client-0.9.10.1-cp39-cp39-macosx_11_0_arm64.whl +0 -0
- 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)
|