sondera-harness 0.6.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.
- sondera/__init__.py +111 -0
- sondera/__main__.py +4 -0
- sondera/adk/__init__.py +3 -0
- sondera/adk/analyze.py +222 -0
- sondera/adk/plugin.py +387 -0
- sondera/cli.py +22 -0
- sondera/exceptions.py +167 -0
- sondera/harness/__init__.py +6 -0
- sondera/harness/abc.py +102 -0
- sondera/harness/cedar/__init__.py +0 -0
- sondera/harness/cedar/harness.py +363 -0
- sondera/harness/cedar/schema.py +225 -0
- sondera/harness/sondera/__init__.py +0 -0
- sondera/harness/sondera/_grpc.py +354 -0
- sondera/harness/sondera/harness.py +890 -0
- sondera/langgraph/__init__.py +15 -0
- sondera/langgraph/analyze.py +543 -0
- sondera/langgraph/exceptions.py +19 -0
- sondera/langgraph/graph.py +210 -0
- sondera/langgraph/middleware.py +454 -0
- sondera/proto/google/protobuf/any_pb2.py +37 -0
- sondera/proto/google/protobuf/any_pb2.pyi +14 -0
- sondera/proto/google/protobuf/any_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/duration_pb2.py +37 -0
- sondera/proto/google/protobuf/duration_pb2.pyi +14 -0
- sondera/proto/google/protobuf/duration_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/empty_pb2.py +37 -0
- sondera/proto/google/protobuf/empty_pb2.pyi +9 -0
- sondera/proto/google/protobuf/empty_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/struct_pb2.py +47 -0
- sondera/proto/google/protobuf/struct_pb2.pyi +49 -0
- sondera/proto/google/protobuf/struct_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/timestamp_pb2.py +37 -0
- sondera/proto/google/protobuf/timestamp_pb2.pyi +14 -0
- sondera/proto/google/protobuf/timestamp_pb2_grpc.py +24 -0
- sondera/proto/google/protobuf/wrappers_pb2.py +53 -0
- sondera/proto/google/protobuf/wrappers_pb2.pyi +59 -0
- sondera/proto/google/protobuf/wrappers_pb2_grpc.py +24 -0
- sondera/proto/sondera/__init__.py +0 -0
- sondera/proto/sondera/core/__init__.py +0 -0
- sondera/proto/sondera/core/v1/__init__.py +0 -0
- sondera/proto/sondera/core/v1/primitives_pb2.py +88 -0
- sondera/proto/sondera/core/v1/primitives_pb2.pyi +259 -0
- sondera/proto/sondera/core/v1/primitives_pb2_grpc.py +24 -0
- sondera/proto/sondera/harness/__init__.py +0 -0
- sondera/proto/sondera/harness/v1/__init__.py +0 -0
- sondera/proto/sondera/harness/v1/harness_pb2.py +81 -0
- sondera/proto/sondera/harness/v1/harness_pb2.pyi +192 -0
- sondera/proto/sondera/harness/v1/harness_pb2_grpc.py +498 -0
- sondera/py.typed +0 -0
- sondera/settings.py +20 -0
- sondera/strands/__init__.py +5 -0
- sondera/strands/analyze.py +244 -0
- sondera/strands/harness.py +333 -0
- sondera/tui/__init__.py +0 -0
- sondera/tui/app.py +309 -0
- sondera/tui/screens/__init__.py +5 -0
- sondera/tui/screens/adjudication.py +184 -0
- sondera/tui/screens/agent.py +158 -0
- sondera/tui/screens/trajectory.py +158 -0
- sondera/tui/widgets/__init__.py +23 -0
- sondera/tui/widgets/agent_card.py +94 -0
- sondera/tui/widgets/agent_list.py +73 -0
- sondera/tui/widgets/recent_adjudications.py +52 -0
- sondera/tui/widgets/recent_trajectories.py +54 -0
- sondera/tui/widgets/summary.py +57 -0
- sondera/tui/widgets/tool_card.py +33 -0
- sondera/tui/widgets/violation_panel.py +72 -0
- sondera/tui/widgets/violations_list.py +78 -0
- sondera/tui/widgets/violations_summary.py +104 -0
- sondera/types.py +346 -0
- sondera_harness-0.6.0.dist-info/METADATA +323 -0
- sondera_harness-0.6.0.dist-info/RECORD +77 -0
- sondera_harness-0.6.0.dist-info/WHEEL +5 -0
- sondera_harness-0.6.0.dist-info/entry_points.txt +2 -0
- sondera_harness-0.6.0.dist-info/licenses/LICENSE +21 -0
- sondera_harness-0.6.0.dist-info/top_level.txt +1 -0
sondera/__init__.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Sondera SDK for Python - Agent governance and policy enforcement.
|
|
2
|
+
|
|
3
|
+
This SDK provides tools for integrating AI agents with the Sondera Platform
|
|
4
|
+
for policy enforcement, guardrails, and governance.
|
|
5
|
+
|
|
6
|
+
Main Components:
|
|
7
|
+
- Harness: Abstract base class for harness implementations
|
|
8
|
+
- RemoteHarness: Production harness connecting to Sondera Platform
|
|
9
|
+
- CedarPolicyEngine: Local policy-as-code engine using Cedar
|
|
10
|
+
|
|
11
|
+
Framework Integrations:
|
|
12
|
+
- sondera.langgraph: LangGraph/LangChain middleware
|
|
13
|
+
- sondera.adk: Google ADK plugin
|
|
14
|
+
- sondera.strands: Strands Agent SDK hook
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> from sondera import SonderaRemoteHarness, Agent
|
|
18
|
+
>>> harness = SonderaRemoteHarness(sondera_api_key="<YOUR_SONDERA_API_KEY>")
|
|
19
|
+
>>> agent = Agent(
|
|
20
|
+
... id="my-agent",
|
|
21
|
+
... provider_id="langchain",
|
|
22
|
+
... name="My Agent",
|
|
23
|
+
... description="A helpful assistant",
|
|
24
|
+
... instruction="Be helpful and concise",
|
|
25
|
+
... tools=[],
|
|
26
|
+
... )
|
|
27
|
+
>>> await harness.initialize(agent=agent)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from sondera.exceptions import (
|
|
31
|
+
AgentError,
|
|
32
|
+
AuthenticationError,
|
|
33
|
+
ConfigurationError,
|
|
34
|
+
ConnectionError,
|
|
35
|
+
PolicyError,
|
|
36
|
+
PolicyEvaluationError,
|
|
37
|
+
PolicyViolationError,
|
|
38
|
+
SerializationError,
|
|
39
|
+
SonderaError,
|
|
40
|
+
ToolBlockedError,
|
|
41
|
+
ToolError,
|
|
42
|
+
TrajectoryError,
|
|
43
|
+
TrajectoryNotInitializedError,
|
|
44
|
+
)
|
|
45
|
+
from sondera.harness import CedarPolicyHarness, Harness, SonderaRemoteHarness
|
|
46
|
+
from sondera.types import (
|
|
47
|
+
AdjudicatedStep,
|
|
48
|
+
AdjudicatedTrajectory,
|
|
49
|
+
Adjudication,
|
|
50
|
+
Agent,
|
|
51
|
+
Content,
|
|
52
|
+
Decision,
|
|
53
|
+
Parameter,
|
|
54
|
+
PolicyEngineMode,
|
|
55
|
+
PromptContent,
|
|
56
|
+
Role,
|
|
57
|
+
SourceCode,
|
|
58
|
+
Stage,
|
|
59
|
+
Tool,
|
|
60
|
+
ToolRequestContent,
|
|
61
|
+
ToolResponseContent,
|
|
62
|
+
Trajectory,
|
|
63
|
+
TrajectoryStatus,
|
|
64
|
+
TrajectoryStep,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
__version__ = "0.6.0"
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
# Harness implementations
|
|
71
|
+
"Harness",
|
|
72
|
+
"SonderaRemoteHarness",
|
|
73
|
+
"CedarPolicyHarness",
|
|
74
|
+
# Core types
|
|
75
|
+
"Agent",
|
|
76
|
+
"Tool",
|
|
77
|
+
"Parameter",
|
|
78
|
+
"SourceCode",
|
|
79
|
+
# Trajectory types
|
|
80
|
+
"Trajectory",
|
|
81
|
+
"TrajectoryStep",
|
|
82
|
+
"TrajectoryStatus",
|
|
83
|
+
"Stage",
|
|
84
|
+
"Role",
|
|
85
|
+
# Content types
|
|
86
|
+
"Content",
|
|
87
|
+
"PromptContent",
|
|
88
|
+
"ToolRequestContent",
|
|
89
|
+
"ToolResponseContent",
|
|
90
|
+
# Policy types
|
|
91
|
+
"PolicyEngineMode",
|
|
92
|
+
# Adjudication types
|
|
93
|
+
"Adjudication",
|
|
94
|
+
"AdjudicatedStep",
|
|
95
|
+
"AdjudicatedTrajectory",
|
|
96
|
+
"Decision",
|
|
97
|
+
# Exceptions
|
|
98
|
+
"SonderaError",
|
|
99
|
+
"ConfigurationError",
|
|
100
|
+
"AuthenticationError",
|
|
101
|
+
"ConnectionError",
|
|
102
|
+
"TrajectoryError",
|
|
103
|
+
"TrajectoryNotInitializedError",
|
|
104
|
+
"PolicyError",
|
|
105
|
+
"PolicyViolationError",
|
|
106
|
+
"PolicyEvaluationError",
|
|
107
|
+
"AgentError",
|
|
108
|
+
"ToolError",
|
|
109
|
+
"ToolBlockedError",
|
|
110
|
+
"SerializationError",
|
|
111
|
+
]
|
sondera/__main__.py
ADDED
sondera/adk/__init__.py
ADDED
sondera/adk/analyze.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import logging
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import get_type_hints
|
|
5
|
+
|
|
6
|
+
from google.adk import Agent as AdkAgent
|
|
7
|
+
from google.adk.tools.base_tool import BaseTool
|
|
8
|
+
from google.adk.tools.function_tool import FunctionTool
|
|
9
|
+
|
|
10
|
+
from sondera.types import Agent, Parameter, SourceCode, Tool
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _get_function_source(func: Callable) -> tuple[str, str]:
|
|
16
|
+
"""Extract source code and language from a function."""
|
|
17
|
+
try:
|
|
18
|
+
source = inspect.getsource(func)
|
|
19
|
+
return "python", source
|
|
20
|
+
except (OSError, TypeError):
|
|
21
|
+
# Source not available (e.g., built-in function)
|
|
22
|
+
return "python", f"# Source code not available for {func.__name__}"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _analyze_function_parameters(func: Callable) -> list[Parameter]:
|
|
26
|
+
"""Analyze function parameters and return Sondera format Parameters."""
|
|
27
|
+
parameters = []
|
|
28
|
+
sig = inspect.signature(func)
|
|
29
|
+
|
|
30
|
+
# Try to get type hints for better type information
|
|
31
|
+
try:
|
|
32
|
+
type_hints = get_type_hints(func)
|
|
33
|
+
except Exception:
|
|
34
|
+
type_hints = {}
|
|
35
|
+
|
|
36
|
+
for param_name, param in sig.parameters.items():
|
|
37
|
+
# Skip special parameters like tool_context that ADK injects
|
|
38
|
+
if param_name in ["tool_context", "self", "cls"]:
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
# Get parameter type
|
|
42
|
+
param_type = "Any"
|
|
43
|
+
if param.annotation != inspect.Parameter.empty:
|
|
44
|
+
if isinstance(param.annotation, type):
|
|
45
|
+
param_type = param.annotation.__name__
|
|
46
|
+
else:
|
|
47
|
+
param_type = str(param.annotation)
|
|
48
|
+
elif param_name in type_hints:
|
|
49
|
+
hint = type_hints[param_name]
|
|
50
|
+
param_type = hint.__name__ if isinstance(hint, type) else str(hint)
|
|
51
|
+
|
|
52
|
+
# Extract parameter description from docstring if available
|
|
53
|
+
description = f"Parameter {param_name}"
|
|
54
|
+
if func.__doc__:
|
|
55
|
+
# Simple extraction - could be enhanced with proper docstring parsing
|
|
56
|
+
lines = func.__doc__.split("\n")
|
|
57
|
+
for line in lines:
|
|
58
|
+
if param_name in line:
|
|
59
|
+
description = line.strip()
|
|
60
|
+
break
|
|
61
|
+
|
|
62
|
+
parameters.append(
|
|
63
|
+
Parameter(name=param_name, description=description, type=param_type)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return parameters
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _get_function_return_type(func: Callable) -> str:
|
|
70
|
+
"""Extract the return type from a function."""
|
|
71
|
+
sig = inspect.signature(func)
|
|
72
|
+
if sig.return_annotation != inspect.Signature.empty:
|
|
73
|
+
if isinstance(sig.return_annotation, type):
|
|
74
|
+
return sig.return_annotation.__name__
|
|
75
|
+
else:
|
|
76
|
+
return str(sig.return_annotation)
|
|
77
|
+
|
|
78
|
+
# Try type hints as fallback
|
|
79
|
+
try:
|
|
80
|
+
type_hints = get_type_hints(func)
|
|
81
|
+
if "return" in type_hints:
|
|
82
|
+
hint = type_hints["return"]
|
|
83
|
+
if isinstance(hint, type):
|
|
84
|
+
return hint.__name__
|
|
85
|
+
else:
|
|
86
|
+
return str(hint)
|
|
87
|
+
except Exception:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
return "Any"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _extract_json_schemas(func_decl) -> tuple[str | None, str | None]:
|
|
94
|
+
"""Extract parameters and response JSON schemas from a function declaration."""
|
|
95
|
+
parameters_json_schema = None
|
|
96
|
+
response_json_schema = None
|
|
97
|
+
|
|
98
|
+
if func_decl and func_decl.parameters is not None:
|
|
99
|
+
parameters_json_schema = func_decl.parameters.model_dump_json(
|
|
100
|
+
exclude_unset=True
|
|
101
|
+
)
|
|
102
|
+
if func_decl and func_decl.response is not None:
|
|
103
|
+
response_json_schema = func_decl.response.model_dump_json(exclude_unset=True)
|
|
104
|
+
|
|
105
|
+
return parameters_json_schema, response_json_schema
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _extract_source_code(obj: Callable | object, default_name: str) -> tuple[str, str]:
|
|
109
|
+
"""Extract source code from a function or tool object."""
|
|
110
|
+
# If it's a function, get source directly
|
|
111
|
+
if inspect.isfunction(obj):
|
|
112
|
+
return _get_function_source(obj)
|
|
113
|
+
|
|
114
|
+
# For BaseTool instances, try to find source from methods
|
|
115
|
+
language = "python"
|
|
116
|
+
source_code = f"# {obj.__class__.__name__} instance: {default_name}"
|
|
117
|
+
for method_name in ["run_async", "run", "execute", "__call__"]:
|
|
118
|
+
if hasattr(obj, method_name):
|
|
119
|
+
try:
|
|
120
|
+
method = getattr(obj, method_name)
|
|
121
|
+
source_code = inspect.getsource(method)
|
|
122
|
+
break
|
|
123
|
+
except Exception:
|
|
124
|
+
pass
|
|
125
|
+
|
|
126
|
+
return language, source_code
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def format(
|
|
130
|
+
agent: AdkAgent, agent_name: str | None = None, agent_id: str | None = None
|
|
131
|
+
) -> Agent:
|
|
132
|
+
"""Transform the ADK agent into the Sondera Format."""
|
|
133
|
+
|
|
134
|
+
agent_name = agent_name or agent.name
|
|
135
|
+
agent_id = agent_id or agent.name
|
|
136
|
+
|
|
137
|
+
if type(agent) is not AdkAgent:
|
|
138
|
+
raise ValueError("Agent must be an ADK agent")
|
|
139
|
+
|
|
140
|
+
# Extract instruction from various ADK instruction provider types
|
|
141
|
+
if isinstance(agent.instruction, str):
|
|
142
|
+
instruction = agent.instruction
|
|
143
|
+
elif (render := getattr(agent.instruction, "render", None)) and callable(render):
|
|
144
|
+
# Handle InstructionProvider with render method
|
|
145
|
+
try:
|
|
146
|
+
instruction = render()
|
|
147
|
+
if not isinstance(instruction, str):
|
|
148
|
+
raise TypeError(
|
|
149
|
+
f"Expected render() to return str, got {type(instruction).__name__}"
|
|
150
|
+
)
|
|
151
|
+
except TypeError:
|
|
152
|
+
raise
|
|
153
|
+
except Exception:
|
|
154
|
+
instruction = str(agent.instruction)
|
|
155
|
+
elif agent.instruction is not None:
|
|
156
|
+
# Fallback for other types - convert to string
|
|
157
|
+
instruction = str(agent.instruction)
|
|
158
|
+
else:
|
|
159
|
+
instruction = ""
|
|
160
|
+
|
|
161
|
+
tools = []
|
|
162
|
+
for tool in agent.tools:
|
|
163
|
+
logger.info(f"Analyzing tool: {tool}")
|
|
164
|
+
# ADK tools can be functions or BaseTool/BaseToolset instances
|
|
165
|
+
if inspect.isfunction(tool):
|
|
166
|
+
# It's a raw function - wrap it in FunctionTool to get JSON schemas
|
|
167
|
+
func = tool
|
|
168
|
+
tool_name = func.__name__
|
|
169
|
+
tool_description = func.__doc__ or f"Function {tool_name}"
|
|
170
|
+
|
|
171
|
+
# Wrap function in FunctionTool to get JSON schema declarations
|
|
172
|
+
function_tool = FunctionTool(func)
|
|
173
|
+
func_decl = function_tool._get_declaration()
|
|
174
|
+
parameters_json_schema, response_json_schema = _extract_json_schemas(
|
|
175
|
+
func_decl
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Analyze function signature for parameters (fallback if JSON schema unavailable)
|
|
179
|
+
parameters = _analyze_function_parameters(func)
|
|
180
|
+
response_type = _get_function_return_type(func)
|
|
181
|
+
language, source_code = _extract_source_code(func, tool_name)
|
|
182
|
+
|
|
183
|
+
elif isinstance(tool, BaseTool):
|
|
184
|
+
# It's a BaseTool instance or similar
|
|
185
|
+
tool_name = getattr(tool, "name", tool.__class__.__name__)
|
|
186
|
+
tool_description = getattr(tool, "description", f"Tool {tool_name}")
|
|
187
|
+
|
|
188
|
+
# Try to get JSON schema from tool declaration
|
|
189
|
+
func_decl = tool._get_declaration()
|
|
190
|
+
parameters_json_schema, response_json_schema = _extract_json_schemas(
|
|
191
|
+
func_decl
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
# For BaseTool instances, we may not have parameter info readily available
|
|
195
|
+
parameters = []
|
|
196
|
+
response_type = "Any"
|
|
197
|
+
language, source_code = _extract_source_code(tool, tool_name)
|
|
198
|
+
|
|
199
|
+
else:
|
|
200
|
+
raise ValueError(f"Unknown tool type: {type(tool)}")
|
|
201
|
+
|
|
202
|
+
# Create Tool object (common for both function and BaseTool paths)
|
|
203
|
+
tools.append(
|
|
204
|
+
Tool(
|
|
205
|
+
name=tool_name,
|
|
206
|
+
description=tool_description.strip(),
|
|
207
|
+
parameters=parameters,
|
|
208
|
+
response=response_type,
|
|
209
|
+
parameters_json_schema=parameters_json_schema,
|
|
210
|
+
response_json_schema=response_json_schema,
|
|
211
|
+
source=SourceCode(language=language, code=source_code),
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return Agent(
|
|
216
|
+
id=agent_id,
|
|
217
|
+
provider_id="google",
|
|
218
|
+
name=agent_name,
|
|
219
|
+
instruction=instruction,
|
|
220
|
+
description=agent.description,
|
|
221
|
+
tools=tools,
|
|
222
|
+
)
|