@macaw-ai/client 0.0.0-stage → 0.9.11

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 +422 -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,306 @@
1
+ """
2
+ MACAW Callback Handler - MACAW-integrated LangChain callbacks.
3
+
4
+ Usage:
5
+ from macaw_adapters.langchain.callbacks import MACAWCallbackHandler
6
+
7
+ # Simple usage with MACAWClient
8
+ handler = MACAWCallbackHandler.from_client(macaw_client)
9
+ chain = LLMChain(llm=llm, callbacks=[handler])
10
+
11
+ # Or with the drop-in ChatOpenAI
12
+ from macaw_adapters.langchain import ChatOpenAI, MACAWCallbackHandler
13
+
14
+ llm = ChatOpenAI(model="gpt-4")
15
+ handler = MACAWCallbackHandler.from_client(llm._macaw)
16
+ """
17
+
18
+ import logging
19
+ from typing import Any, Dict, List, Optional, Union
20
+ from uuid import UUID
21
+
22
+ from macaw_client import MACAWClient
23
+ from ._utils import get_or_create_client
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ try:
28
+ from langchain_core.callbacks import BaseCallbackHandler
29
+ LANGCHAIN_AVAILABLE = True
30
+ except ImportError:
31
+ try:
32
+ from langchain.callbacks.base import BaseCallbackHandler
33
+ LANGCHAIN_AVAILABLE = True
34
+ except ImportError:
35
+ class BaseCallbackHandler:
36
+ pass
37
+ LANGCHAIN_AVAILABLE = False
38
+
39
+
40
+ class MACAWCallbackHandler(BaseCallbackHandler):
41
+ """
42
+ LangChain callback handler that logs all events through MACAW.
43
+
44
+ This provides audit logging for:
45
+ - LLM invocations (on_llm_start, on_llm_end, on_llm_error)
46
+ - Tool executions (on_tool_start, on_tool_end, on_tool_error)
47
+ - Chain executions (on_chain_start, on_chain_end, on_chain_error)
48
+
49
+ All events are logged via MACAWClient.log_event() for signed audit trails.
50
+ """
51
+
52
+ def __init__(self, macaw_client: MACAWClient):
53
+ """
54
+ Initialize with a MACAWClient.
55
+
56
+ Args:
57
+ macaw_client: MACAWClient instance for logging events
58
+ """
59
+ if not LANGCHAIN_AVAILABLE:
60
+ raise ImportError("LangChain must be installed to use MACAWCallbackHandler")
61
+
62
+ super().__init__()
63
+ self._macaw = macaw_client
64
+
65
+ @classmethod
66
+ def from_client(cls, macaw_client: MACAWClient) -> 'MACAWCallbackHandler':
67
+ """Create handler from existing MACAWClient."""
68
+ return cls(macaw_client)
69
+
70
+ @classmethod
71
+ def create(cls, app_name: str = "langchain-callbacks") -> 'MACAWCallbackHandler':
72
+ """Create handler with a new MACAWClient."""
73
+ client = get_or_create_client(app_name)
74
+ if not client:
75
+ raise RuntimeError("Failed to create MACAWClient")
76
+ return cls(client)
77
+
78
+ # LLM Callbacks
79
+
80
+ def on_llm_start(
81
+ self,
82
+ serialized: Dict[str, Any],
83
+ prompts: List[str],
84
+ *,
85
+ run_id: UUID,
86
+ parent_run_id: Optional[UUID] = None,
87
+ **kwargs: Any
88
+ ) -> None:
89
+ """Log LLM invocation start."""
90
+ self._macaw.log_event(
91
+ event_type="llm_start",
92
+ action="start",
93
+ target=f"llm:{serialized.get('name', 'unknown')}",
94
+ metadata={
95
+ "run_id": str(run_id),
96
+ "parent_run_id": str(parent_run_id) if parent_run_id else None,
97
+ "model": serialized.get("name"),
98
+ "prompt_count": len(prompts),
99
+ "total_prompt_length": sum(len(p) for p in prompts)
100
+ }
101
+ )
102
+
103
+ def on_llm_end(
104
+ self,
105
+ response: Any,
106
+ *,
107
+ run_id: UUID,
108
+ parent_run_id: Optional[UUID] = None,
109
+ **kwargs: Any
110
+ ) -> None:
111
+ """Log LLM invocation completion."""
112
+ self._macaw.log_event(
113
+ event_type="llm_end",
114
+ action="complete",
115
+ target="llm",
116
+ metadata={
117
+ "run_id": str(run_id),
118
+ "parent_run_id": str(parent_run_id) if parent_run_id else None
119
+ }
120
+ )
121
+
122
+ def on_llm_error(
123
+ self,
124
+ error: Union[Exception, KeyboardInterrupt],
125
+ *,
126
+ run_id: UUID,
127
+ parent_run_id: Optional[UUID] = None,
128
+ **kwargs: Any
129
+ ) -> None:
130
+ """Log LLM error."""
131
+ self._macaw.log_event(
132
+ event_type="llm_error",
133
+ action="error",
134
+ target="llm",
135
+ metadata={
136
+ "run_id": str(run_id),
137
+ "error": str(error)
138
+ }
139
+ )
140
+
141
+ # Tool Callbacks
142
+
143
+ def on_tool_start(
144
+ self,
145
+ serialized: Dict[str, Any],
146
+ input_str: str,
147
+ *,
148
+ run_id: UUID,
149
+ parent_run_id: Optional[UUID] = None,
150
+ **kwargs: Any
151
+ ) -> None:
152
+ """Log tool invocation start."""
153
+ tool_name = serialized.get("name", "unknown")
154
+ self._macaw.log_event(
155
+ event_type="tool_start",
156
+ action="start",
157
+ target=f"tool:{tool_name}",
158
+ metadata={
159
+ "run_id": str(run_id),
160
+ "parent_run_id": str(parent_run_id) if parent_run_id else None,
161
+ "tool": tool_name,
162
+ "input_length": len(input_str)
163
+ }
164
+ )
165
+
166
+ def on_tool_end(
167
+ self,
168
+ output: str,
169
+ *,
170
+ run_id: UUID,
171
+ parent_run_id: Optional[UUID] = None,
172
+ **kwargs: Any
173
+ ) -> None:
174
+ """Log tool completion."""
175
+ self._macaw.log_event(
176
+ event_type="tool_end",
177
+ action="complete",
178
+ target="tool",
179
+ metadata={
180
+ "run_id": str(run_id),
181
+ "output_length": len(str(output)) if output else 0
182
+ }
183
+ )
184
+
185
+ def on_tool_error(
186
+ self,
187
+ error: Union[Exception, KeyboardInterrupt],
188
+ *,
189
+ run_id: UUID,
190
+ parent_run_id: Optional[UUID] = None,
191
+ **kwargs: Any
192
+ ) -> None:
193
+ """Log tool error."""
194
+ self._macaw.log_event(
195
+ event_type="tool_error",
196
+ action="error",
197
+ target="tool",
198
+ metadata={
199
+ "run_id": str(run_id),
200
+ "error": str(error)
201
+ }
202
+ )
203
+
204
+ # Chain Callbacks
205
+
206
+ def on_chain_start(
207
+ self,
208
+ serialized: Dict[str, Any],
209
+ inputs: Dict[str, Any],
210
+ *,
211
+ run_id: UUID,
212
+ parent_run_id: Optional[UUID] = None,
213
+ **kwargs: Any
214
+ ) -> None:
215
+ """Log chain execution start."""
216
+ chain_name = serialized.get("name", "unknown")
217
+ self._macaw.log_event(
218
+ event_type="chain_start",
219
+ action="start",
220
+ target=f"chain:{chain_name}",
221
+ metadata={
222
+ "run_id": str(run_id),
223
+ "parent_run_id": str(parent_run_id) if parent_run_id else None,
224
+ "chain": chain_name,
225
+ "input_keys": list(inputs.keys())
226
+ }
227
+ )
228
+
229
+ def on_chain_end(
230
+ self,
231
+ outputs: Dict[str, Any],
232
+ *,
233
+ run_id: UUID,
234
+ parent_run_id: Optional[UUID] = None,
235
+ **kwargs: Any
236
+ ) -> None:
237
+ """Log chain completion."""
238
+ self._macaw.log_event(
239
+ event_type="chain_end",
240
+ action="complete",
241
+ target="chain",
242
+ metadata={
243
+ "run_id": str(run_id),
244
+ "output_keys": list(outputs.keys()) if outputs else []
245
+ }
246
+ )
247
+
248
+ def on_chain_error(
249
+ self,
250
+ error: Union[Exception, KeyboardInterrupt],
251
+ *,
252
+ run_id: UUID,
253
+ parent_run_id: Optional[UUID] = None,
254
+ **kwargs: Any
255
+ ) -> None:
256
+ """Log chain error."""
257
+ self._macaw.log_event(
258
+ event_type="chain_error",
259
+ action="error",
260
+ target="chain",
261
+ metadata={
262
+ "run_id": str(run_id),
263
+ "error": str(error)
264
+ }
265
+ )
266
+
267
+ # Agent Callbacks
268
+
269
+ def on_agent_action(
270
+ self,
271
+ action: Any,
272
+ *,
273
+ run_id: UUID,
274
+ parent_run_id: Optional[UUID] = None,
275
+ **kwargs: Any
276
+ ) -> None:
277
+ """Log agent action."""
278
+ self._macaw.log_event(
279
+ event_type="agent_action",
280
+ action="action",
281
+ target="agent",
282
+ metadata={
283
+ "run_id": str(run_id),
284
+ "tool": getattr(action, 'tool', 'unknown'),
285
+ "tool_input": str(getattr(action, 'tool_input', ''))[:100]
286
+ }
287
+ )
288
+
289
+ def on_agent_finish(
290
+ self,
291
+ finish: Any,
292
+ *,
293
+ run_id: UUID,
294
+ parent_run_id: Optional[UUID] = None,
295
+ **kwargs: Any
296
+ ) -> None:
297
+ """Log agent finish."""
298
+ self._macaw.log_event(
299
+ event_type="agent_finish",
300
+ action="complete",
301
+ target="agent",
302
+ metadata={
303
+ "run_id": str(run_id),
304
+ "return_values": list(getattr(finish, 'return_values', {}).keys())
305
+ }
306
+ )
@@ -0,0 +1,352 @@
1
+ """
2
+ MACAW Memory Classes - Drop-in replacements for LangChain memory with MACAW context backing.
3
+
4
+ Usage:
5
+ # Instead of: from langchain.memory import ConversationBufferMemory
6
+ from macaw_adapters.langchain.memory import ConversationBufferMemory
7
+
8
+ # Same API, MACAW context vault backing
9
+ memory = ConversationBufferMemory()
10
+ chain = ConversationChain(llm=llm, memory=memory)
11
+ """
12
+
13
+ import logging
14
+ import uuid
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from macaw_client import MACAWClient
18
+ from ._utils import get_or_create_client, cleanup_client
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class ConversationBufferMemory:
24
+ """
25
+ Drop-in replacement for LangChain's ConversationBufferMemory with MACAW backing.
26
+
27
+ This memory class stores conversation history in MACAW's secure context vault,
28
+ providing audit logging and secure storage for all memory operations.
29
+ """
30
+
31
+ def __init__(
32
+ self,
33
+ memory_key: str = "history",
34
+ input_key: str = "input",
35
+ output_key: str = "output",
36
+ human_prefix: str = "Human",
37
+ ai_prefix: str = "AI",
38
+ return_messages: bool = False,
39
+ session_id: Optional[str] = None,
40
+ macaw_client: Optional[MACAWClient] = None,
41
+ **kwargs
42
+ ):
43
+ """
44
+ Initialize SecureConversationBufferMemory.
45
+
46
+ Args:
47
+ memory_key: Key to use for memory in chain context
48
+ input_key: Key for input in chain context
49
+ output_key: Key for output in chain context
50
+ human_prefix: Prefix for human messages
51
+ ai_prefix: Prefix for AI messages
52
+ return_messages: Return messages as list vs string
53
+ session_id: Unique session ID (auto-generated if not provided)
54
+ macaw_client: Optional pre-configured MACAWClient
55
+ **kwargs: Additional arguments
56
+ """
57
+ self.memory_key = memory_key
58
+ self.input_key = input_key
59
+ self.output_key = output_key
60
+ self.human_prefix = human_prefix
61
+ self.ai_prefix = ai_prefix
62
+ self.return_messages = return_messages
63
+ self._kwargs = kwargs
64
+
65
+ # Session identification
66
+ self.session_id = session_id or str(uuid.uuid4())
67
+ self._context_key = f"memory:{self.session_id}"
68
+
69
+ # Initialize MACAW client
70
+ self._macaw = macaw_client or get_or_create_client("langchain-memory")
71
+
72
+ # Local buffer (synced with MACAW context)
73
+ self._buffer: List[Dict[str, str]] = []
74
+
75
+ # Load existing memory from context if available
76
+ self._load_from_context()
77
+
78
+ def _load_from_context(self) -> None:
79
+ """Load existing memory from MACAW context."""
80
+ if self._macaw:
81
+ stored = self._macaw.context_get(self._context_key)
82
+ if stored and isinstance(stored, list):
83
+ self._buffer = stored
84
+ logger.debug(f"Loaded {len(self._buffer)} messages from context")
85
+
86
+ def _save_to_context(self) -> None:
87
+ """Save current memory to MACAW context."""
88
+ if self._macaw:
89
+ self._macaw.context_set(self._context_key, self._buffer)
90
+ logger.debug(f"Saved {len(self._buffer)} messages to context")
91
+
92
+ @property
93
+ def buffer(self) -> List[Dict[str, str]]:
94
+ """Get the conversation buffer."""
95
+ return self._buffer
96
+
97
+ @property
98
+ def memory_variables(self) -> List[str]:
99
+ """Return memory variables."""
100
+ return [self.memory_key]
101
+
102
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
103
+ """
104
+ Load memory variables for chain context.
105
+
106
+ Args:
107
+ inputs: Input dictionary (not used for buffer memory)
108
+
109
+ Returns:
110
+ Dictionary with memory_key containing conversation history
111
+ """
112
+ # Reload from context to get latest
113
+ self._load_from_context()
114
+
115
+ if self.return_messages:
116
+ # Return as list of message objects
117
+ try:
118
+ from langchain_core.messages import HumanMessage, AIMessage
119
+ messages = []
120
+ for msg in self._buffer:
121
+ if msg.get("type") == "human":
122
+ messages.append(HumanMessage(content=msg["content"]))
123
+ else:
124
+ messages.append(AIMessage(content=msg["content"]))
125
+ return {self.memory_key: messages}
126
+ except ImportError:
127
+ # Fallback to dicts if langchain_core not available
128
+ return {self.memory_key: self._buffer}
129
+ else:
130
+ # Return as formatted string
131
+ lines = []
132
+ for msg in self._buffer:
133
+ prefix = self.human_prefix if msg.get("type") == "human" else self.ai_prefix
134
+ lines.append(f"{prefix}: {msg['content']}")
135
+ return {self.memory_key: "\n".join(lines)}
136
+
137
+ def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
138
+ """
139
+ Save context from this conversation turn.
140
+
141
+ Args:
142
+ inputs: Input dictionary with user message
143
+ outputs: Output dictionary with AI response
144
+ """
145
+ # Extract input and output
146
+ input_str = inputs.get(self.input_key, "")
147
+ output_str = outputs.get(self.output_key, "")
148
+
149
+ # Handle different output formats
150
+ if hasattr(output_str, 'content'):
151
+ output_str = output_str.content
152
+ elif isinstance(output_str, dict):
153
+ output_str = output_str.get('content', str(output_str))
154
+
155
+ # Add to buffer
156
+ if input_str:
157
+ self._buffer.append({"type": "human", "content": str(input_str)})
158
+ if output_str:
159
+ self._buffer.append({"type": "ai", "content": str(output_str)})
160
+
161
+ # Persist to MACAW context
162
+ self._save_to_context()
163
+
164
+ def clear(self) -> None:
165
+ """Clear memory contents."""
166
+ self._buffer = []
167
+ self._save_to_context()
168
+ logger.debug(f"Cleared memory for session {self.session_id}")
169
+
170
+ def add_user_message(self, message: str) -> None:
171
+ """Add a user message to the buffer."""
172
+ self._buffer.append({"type": "human", "content": message})
173
+ self._save_to_context()
174
+
175
+ def add_ai_message(self, message: str) -> None:
176
+ """Add an AI message to the buffer."""
177
+ self._buffer.append({"type": "ai", "content": message})
178
+ self._save_to_context()
179
+
180
+
181
+ class ConversationBufferWindowMemory(ConversationBufferMemory):
182
+ """
183
+ Buffer memory that only keeps the last k conversation turns.
184
+
185
+ Drop-in replacement for LangChain's ConversationBufferWindowMemory.
186
+ """
187
+
188
+ def __init__(self, k: int = 5, **kwargs):
189
+ """
190
+ Initialize with window size.
191
+
192
+ Args:
193
+ k: Number of conversation turns to keep
194
+ **kwargs: Arguments passed to ConversationBufferMemory
195
+ """
196
+ super().__init__(**kwargs)
197
+ self.k = k
198
+
199
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
200
+ """Load only the last k turns."""
201
+ self._load_from_context()
202
+
203
+ # Keep only last k*2 messages (k turns = k human + k ai)
204
+ window = self._buffer[-(self.k * 2):]
205
+
206
+ if self.return_messages:
207
+ try:
208
+ from langchain_core.messages import HumanMessage, AIMessage
209
+ messages = []
210
+ for msg in window:
211
+ if msg.get("type") == "human":
212
+ messages.append(HumanMessage(content=msg["content"]))
213
+ else:
214
+ messages.append(AIMessage(content=msg["content"]))
215
+ return {self.memory_key: messages}
216
+ except ImportError:
217
+ return {self.memory_key: window}
218
+ else:
219
+ lines = []
220
+ for msg in window:
221
+ prefix = self.human_prefix if msg.get("type") == "human" else self.ai_prefix
222
+ lines.append(f"{prefix}: {msg['content']}")
223
+ return {self.memory_key: "\n".join(lines)}
224
+
225
+
226
+ class ConversationSummaryMemory:
227
+ """
228
+ Memory that summarizes conversation history.
229
+
230
+ Drop-in replacement for LangChain's ConversationSummaryMemory.
231
+ Stores summary in MACAW context vault.
232
+ """
233
+
234
+ def __init__(
235
+ self,
236
+ llm: Any = None,
237
+ memory_key: str = "history",
238
+ human_prefix: str = "Human",
239
+ ai_prefix: str = "AI",
240
+ session_id: Optional[str] = None,
241
+ macaw_client: Optional[MACAWClient] = None,
242
+ **kwargs
243
+ ):
244
+ """
245
+ Initialize summary memory.
246
+
247
+ Args:
248
+ llm: Language model for summarization
249
+ memory_key: Key for memory in chain context
250
+ human_prefix: Prefix for human messages
251
+ ai_prefix: Prefix for AI messages
252
+ session_id: Unique session ID
253
+ macaw_client: Optional pre-configured MACAWClient
254
+ """
255
+ self.llm = llm
256
+ self.memory_key = memory_key
257
+ self.human_prefix = human_prefix
258
+ self.ai_prefix = ai_prefix
259
+ self._kwargs = kwargs
260
+
261
+ # Session identification
262
+ self.session_id = session_id or str(uuid.uuid4())
263
+ self._summary_key = f"memory_summary:{self.session_id}"
264
+ self._buffer_key = f"memory_buffer:{self.session_id}"
265
+
266
+ # Initialize MACAW client
267
+ self._macaw = macaw_client or get_or_create_client("langchain-memory")
268
+
269
+ # Local state
270
+ self._summary = ""
271
+ self._buffer: List[Dict[str, str]] = []
272
+
273
+ # Load existing from context
274
+ self._load_from_context()
275
+
276
+ def _load_from_context(self) -> None:
277
+ """Load existing summary and buffer from context."""
278
+ if self._macaw:
279
+ summary = self._macaw.context_get(self._summary_key)
280
+ if summary:
281
+ self._summary = summary
282
+
283
+ buffer = self._macaw.context_get(self._buffer_key)
284
+ if buffer and isinstance(buffer, list):
285
+ self._buffer = buffer
286
+
287
+ def _save_to_context(self) -> None:
288
+ """Save summary and buffer to context."""
289
+ if self._macaw:
290
+ self._macaw.context_set(self._summary_key, self._summary)
291
+ self._macaw.context_set(self._buffer_key, self._buffer)
292
+
293
+ @property
294
+ def memory_variables(self) -> List[str]:
295
+ return [self.memory_key]
296
+
297
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
298
+ """Load summary as memory variable."""
299
+ self._load_from_context()
300
+ return {self.memory_key: self._summary}
301
+
302
+ def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
303
+ """Save context and update summary."""
304
+ input_key = self._kwargs.get('input_key', 'input')
305
+ output_key = self._kwargs.get('output_key', 'output')
306
+
307
+ input_str = inputs.get(input_key, "")
308
+ output_str = outputs.get(output_key, "")
309
+
310
+ if hasattr(output_str, 'content'):
311
+ output_str = output_str.content
312
+
313
+ # Add to buffer
314
+ new_lines = f"{self.human_prefix}: {input_str}\n{self.ai_prefix}: {output_str}"
315
+ self._buffer.append({"input": input_str, "output": output_str})
316
+
317
+ # Update summary using LLM if available
318
+ if self.llm:
319
+ try:
320
+ prompt = f"""Progressively summarize the conversation, adding to the previous summary.
321
+
322
+ Current summary: {self._summary}
323
+
324
+ New lines of conversation:
325
+ {new_lines}
326
+
327
+ New summary:"""
328
+ response = self.llm.invoke(prompt)
329
+ if hasattr(response, 'content'):
330
+ self._summary = response.content
331
+ else:
332
+ self._summary = str(response)
333
+ except Exception as e:
334
+ logger.warning(f"Failed to update summary: {e}")
335
+ # Fallback: append to summary
336
+ self._summary = f"{self._summary}\n{new_lines}" if self._summary else new_lines
337
+ else:
338
+ # No LLM - just concatenate
339
+ self._summary = f"{self._summary}\n{new_lines}" if self._summary else new_lines
340
+
341
+ self._save_to_context()
342
+
343
+ def clear(self) -> None:
344
+ """Clear memory."""
345
+ self._summary = ""
346
+ self._buffer = []
347
+ self._save_to_context()
348
+
349
+
350
+ def cleanup():
351
+ """Clean up the langchain-memory MACAW client."""
352
+ cleanup_client("langchain-memory")