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.
Files changed (44) hide show
  1. control_zero/__init__.py +31 -0
  2. control_zero/client.py +584 -0
  3. control_zero/integrations/crewai/__init__.py +53 -0
  4. control_zero/integrations/crewai/agent.py +267 -0
  5. control_zero/integrations/crewai/crew.py +381 -0
  6. control_zero/integrations/crewai/task.py +291 -0
  7. control_zero/integrations/crewai/tool.py +299 -0
  8. control_zero/integrations/langchain/__init__.py +58 -0
  9. control_zero/integrations/langchain/agent.py +311 -0
  10. control_zero/integrations/langchain/callbacks.py +441 -0
  11. control_zero/integrations/langchain/chain.py +319 -0
  12. control_zero/integrations/langchain/graph.py +441 -0
  13. control_zero/integrations/langchain/tool.py +271 -0
  14. control_zero/llm/__init__.py +77 -0
  15. control_zero/llm/anthropic/__init__.py +35 -0
  16. control_zero/llm/anthropic/client.py +136 -0
  17. control_zero/llm/anthropic/messages.py +375 -0
  18. control_zero/llm/base.py +551 -0
  19. control_zero/llm/cohere/__init__.py +32 -0
  20. control_zero/llm/cohere/client.py +402 -0
  21. control_zero/llm/gemini/__init__.py +34 -0
  22. control_zero/llm/gemini/client.py +486 -0
  23. control_zero/llm/groq/__init__.py +32 -0
  24. control_zero/llm/groq/client.py +330 -0
  25. control_zero/llm/mistral/__init__.py +32 -0
  26. control_zero/llm/mistral/client.py +319 -0
  27. control_zero/llm/ollama/__init__.py +31 -0
  28. control_zero/llm/ollama/client.py +439 -0
  29. control_zero/llm/openai/__init__.py +34 -0
  30. control_zero/llm/openai/chat.py +331 -0
  31. control_zero/llm/openai/client.py +182 -0
  32. control_zero/logging/__init__.py +5 -0
  33. control_zero/logging/async_logger.py +65 -0
  34. control_zero/mcp/__init__.py +5 -0
  35. control_zero/mcp/middleware.py +148 -0
  36. control_zero/policy/__init__.py +5 -0
  37. control_zero/policy/enforcer.py +99 -0
  38. control_zero/secrets/__init__.py +5 -0
  39. control_zero/secrets/manager.py +77 -0
  40. control_zero/types.py +51 -0
  41. control_zero-0.2.0.dist-info/METADATA +216 -0
  42. control_zero-0.2.0.dist-info/RECORD +44 -0
  43. control_zero-0.2.0.dist-info/WHEEL +4 -0
  44. control_zero-0.2.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,31 @@
1
+ """
2
+ Control Zero Ollama Governance Wrapper.
3
+
4
+ This module provides governance wrappers for Ollama local LLMs,
5
+ enabling policy enforcement, usage tracking, and audit logging for
6
+ locally-hosted open-source models.
7
+
8
+ Usage:
9
+ from control_zero import ControlZeroClient
10
+ from control_zero.llm.ollama import GovernedOllama
11
+ import ollama
12
+
13
+ # Initialize Control Zero
14
+ cz_client = ControlZeroClient(api_key="cz_live_xxx")
15
+ cz_client.initialize()
16
+
17
+ # Create governed Ollama client
18
+ governed = GovernedOllama(control_zero=cz_client)
19
+
20
+ # All calls are now governed
21
+ response = governed.chat(
22
+ model="llama3.2",
23
+ messages=[{"role": "user", "content": "Hello"}]
24
+ )
25
+ """
26
+
27
+ from control_zero.llm.ollama.client import GovernedOllama
28
+
29
+ __all__ = [
30
+ "GovernedOllama",
31
+ ]
@@ -0,0 +1,439 @@
1
+ """
2
+ Governed Ollama client wrapper.
3
+
4
+ Provides governance features for Ollama local LLMs including:
5
+ - Model access control
6
+ - Usage tracking
7
+ - PII detection and masking
8
+ - Audit logging
9
+
10
+ Ollama runs models locally, so there's no cost tracking,
11
+ but governance policies still apply for:
12
+ - Model allowlisting
13
+ - Request limits
14
+ - PII detection
15
+ - Audit logging
16
+ """
17
+
18
+ import time
19
+ from typing import Any, Dict, Iterator, List, Optional
20
+
21
+ from control_zero.llm.base import (
22
+ GovernanceAction,
23
+ GovernedLLM,
24
+ GovernedChatMixin,
25
+ LLMGovernanceConfig,
26
+ LLMUsageMetrics,
27
+ )
28
+ from control_zero.policy import PolicyDeniedError
29
+
30
+
31
+ class GovernedOllama(GovernedLLM, GovernedChatMixin):
32
+ """
33
+ Governed wrapper for Ollama local LLMs.
34
+
35
+ Ollama runs models locally, providing privacy and cost benefits.
36
+ This wrapper adds governance for:
37
+ - Model access control (which local models can be used)
38
+ - Request rate limiting
39
+ - PII detection and masking
40
+ - Audit logging
41
+
42
+ Example:
43
+ from control_zero import ControlZeroClient
44
+ from control_zero.llm.ollama import GovernedOllama
45
+
46
+ cz = ControlZeroClient(api_key="...")
47
+ cz.initialize()
48
+
49
+ governed = GovernedOllama(
50
+ control_zero=cz,
51
+ host="http://localhost:11434",
52
+ config=LLMGovernanceConfig(
53
+ model_policy=ModelPolicy(
54
+ allowed_models=["llama3.2", "mistral"]
55
+ )
56
+ )
57
+ )
58
+
59
+ response = governed.chat(
60
+ model="llama3.2",
61
+ messages=[{"role": "user", "content": "Hello!"}]
62
+ )
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ control_zero: Any,
68
+ config: Optional[LLMGovernanceConfig] = None,
69
+ user_context: Optional[Dict[str, Any]] = None,
70
+ host: Optional[str] = None,
71
+ timeout: Optional[float] = None,
72
+ ):
73
+ """
74
+ Initialize a governed Ollama client.
75
+
76
+ Args:
77
+ control_zero: Control Zero client for policy and logging
78
+ config: Governance configuration
79
+ user_context: Context about the current user
80
+ host: Ollama server host (default: http://localhost:11434)
81
+ timeout: Request timeout in seconds
82
+ """
83
+ try:
84
+ import ollama
85
+ except ImportError:
86
+ raise ImportError(
87
+ "ollama is required for Ollama support. "
88
+ "Install it with: pip install ollama"
89
+ )
90
+
91
+ # Create client with optional parameters
92
+ client_kwargs = {}
93
+ if host:
94
+ client_kwargs["host"] = host
95
+ if timeout:
96
+ client_kwargs["timeout"] = timeout
97
+
98
+ client = ollama.Client(**client_kwargs) if client_kwargs else ollama
99
+
100
+ super().__init__(client, control_zero, config, user_context)
101
+
102
+ self._host = host or "http://localhost:11434"
103
+
104
+ @property
105
+ def provider_name(self) -> str:
106
+ return "ollama"
107
+
108
+ def chat(
109
+ self,
110
+ *,
111
+ model: str,
112
+ messages: List[Dict[str, Any]],
113
+ tools: Optional[List[Dict[str, Any]]] = None,
114
+ stream: bool = False,
115
+ format: Optional[str] = None,
116
+ options: Optional[Dict[str, Any]] = None,
117
+ keep_alive: Optional[str] = None,
118
+ **kwargs,
119
+ ) -> Any:
120
+ """
121
+ Create a governed chat completion.
122
+
123
+ Args:
124
+ model: Model to use (e.g., "llama3.2", "mistral")
125
+ messages: List of message dicts
126
+ tools: Tool definitions (if model supports it)
127
+ stream: Whether to stream
128
+ format: Response format (e.g., "json")
129
+ options: Model options (temperature, top_p, etc.)
130
+ keep_alive: How long to keep model loaded
131
+ **kwargs: Additional parameters
132
+
133
+ Returns:
134
+ Chat response or stream iterator
135
+
136
+ Raises:
137
+ PolicyDeniedError: If request violates policy
138
+ """
139
+ start_time = time.time()
140
+
141
+ # Estimate tokens
142
+ estimated_tokens = self._estimate_message_tokens(messages)
143
+
144
+ # Prepare tools for policy check
145
+ tools_to_check = []
146
+ if tools:
147
+ tools_to_check = [{"name": t.get("function", {}).get("name", ""), "type": "function"} for t in tools]
148
+
149
+ # Run governance checks
150
+ self._pre_request_checks(
151
+ model=model,
152
+ action=GovernanceAction.CHAT_COMPLETION,
153
+ messages=messages,
154
+ functions=tools_to_check,
155
+ estimated_tokens=estimated_tokens,
156
+ )
157
+
158
+ # Process messages for governance
159
+ processed_messages = self._process_messages_for_governance(messages)
160
+
161
+ # Filter tools
162
+ filtered_tools = self._filter_tools_for_governance(tools)
163
+
164
+ # Build request
165
+ request_kwargs = {
166
+ "model": model,
167
+ "messages": processed_messages,
168
+ }
169
+
170
+ if filtered_tools:
171
+ request_kwargs["tools"] = filtered_tools
172
+ if format:
173
+ request_kwargs["format"] = format
174
+ if options:
175
+ request_kwargs["options"] = options
176
+ if keep_alive:
177
+ request_kwargs["keep_alive"] = keep_alive
178
+
179
+ request_kwargs.update(kwargs)
180
+
181
+ # Handle streaming
182
+ if stream:
183
+ return self._create_stream(request_kwargs, start_time, model)
184
+
185
+ # Make API call
186
+ try:
187
+ response = self._client.chat(**request_kwargs)
188
+ latency_ms = int((time.time() - start_time) * 1000)
189
+
190
+ # Extract metrics (Ollama provides some usage info)
191
+ prompt_tokens = response.get("prompt_eval_count", estimated_tokens)
192
+ output_tokens = response.get("eval_count", 0)
193
+ total_tokens = prompt_tokens + output_tokens
194
+
195
+ # Count tool calls
196
+ tool_call_count = 0
197
+ message = response.get("message", {})
198
+ if message.get("tool_calls"):
199
+ tool_call_count = len(message["tool_calls"])
200
+
201
+ metrics = LLMUsageMetrics(
202
+ provider="ollama",
203
+ model=model,
204
+ action=GovernanceAction.CHAT_COMPLETION,
205
+ input_tokens=prompt_tokens,
206
+ output_tokens=output_tokens,
207
+ total_tokens=total_tokens,
208
+ latency_ms=latency_ms,
209
+ estimated_cost=0.0, # Local models have no API cost
210
+ function_calls=tool_call_count,
211
+ )
212
+
213
+ self._post_request_update(metrics)
214
+ self._log_request(model, GovernanceAction.CHAT_COMPLETION, metrics)
215
+
216
+ return response
217
+
218
+ except PolicyDeniedError:
219
+ raise
220
+ except Exception as e:
221
+ latency_ms = int((time.time() - start_time) * 1000)
222
+ metrics = LLMUsageMetrics(
223
+ provider="ollama",
224
+ model=model,
225
+ action=GovernanceAction.CHAT_COMPLETION,
226
+ latency_ms=latency_ms,
227
+ )
228
+ self._log_request(
229
+ model, GovernanceAction.CHAT_COMPLETION, metrics,
230
+ status="error", error=str(e)
231
+ )
232
+ raise
233
+
234
+ def _create_stream(
235
+ self,
236
+ request_kwargs: Dict[str, Any],
237
+ start_time: float,
238
+ model: str,
239
+ ) -> Iterator[Any]:
240
+ """Create a governed streaming response."""
241
+ request_kwargs["stream"] = True
242
+ total_tokens = 0
243
+
244
+ try:
245
+ stream = self._client.chat(**request_kwargs)
246
+
247
+ for chunk in stream:
248
+ total_tokens += 1
249
+ yield chunk
250
+
251
+ latency_ms = int((time.time() - start_time) * 1000)
252
+
253
+ metrics = LLMUsageMetrics(
254
+ provider="ollama",
255
+ model=model,
256
+ action=GovernanceAction.CHAT_COMPLETION,
257
+ total_tokens=total_tokens,
258
+ latency_ms=latency_ms,
259
+ estimated_cost=0.0,
260
+ )
261
+
262
+ self._post_request_update(metrics)
263
+ self._log_request(model, GovernanceAction.CHAT_COMPLETION, metrics)
264
+
265
+ except Exception as e:
266
+ latency_ms = int((time.time() - start_time) * 1000)
267
+ metrics = LLMUsageMetrics(
268
+ provider="ollama",
269
+ model=model,
270
+ action=GovernanceAction.CHAT_COMPLETION,
271
+ latency_ms=latency_ms,
272
+ )
273
+ self._log_request(
274
+ model, GovernanceAction.CHAT_COMPLETION, metrics,
275
+ status="error", error=str(e)
276
+ )
277
+ raise
278
+
279
+ def generate(
280
+ self,
281
+ *,
282
+ model: str,
283
+ prompt: str,
284
+ stream: bool = False,
285
+ format: Optional[str] = None,
286
+ options: Optional[Dict[str, Any]] = None,
287
+ system: Optional[str] = None,
288
+ **kwargs,
289
+ ) -> Any:
290
+ """
291
+ Generate text with governance (legacy API).
292
+
293
+ Args:
294
+ model: Model to use
295
+ prompt: Input prompt
296
+ stream: Whether to stream
297
+ format: Response format
298
+ options: Model options
299
+ system: System prompt
300
+ **kwargs: Additional parameters
301
+
302
+ Returns:
303
+ Generate response or stream iterator
304
+ """
305
+ start_time = time.time()
306
+
307
+ # Governance checks
308
+ messages = [{"role": "user", "content": prompt}]
309
+ if system:
310
+ messages.insert(0, {"role": "system", "content": system})
311
+
312
+ estimated_tokens = len(prompt) // 4
313
+
314
+ self._pre_request_checks(
315
+ model=model,
316
+ action=GovernanceAction.TEXT_COMPLETION,
317
+ messages=messages,
318
+ estimated_tokens=estimated_tokens,
319
+ )
320
+
321
+ # Process prompt for PII
322
+ processed_prompt = prompt
323
+ if self._config.content_policy.enable_pii_detection:
324
+ if self._config.content_policy.pii_action == "mask":
325
+ processed_prompt = self._mask_pii(prompt)
326
+
327
+ request_kwargs = {
328
+ "model": model,
329
+ "prompt": processed_prompt,
330
+ }
331
+
332
+ if format:
333
+ request_kwargs["format"] = format
334
+ if options:
335
+ request_kwargs["options"] = options
336
+ if system:
337
+ request_kwargs["system"] = system
338
+ if stream:
339
+ request_kwargs["stream"] = True
340
+
341
+ request_kwargs.update(kwargs)
342
+
343
+ try:
344
+ response = self._client.generate(**request_kwargs)
345
+ latency_ms = int((time.time() - start_time) * 1000)
346
+
347
+ metrics = LLMUsageMetrics(
348
+ provider="ollama",
349
+ model=model,
350
+ action=GovernanceAction.TEXT_COMPLETION,
351
+ input_tokens=response.get("prompt_eval_count", estimated_tokens),
352
+ output_tokens=response.get("eval_count", 0),
353
+ latency_ms=latency_ms,
354
+ estimated_cost=0.0,
355
+ )
356
+
357
+ self._post_request_update(metrics)
358
+ self._log_request(model, GovernanceAction.TEXT_COMPLETION, metrics)
359
+
360
+ return response
361
+
362
+ except Exception as e:
363
+ latency_ms = int((time.time() - start_time) * 1000)
364
+ metrics = LLMUsageMetrics(
365
+ provider="ollama",
366
+ model=model,
367
+ action=GovernanceAction.TEXT_COMPLETION,
368
+ latency_ms=latency_ms,
369
+ )
370
+ self._log_request(
371
+ model, GovernanceAction.TEXT_COMPLETION, metrics,
372
+ status="error", error=str(e)
373
+ )
374
+ raise
375
+
376
+ def list(self) -> Any:
377
+ """List available models (pass-through)."""
378
+ return self._client.list()
379
+
380
+ def show(self, model: str) -> Any:
381
+ """Show model info (pass-through)."""
382
+ return self._client.show(model)
383
+
384
+ def pull(self, model: str, **kwargs) -> Any:
385
+ """Pull a model (pass-through)."""
386
+ return self._client.pull(model, **kwargs)
387
+
388
+ def _filter_tools_for_governance(
389
+ self,
390
+ tools: Optional[List[Dict[str, Any]]],
391
+ ) -> Optional[List[Dict[str, Any]]]:
392
+ """Filter tools according to governance policies."""
393
+ if not tools:
394
+ return tools
395
+
396
+ policy = self._config.function_policy
397
+
398
+ if not policy.allowed_functions and not policy.denied_functions:
399
+ return tools
400
+
401
+ filtered = []
402
+ for tool in tools:
403
+ tool_name = tool.get("function", {}).get("name", "")
404
+
405
+ if policy.denied_functions:
406
+ denied = any(d.lower() in tool_name.lower() for d in policy.denied_functions)
407
+ if denied:
408
+ continue
409
+
410
+ if policy.allowed_functions:
411
+ allowed = any(a.lower() in tool_name.lower() for a in policy.allowed_functions)
412
+ if not allowed:
413
+ continue
414
+
415
+ filtered.append(tool)
416
+
417
+ return filtered if filtered else None
418
+
419
+ def _estimate_message_tokens(self, messages: List[Dict[str, Any]]) -> int:
420
+ """Estimate token count for messages."""
421
+ total_chars = sum(len(str(m.get("content", ""))) for m in messages)
422
+ return max(1, total_chars // 4)
423
+
424
+ def with_user_context(self, user_context: Dict[str, Any]) -> "GovernedOllama":
425
+ merged_context = {**self._user_context, **user_context}
426
+ return GovernedOllama(
427
+ control_zero=self._cz,
428
+ config=self._config,
429
+ user_context=merged_context,
430
+ host=self._host,
431
+ )
432
+
433
+ def with_config(self, config: LLMGovernanceConfig) -> "GovernedOllama":
434
+ return GovernedOllama(
435
+ control_zero=self._cz,
436
+ config=config,
437
+ user_context=self._user_context,
438
+ host=self._host,
439
+ )
@@ -0,0 +1,34 @@
1
+ """
2
+ Control Zero OpenAI Governance Wrapper.
3
+
4
+ This module provides governance wrappers for the OpenAI Python SDK,
5
+ enabling policy enforcement, cost tracking, and audit logging for
6
+ all OpenAI API calls.
7
+
8
+ Usage:
9
+ from control_zero import ControlZeroClient
10
+ from control_zero.llm.openai import GovernedOpenAI
11
+ from openai import OpenAI
12
+
13
+ # Initialize Control Zero
14
+ cz_client = ControlZeroClient(api_key="cz_live_xxx")
15
+ cz_client.initialize()
16
+
17
+ # Wrap OpenAI client with governance
18
+ openai_client = OpenAI()
19
+ governed = GovernedOpenAI(client=openai_client, control_zero=cz_client)
20
+
21
+ # All calls are now governed
22
+ response = governed.chat.completions.create(
23
+ model="gpt-4",
24
+ messages=[{"role": "user", "content": "Hello"}]
25
+ )
26
+ """
27
+
28
+ from control_zero.llm.openai.client import GovernedOpenAI
29
+ from control_zero.llm.openai.chat import GovernedChatCompletions
30
+
31
+ __all__ = [
32
+ "GovernedOpenAI",
33
+ "GovernedChatCompletions",
34
+ ]