control-zero 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- control_zero/__init__.py +31 -0
- control_zero/client.py +584 -0
- control_zero/integrations/crewai/__init__.py +53 -0
- control_zero/integrations/crewai/agent.py +267 -0
- control_zero/integrations/crewai/crew.py +381 -0
- control_zero/integrations/crewai/task.py +291 -0
- control_zero/integrations/crewai/tool.py +299 -0
- control_zero/integrations/langchain/__init__.py +58 -0
- control_zero/integrations/langchain/agent.py +311 -0
- control_zero/integrations/langchain/callbacks.py +441 -0
- control_zero/integrations/langchain/chain.py +319 -0
- control_zero/integrations/langchain/graph.py +441 -0
- control_zero/integrations/langchain/tool.py +271 -0
- control_zero/llm/__init__.py +77 -0
- control_zero/llm/anthropic/__init__.py +35 -0
- control_zero/llm/anthropic/client.py +136 -0
- control_zero/llm/anthropic/messages.py +375 -0
- control_zero/llm/base.py +551 -0
- control_zero/llm/cohere/__init__.py +32 -0
- control_zero/llm/cohere/client.py +402 -0
- control_zero/llm/gemini/__init__.py +34 -0
- control_zero/llm/gemini/client.py +486 -0
- control_zero/llm/groq/__init__.py +32 -0
- control_zero/llm/groq/client.py +330 -0
- control_zero/llm/mistral/__init__.py +32 -0
- control_zero/llm/mistral/client.py +319 -0
- control_zero/llm/ollama/__init__.py +31 -0
- control_zero/llm/ollama/client.py +439 -0
- control_zero/llm/openai/__init__.py +34 -0
- control_zero/llm/openai/chat.py +331 -0
- control_zero/llm/openai/client.py +182 -0
- control_zero/logging/__init__.py +5 -0
- control_zero/logging/async_logger.py +65 -0
- control_zero/mcp/__init__.py +5 -0
- control_zero/mcp/middleware.py +148 -0
- control_zero/policy/__init__.py +5 -0
- control_zero/policy/enforcer.py +99 -0
- control_zero/secrets/__init__.py +5 -0
- control_zero/secrets/manager.py +77 -0
- control_zero/types.py +51 -0
- control_zero-0.2.0.dist-info/METADATA +216 -0
- control_zero-0.2.0.dist-info/RECORD +44 -0
- control_zero-0.2.0.dist-info/WHEEL +4 -0
- control_zero-0.2.0.dist-info/licenses/LICENSE +17 -0
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Governed LangChain Agent wrapper.
|
|
3
|
+
|
|
4
|
+
Provides governance enforcement at the agent level including:
|
|
5
|
+
- Per-action policy checks
|
|
6
|
+
- Cost and token tracking
|
|
7
|
+
- Conversation audit logging
|
|
8
|
+
- Tool filtering
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Dict, List, Optional, Sequence, Union
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
from langchain.agents import AgentExecutor
|
|
18
|
+
from langchain_core.agents import AgentAction, AgentFinish
|
|
19
|
+
from langchain_core.tools import BaseTool
|
|
20
|
+
from langchain_core.callbacks import BaseCallbackHandler
|
|
21
|
+
LANGCHAIN_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
LANGCHAIN_AVAILABLE = False
|
|
24
|
+
class AgentExecutor:
|
|
25
|
+
pass
|
|
26
|
+
class AgentAction:
|
|
27
|
+
pass
|
|
28
|
+
class AgentFinish:
|
|
29
|
+
pass
|
|
30
|
+
class BaseTool:
|
|
31
|
+
pass
|
|
32
|
+
class BaseCallbackHandler:
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
from control_zero.client import ControlZeroClient
|
|
36
|
+
from control_zero.policy import PolicyDecision, PolicyDeniedError
|
|
37
|
+
from control_zero.integrations.langchain.tool import GovernanceTool
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class GovernedAgent:
|
|
41
|
+
"""
|
|
42
|
+
Governance wrapper for LangChain agents.
|
|
43
|
+
|
|
44
|
+
Wraps an AgentExecutor to provide:
|
|
45
|
+
- Tool-level policy enforcement
|
|
46
|
+
- Agent action logging
|
|
47
|
+
- Cost tracking
|
|
48
|
+
- Iteration limits
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
from langchain.agents import initialize_agent
|
|
52
|
+
|
|
53
|
+
agent = initialize_agent(tools, llm, agent=AgentType.OPENAI_FUNCTIONS)
|
|
54
|
+
|
|
55
|
+
governed_agent = GovernedAgent(
|
|
56
|
+
agent=agent,
|
|
57
|
+
client=client,
|
|
58
|
+
max_iterations=10,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
result = governed_agent.run("What's the weather?")
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
agent: AgentExecutor,
|
|
67
|
+
client: ControlZeroClient,
|
|
68
|
+
max_iterations: Optional[int] = None,
|
|
69
|
+
max_execution_time: Optional[float] = None,
|
|
70
|
+
wrap_tools: bool = True,
|
|
71
|
+
agent_name: str = "langchain_agent",
|
|
72
|
+
):
|
|
73
|
+
"""
|
|
74
|
+
Initialize governed agent.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
agent: The LangChain AgentExecutor to wrap
|
|
78
|
+
client: Control Zero client
|
|
79
|
+
max_iterations: Maximum agent iterations (overrides agent setting)
|
|
80
|
+
max_execution_time: Maximum execution time in seconds
|
|
81
|
+
wrap_tools: Whether to wrap agent tools with governance
|
|
82
|
+
agent_name: Name for logging purposes
|
|
83
|
+
"""
|
|
84
|
+
if not LANGCHAIN_AVAILABLE:
|
|
85
|
+
raise ImportError(
|
|
86
|
+
"langchain is required. Install with: pip install langchain"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
self._agent = agent
|
|
90
|
+
self._client = client
|
|
91
|
+
self._agent_name = agent_name
|
|
92
|
+
self._max_iterations = max_iterations
|
|
93
|
+
self._max_execution_time = max_execution_time
|
|
94
|
+
|
|
95
|
+
# Apply iteration limit
|
|
96
|
+
if max_iterations:
|
|
97
|
+
self._agent.max_iterations = max_iterations
|
|
98
|
+
if max_execution_time:
|
|
99
|
+
self._agent.max_execution_time = max_execution_time
|
|
100
|
+
|
|
101
|
+
# Wrap tools with governance if requested
|
|
102
|
+
if wrap_tools:
|
|
103
|
+
self._wrap_agent_tools()
|
|
104
|
+
|
|
105
|
+
# Tracking
|
|
106
|
+
self._total_iterations = 0
|
|
107
|
+
self._total_tool_calls = 0
|
|
108
|
+
self._session_start = time.time()
|
|
109
|
+
|
|
110
|
+
def _wrap_agent_tools(self) -> None:
|
|
111
|
+
"""Wrap all agent tools with governance."""
|
|
112
|
+
governed_tools = []
|
|
113
|
+
for tool in self._agent.tools:
|
|
114
|
+
if isinstance(tool, GovernanceTool):
|
|
115
|
+
governed_tools.append(tool)
|
|
116
|
+
else:
|
|
117
|
+
governed_tools.append(
|
|
118
|
+
GovernanceTool(
|
|
119
|
+
base_tool=tool,
|
|
120
|
+
client=self._client,
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
self._agent.tools = governed_tools
|
|
124
|
+
|
|
125
|
+
def _check_agent_policy(self, action: str) -> PolicyDecision:
|
|
126
|
+
"""Check policy for agent action."""
|
|
127
|
+
if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
|
|
128
|
+
return self._client._policy_cache.evaluate(self._agent_name, action)
|
|
129
|
+
return PolicyDecision(effect="allow")
|
|
130
|
+
|
|
131
|
+
def run(
|
|
132
|
+
self,
|
|
133
|
+
input: Union[str, Dict[str, Any]],
|
|
134
|
+
callbacks: Optional[List[BaseCallbackHandler]] = None,
|
|
135
|
+
**kwargs,
|
|
136
|
+
) -> str:
|
|
137
|
+
"""
|
|
138
|
+
Run the agent with governance.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
input: Agent input (string or dict)
|
|
142
|
+
callbacks: Additional callbacks
|
|
143
|
+
**kwargs: Additional arguments passed to agent
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Agent response string
|
|
147
|
+
|
|
148
|
+
Raises:
|
|
149
|
+
PolicyDeniedError: If agent execution is denied
|
|
150
|
+
"""
|
|
151
|
+
# Check agent-level policy
|
|
152
|
+
decision = self._check_agent_policy("run")
|
|
153
|
+
if decision.effect == "deny":
|
|
154
|
+
self._log_denied("run", decision)
|
|
155
|
+
raise PolicyDeniedError(decision)
|
|
156
|
+
|
|
157
|
+
start = time.perf_counter()
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
# Run agent
|
|
161
|
+
result = self._agent.run(input, callbacks=callbacks, **kwargs)
|
|
162
|
+
|
|
163
|
+
# Log success
|
|
164
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
165
|
+
self._log_success("run", latency_ms, decision, result)
|
|
166
|
+
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
except PolicyDeniedError:
|
|
170
|
+
raise
|
|
171
|
+
except Exception as e:
|
|
172
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
173
|
+
self._log_error("run", latency_ms, decision, e)
|
|
174
|
+
raise
|
|
175
|
+
|
|
176
|
+
async def arun(
|
|
177
|
+
self,
|
|
178
|
+
input: Union[str, Dict[str, Any]],
|
|
179
|
+
callbacks: Optional[List[BaseCallbackHandler]] = None,
|
|
180
|
+
**kwargs,
|
|
181
|
+
) -> str:
|
|
182
|
+
"""Async run with governance."""
|
|
183
|
+
decision = self._check_agent_policy("run")
|
|
184
|
+
if decision.effect == "deny":
|
|
185
|
+
self._log_denied("run", decision)
|
|
186
|
+
raise PolicyDeniedError(decision)
|
|
187
|
+
|
|
188
|
+
start = time.perf_counter()
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
result = await self._agent.arun(input, callbacks=callbacks, **kwargs)
|
|
192
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
193
|
+
self._log_success("run", latency_ms, decision, result)
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
except PolicyDeniedError:
|
|
197
|
+
raise
|
|
198
|
+
except Exception as e:
|
|
199
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
200
|
+
self._log_error("run", latency_ms, decision, e)
|
|
201
|
+
raise
|
|
202
|
+
|
|
203
|
+
def invoke(
|
|
204
|
+
self,
|
|
205
|
+
input: Union[str, Dict[str, Any]],
|
|
206
|
+
config: Optional[Dict] = None,
|
|
207
|
+
**kwargs,
|
|
208
|
+
) -> Dict[str, Any]:
|
|
209
|
+
"""
|
|
210
|
+
Invoke the agent (LCEL interface) with governance.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
input: Agent input
|
|
214
|
+
config: Run configuration
|
|
215
|
+
**kwargs: Additional arguments
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Agent output dict
|
|
219
|
+
"""
|
|
220
|
+
decision = self._check_agent_policy("invoke")
|
|
221
|
+
if decision.effect == "deny":
|
|
222
|
+
self._log_denied("invoke", decision)
|
|
223
|
+
raise PolicyDeniedError(decision)
|
|
224
|
+
|
|
225
|
+
start = time.perf_counter()
|
|
226
|
+
|
|
227
|
+
try:
|
|
228
|
+
result = self._agent.invoke(input, config=config, **kwargs)
|
|
229
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
230
|
+
self._log_success("invoke", latency_ms, decision, str(result))
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
except PolicyDeniedError:
|
|
234
|
+
raise
|
|
235
|
+
except Exception as e:
|
|
236
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
237
|
+
self._log_error("invoke", latency_ms, decision, e)
|
|
238
|
+
raise
|
|
239
|
+
|
|
240
|
+
async def ainvoke(
|
|
241
|
+
self,
|
|
242
|
+
input: Union[str, Dict[str, Any]],
|
|
243
|
+
config: Optional[Dict] = None,
|
|
244
|
+
**kwargs,
|
|
245
|
+
) -> Dict[str, Any]:
|
|
246
|
+
"""Async invoke with governance."""
|
|
247
|
+
decision = self._check_agent_policy("invoke")
|
|
248
|
+
if decision.effect == "deny":
|
|
249
|
+
self._log_denied("invoke", decision)
|
|
250
|
+
raise PolicyDeniedError(decision)
|
|
251
|
+
|
|
252
|
+
start = time.perf_counter()
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
result = await self._agent.ainvoke(input, config=config, **kwargs)
|
|
256
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
257
|
+
self._log_success("invoke", latency_ms, decision, str(result))
|
|
258
|
+
return result
|
|
259
|
+
|
|
260
|
+
except PolicyDeniedError:
|
|
261
|
+
raise
|
|
262
|
+
except Exception as e:
|
|
263
|
+
latency_ms = int((time.perf_counter() - start) * 1000)
|
|
264
|
+
self._log_error("invoke", latency_ms, decision, e)
|
|
265
|
+
raise
|
|
266
|
+
|
|
267
|
+
def _log_denied(self, method: str, decision: PolicyDecision) -> None:
|
|
268
|
+
"""Log denied execution."""
|
|
269
|
+
self._client._log(
|
|
270
|
+
self._agent_name, method, "denied", 0,
|
|
271
|
+
policy_decision=decision
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _log_success(
|
|
275
|
+
self,
|
|
276
|
+
method: str,
|
|
277
|
+
latency_ms: int,
|
|
278
|
+
decision: PolicyDecision,
|
|
279
|
+
result: str,
|
|
280
|
+
) -> None:
|
|
281
|
+
"""Log successful execution."""
|
|
282
|
+
self._client._log(
|
|
283
|
+
self._agent_name, method, "success", latency_ms,
|
|
284
|
+
policy_decision=decision,
|
|
285
|
+
metadata={"result_length": len(result)}
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
def _log_error(
|
|
289
|
+
self,
|
|
290
|
+
method: str,
|
|
291
|
+
latency_ms: int,
|
|
292
|
+
decision: PolicyDecision,
|
|
293
|
+
error: Exception,
|
|
294
|
+
) -> None:
|
|
295
|
+
"""Log execution error."""
|
|
296
|
+
self._client._log(
|
|
297
|
+
self._agent_name, method, "error", latency_ms,
|
|
298
|
+
policy_decision=decision,
|
|
299
|
+
error_type=type(error).__name__,
|
|
300
|
+
error_message=str(error)
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def tools(self) -> List[BaseTool]:
|
|
305
|
+
"""Get agent tools."""
|
|
306
|
+
return self._agent.tools
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def agent(self) -> AgentExecutor:
|
|
310
|
+
"""Get underlying agent executor."""
|
|
311
|
+
return self._agent
|