veris-ai 1.8.2__py3-none-any.whl → 1.10.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.

Potentially problematic release.


This version of veris-ai might be problematic. Click here for more details.

veris_ai/README.md CHANGED
@@ -14,9 +14,10 @@ This module contains the core implementation of the Veris AI Python SDK. Each co
14
14
 
15
15
  | Module | Purpose | Key Classes/Functions | Lines |
16
16
  |--------|---------|----------------------|-------|
17
- | [`tool_mock.py`](tool_mock.py) | Function mocking & FastAPI MCP | `VerisSDK`, `@mock`, `@stub` | 327 |
18
- | [`utils.py`](utils.py) | Type utilities & JSON schema | `extract_json_schema()` | 272 |
19
- | [`logging.py`](logging.py) | Logging configuration | `setup_logging()` | 116 |
17
+ | [`tool_mock.py`](tool_mock.py) | Function mocking & FastAPI MCP | `VerisSDK`, `@mock`, `@stub`, `@spy` | 327 |
18
+ | [`utils.py`](utils.py) | Type utilities & JSON schema | `extract_json_schema()`, `convert_to_type()` | 272 |
19
+ | [`api_client.py`](api_client.py) | Centralized API client | `SimulatorAPIClient` | 62 |
20
+ | [`logging.py`](logging.py) | Tool call/response logging | `log_tool_call()`, `log_tool_response()` | 116 |
20
21
  | [`models.py`](models.py) | Data models | Type definitions | 12 |
21
22
  | [`jaeger_interface/`](jaeger_interface/) | Jaeger Query API wrapper | `JaegerClient` | See module README |
22
23
 
@@ -26,16 +27,16 @@ This module contains the core implementation of the Veris AI Python SDK. Each co
26
27
 
27
28
  ### Mock Flow
28
29
  1. **Decoration**: `@veris.mock()` captures function metadata
29
- 2. **Environment Check**: `ENV=simulation` determines behavior
30
- 3. **API Call**: POST to `{VERIS_ENDPOINT_URL}/api/v2/tool_mock`
30
+ 2. **Session Check**: Presence of session ID determines behavior
31
+ 3. **API Call**: POST to VERIS API endpoint `/v2/tool_mock` (auto-configured)
31
32
  4. **Type Conversion**: Response converted using `extract_json_schema()`
32
33
 
33
34
  **Implementation**: [`tool_mock.py:200-250`](tool_mock.py)
34
35
 
35
36
  ### Spy Flow
36
- 1. **Pre-execution Logging**: Call details sent to `/api/v2/log_tool_call`
37
+ 1. **Pre-execution Logging**: Call details sent to `/v2/simulations/{session_id}/log_tool_call`
37
38
  2. **Function Execution**: Original function runs normally
38
- 3. **Post-execution Logging**: Response sent to `/api/v2/log_tool_response`
39
+ 3. **Post-execution Logging**: Response sent to `/v2/simulations/{session_id}/log_tool_response`
39
40
 
40
41
  **Implementation**: [`tool_mock.py:250-300`](tool_mock.py)
41
42
 
@@ -44,11 +45,11 @@ This module contains the core implementation of the Veris AI Python SDK. Each co
44
45
 
45
46
  **Semantic Tag**: `module-config`
46
47
 
47
- Environment variables are processed in [`tool_mock.py`](tool_mock.py):
48
+ Environment variables are processed in [`api_client.py`](api_client.py):
48
49
 
49
- - `VERIS_ENDPOINT_URL`: Mock server endpoint
50
- - `VERIS_MOCK_TIMEOUT`: Request timeout (default: 90s)
51
- - `ENV`: Set to `"simulation"` for mock mode
50
+ - `VERIS_API_KEY`: API authentication key (optional, but recommended)
51
+ - `VERIS_MOCK_TIMEOUT`: Request timeout (default: 90s)
52
+ - `VERIS_API_URL`: Override default API endpoint (rarely needed - defaults to production)
52
53
 
53
54
  ### Observability (OTLP / Logfire)
54
55
 
veris_ai/__init__.py CHANGED
@@ -6,42 +6,31 @@ __version__ = "0.1.0"
6
6
 
7
7
  # Import lightweight modules that only use base dependencies
8
8
  from .jaeger_interface import JaegerClient
9
- from .models import ResponseExpectation
9
+ from .models import ResponseExpectation, ToolCallOptions
10
10
  from .observability import init_observability, instrument_fastapi_app
11
11
  from .tool_mock import veris
12
12
 
13
13
  # Lazy import for modules with heavy dependencies
14
- _veris_runner = None
14
+ _Runner = None
15
15
  _VerisConfig = None
16
16
 
17
17
 
18
- def veris_runner(*args: Any, **kwargs: Any) -> Any: # noqa: ANN401
19
- """Lazy loader for the veris_runner function from agents_wrapper.
20
-
21
- This function wraps OpenAI agents Runner.run to intercept tool calls
22
- through the Veris SDK's mocking infrastructure.
23
-
24
- This function requires the 'agents' extra dependencies:
25
- pip install veris-ai[agents]
26
- """
27
- global _veris_runner # noqa: PLW0603
28
- if _veris_runner is None:
29
- try:
30
- from .agents_wrapper import veris_runner as _veris_runner_impl # noqa: PLC0415
31
-
32
- _veris_runner = _veris_runner_impl
33
- except ImportError as e:
34
- error_msg = (
35
- "The 'veris_runner' function requires additional dependencies. "
36
- "Please install them with: pip install veris-ai[agents]"
37
- )
38
- raise ImportError(error_msg) from e
39
- return _veris_runner(*args, **kwargs)
40
-
41
-
42
18
  def __getattr__(name: str) -> Any: # noqa: ANN401
43
- """Lazy load VerisConfig class."""
44
- global _VerisConfig # noqa: PLW0603
19
+ """Lazy load Runner and VerisConfig classes."""
20
+ global _Runner, _VerisConfig # noqa: PLW0603
21
+ if name == "Runner":
22
+ if _Runner is None:
23
+ try:
24
+ from .agents_wrapper import Runner as _Runner_impl # noqa: PLC0415
25
+
26
+ _Runner = _Runner_impl
27
+ except ImportError as e:
28
+ error_msg = (
29
+ "The 'Runner' class requires additional dependencies. "
30
+ "Please install them with: pip install veris-ai[agents]"
31
+ )
32
+ raise ImportError(error_msg) from e
33
+ return _Runner
45
34
  if name == "VerisConfig":
46
35
  if _VerisConfig is None:
47
36
  try:
@@ -63,8 +52,9 @@ __all__ = [
63
52
  "veris",
64
53
  "JaegerClient",
65
54
  "ResponseExpectation",
55
+ "ToolCallOptions",
66
56
  "init_observability",
67
57
  "instrument_fastapi_app",
68
- "veris_runner",
58
+ "Runner",
69
59
  "VerisConfig",
70
60
  ]
@@ -1,14 +1,16 @@
1
1
  """OpenAI Agents wrapper for automatic tool mocking via Veris SDK."""
2
2
 
3
+ import json
3
4
  import logging
4
5
  from collections.abc import Callable
5
6
  from typing import Any
6
7
 
7
- from agents import RunContextWrapper, RunResult, Runner
8
+ from agents import RunContextWrapper, RunResult, Runner as OpenAIRunner
8
9
  from pydantic import BaseModel
9
10
 
10
11
  from veris_ai import veris
11
12
  from veris_ai.tool_mock import mock_tool_call
13
+ from veris_ai.models import ToolCallOptions
12
14
 
13
15
  logger = logging.getLogger(__name__)
14
16
 
@@ -16,6 +18,7 @@ logger = logging.getLogger(__name__)
16
18
  def _wrap(
17
19
  include_tools: list[str] | None = None,
18
20
  exclude_tools: list[str] | None = None,
21
+ tool_options: dict[str, ToolCallOptions] | None = None,
19
22
  ) -> Callable:
20
23
  """Private wrapper for OpenAI agents Runner to intercept tool calls through Veris SDK.
21
24
 
@@ -25,6 +28,7 @@ def _wrap(
25
28
  Args:
26
29
  include_tools: Optional list of tool names to intercept (only these if provided)
27
30
  exclude_tools: Optional list of tool names to NOT intercept (these run normally)
31
+ tool_options: Optional per-tool configuration for mocking behavior
28
32
 
29
33
  Returns:
30
34
  A wrapped Runner.run function
@@ -36,6 +40,8 @@ def _wrap(
36
40
  if include_tools and exclude_tools:
37
41
  msg = "Cannot specify both include_tools and exclude_tools"
38
42
  raise ValueError(msg)
43
+ if not tool_options:
44
+ tool_options = {}
39
45
 
40
46
  def wrapped_run_func(run_func: Callable) -> Callable:
41
47
  """Inner wrapper that takes the actual Runner.run function."""
@@ -68,7 +74,7 @@ def _wrap(
68
74
  original_on_invoke = tool.on_invoke_tool
69
75
 
70
76
  def make_wrapped_on_invoke_tool(
71
- tool_id: int, orig_invoke: Callable
77
+ tool_id: int, orig_invoke: Callable, tool_name_inner: str
72
78
  ) -> Callable:
73
79
  """Create a wrapped on_invoke_tool with proper closure."""
74
80
 
@@ -81,7 +87,10 @@ def _wrap(
81
87
  if the_func and session_id:
82
88
  # mock_tool_call is synchronous, don't await it
83
89
  return mock_tool_call(
84
- the_func, session_id, parameters, None
90
+ the_func,
91
+ session_id,
92
+ json.loads(parameters),
93
+ tool_options.get(tool_name_inner),
85
94
  )
86
95
  # Fall back to original if we couldn't extract the function
87
96
  return await orig_invoke(ctx, parameters)
@@ -89,7 +98,7 @@ def _wrap(
89
98
  return wrapped_on_invoke_tool
90
99
 
91
100
  tool.on_invoke_tool = make_wrapped_on_invoke_tool(
92
- id(tool), original_on_invoke
101
+ id(tool), original_on_invoke, tool_name
93
102
  )
94
103
  return await run_func(starting_agent, input_text, **kwargs)
95
104
 
@@ -225,59 +234,78 @@ def _find_user_function_in_closure(closure: tuple) -> Callable | None:
225
234
 
226
235
 
227
236
  class VerisConfig(BaseModel):
228
- """Configuration for the Veris SDK."""
237
+ """Configuration for the Veris SDK Runner.
238
+
239
+ Attributes:
240
+ include_tools: Optional list of tool names to intercept (only these if provided)
241
+ exclude_tools: Optional list of tool names to NOT intercept (these run normally)
242
+ tool_options: Optional per-tool configuration for mocking behavior
243
+ """
229
244
 
230
245
  include_tools: list[str] | None = None
231
246
  exclude_tools: list[str] | None = None
247
+ tool_options: dict[str, ToolCallOptions] | None = None
232
248
 
233
249
 
234
- def veris_runner(
235
- starting_agent: Any, # noqa: ANN401
236
- input_text: str,
237
- veris_config: VerisConfig | None = None,
238
- **kwargs: Any, # noqa: ANN401
239
- ) -> RunResult: # noqa: ANN401
240
- """Veris-wrapped version of OpenAI agents Runner.run.
241
-
242
- This function wraps the OpenAI agents Runner.run to intercept tool calls
243
- and route them through the Veris SDK's mocking infrastructure. It can be
244
- used as a drop-in replacement for Runner.run with an additional veris_config parameter.
250
+ class Runner(OpenAIRunner):
251
+ """Veris-enhanced Runner that extends OpenAI's Runner with tool interception.
245
252
 
246
- Args:
247
- starting_agent: The OpenAI agent to run
248
- input_text: The input text to process
249
- veris_config: Optional configuration for Veris SDK tool interception
250
- **kwargs: Additional arguments to pass to Runner.run
251
-
252
- Returns:
253
- The result from Runner.run
253
+ This class extends the OpenAI agents Runner to intercept tool calls
254
+ and route them through the Veris SDK's mocking infrastructure.
254
255
 
255
256
  Example:
256
257
  ```python
257
- from veris_ai import veris_runner, VerisConfig
258
+ from veris_ai import Runner, VerisConfig
258
259
  from agents import Agent, FunctionTool
259
260
 
260
261
  # Define your agent with tools
261
262
  agent = Agent(...)
262
263
 
263
- # Use veris_runner instead of Runner.run
264
- result = await veris_runner(agent, "Process this input")
264
+ # Use Veris Runner instead of OpenAI Runner
265
+ result = await Runner.run(agent, "Process this input")
265
266
 
266
- # Or with specific tool configuration
267
+ # Or with configuration
267
268
  config = VerisConfig(include_tools=["calculator", "search"])
268
- result = await veris_runner(agent, "Calculate 2+2", veris_config=config)
269
+ result = await Runner.run(agent, "Calculate 2+2", veris_config=config)
269
270
  ```
270
271
  """
271
272
 
272
- # Extract config values
273
- include_tools = None
274
- exclude_tools = None
275
- if veris_config:
276
- include_tools = veris_config.include_tools
277
- exclude_tools = veris_config.exclude_tools
278
-
279
- # Create the wrapped version of Runner.run with the config
280
- wrapped_run = _wrap(include_tools=include_tools, exclude_tools=exclude_tools)(Runner.run)
281
-
282
- # Execute the wrapped run function
283
- return wrapped_run(starting_agent, input_text, **kwargs)
273
+ @classmethod
274
+ async def run(
275
+ cls,
276
+ starting_agent: Any, # noqa: ANN401
277
+ input: Any, # noqa: ANN401, A002
278
+ veris_config: VerisConfig | None = None,
279
+ **kwargs: Any, # noqa: ANN401
280
+ ) -> RunResult: # noqa: ANN401
281
+ """Run an agent with Veris tool interception.
282
+
283
+ This method overrides the OpenAI Runner.run to apply tool interception
284
+ based on the provided configuration.
285
+
286
+ Args:
287
+ starting_agent: The OpenAI agent to run
288
+ input: The input text/messages to process
289
+ veris_config: Optional configuration for tool interception
290
+ **kwargs: Additional arguments to pass to the base Runner.run
291
+
292
+ Returns:
293
+ The result from the agent execution
294
+ """
295
+ # Use provided config or create default
296
+ config = veris_config or VerisConfig()
297
+
298
+ # Validate configuration
299
+ if config.include_tools and config.exclude_tools:
300
+ msg = "Cannot specify both include_tools and exclude_tools"
301
+ raise ValueError(msg)
302
+
303
+ # Apply the wrapping logic
304
+ wrapped_run = _wrap(
305
+ include_tools=config.include_tools,
306
+ exclude_tools=config.exclude_tools,
307
+ tool_options=config.tool_options or {},
308
+ )(OpenAIRunner.run)
309
+
310
+ # Execute the wrapped run function
311
+ return await wrapped_run(starting_agent, input, **kwargs)
veris_ai/models.py CHANGED
@@ -19,4 +19,4 @@ class ToolCallOptions(BaseModel):
19
19
 
20
20
  response_expectation: ResponseExpectation = ResponseExpectation.AUTO
21
21
  cache_response: bool = False
22
- mode: Literal["tool", "function", "spy"] = "tool"
22
+ mode: Literal["tool", "function"] = "tool"
veris_ai/observability.py CHANGED
@@ -28,6 +28,12 @@ def init_observability() -> None: # noqa: PLR0912
28
28
 
29
29
  logfire.configure(scrubbing=False)
30
30
  logfire.instrument_openai_agents()
31
+ try:
32
+ logfire.instrument_redis()
33
+ except Exception:
34
+ logger.warning("Failed to instrument redis")
35
+ logfire.instrument_mcp()
36
+
31
37
  except Exception as e:
32
38
  # Tracing is optional; continue without Traceloop
33
39
  msg = "Logfire not found: " + str(e)
@@ -96,25 +102,6 @@ def init_observability() -> None: # noqa: PLR0912
96
102
  msg = "OpenTelemetry not found: " + str(e)
97
103
  raise RuntimeError(msg) from e
98
104
 
99
- try:
100
- from opentelemetry.instrumentation.httpx import (
101
- HTTPXClientInstrumentor, # type: ignore[import-not-found]
102
- )
103
-
104
- HTTPXClientInstrumentor().instrument(request_hook=_log_request_headers)
105
- except Exception as e:
106
- msg = "OpenTelemetry not found: " + str(e)
107
- raise RuntimeError(msg) from e
108
-
109
- # Optionally enable MCP-specific spans
110
- try:
111
- from opentelemetry.instrumentation.mcp import McpInstrumentor # type: ignore[import-not-found]
112
-
113
- McpInstrumentor().instrument()
114
- except Exception as e:
115
- msg = "OpenTelemetry not found: " + str(e)
116
- raise RuntimeError(msg) from e
117
-
118
105
 
119
106
  def instrument_fastapi_app(app: FastAPI) -> None:
120
107
  """Instrument a FastAPI app so inbound HTTP requests continue W3C traces.
@@ -122,10 +109,6 @@ def instrument_fastapi_app(app: FastAPI) -> None:
122
109
  Safe to call even if the fastapi instrumentation package is not installed.
123
110
  """
124
111
 
125
- try:
126
- from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor # type: ignore[import-not-found]
112
+ import logfire
127
113
 
128
- FastAPIInstrumentor.instrument_app(app)
129
- except Exception as e:
130
- msg = "OpenTelemetry not found: " + str(e)
131
- raise RuntimeError(msg) from e
114
+ logfire.instrument_fastapi(app)
veris_ai/tool_mock.py CHANGED
@@ -5,6 +5,7 @@ from collections.abc import Callable
5
5
  from contextlib import suppress
6
6
  from contextvars import ContextVar
7
7
  from functools import wraps
8
+ import tenacity
8
9
  from typing import (
9
10
  Any,
10
11
  Literal,
@@ -13,10 +14,6 @@ from typing import (
13
14
  )
14
15
 
15
16
 
16
- from veris_ai.logging import (
17
- log_tool_call,
18
- log_tool_response,
19
- )
20
17
  from veris_ai.models import ResponseExpectation, ToolCallOptions
21
18
  from veris_ai.api_client import get_api_client
22
19
  from veris_ai.utils import convert_to_type, extract_json_schema, get_function_parameters
@@ -173,6 +170,9 @@ class VerisSDK:
173
170
  """Async wrapper."""
174
171
  session_id = _session_id_context.get()
175
172
  if not session_id:
173
+ logger.info(
174
+ f"No session ID found, executing original function: {func.__name__}"
175
+ )
176
176
  return await func(*args, **kwargs)
177
177
  parameters = get_function_parameters(func, args, kwargs)
178
178
  return mock_tool_call(
@@ -190,6 +190,9 @@ class VerisSDK:
190
190
  """Sync wrapper."""
191
191
  session_id = _session_id_context.get()
192
192
  if not session_id:
193
+ logger.info(
194
+ f"No session ID found, executing original function: {func.__name__}"
195
+ )
193
196
  return func(*args, **kwargs)
194
197
  parameters = get_function_parameters(func, args, kwargs)
195
198
  return mock_tool_call(
@@ -217,7 +220,9 @@ class VerisSDK:
217
220
  **kwargs: Any, # noqa: ANN401
218
221
  ) -> object:
219
222
  if not self.session_id:
220
- # If not in simulation mode, execute the original function
223
+ logger.info(
224
+ f"No session ID found, executing original function: {func.__name__}"
225
+ )
221
226
  return await func(*args, **kwargs)
222
227
  logger.info(f"Stubbing function: {func.__name__}")
223
228
  return return_value
@@ -225,7 +230,9 @@ class VerisSDK:
225
230
  @wraps(func)
226
231
  def sync_wrapper(*args: tuple[object, ...], **kwargs: Any) -> object: # noqa: ANN401
227
232
  if not self.session_id:
228
- # If not in simulation mode, execute the original function
233
+ logger.info(
234
+ f"No session ID found, executing original function: {func.__name__}"
235
+ )
229
236
  return func(*args, **kwargs)
230
237
  logger.info(f"Stubbing function: {func.__name__}")
231
238
  return return_value
@@ -236,10 +243,15 @@ class VerisSDK:
236
243
  return decorator
237
244
 
238
245
 
246
+ @tenacity.retry(
247
+ stop=tenacity.stop_after_attempt(3),
248
+ wait=tenacity.wait_exponential(multiplier=1, min=4, max=10),
249
+ reraise=True,
250
+ )
239
251
  def mock_tool_call(
240
252
  func: Callable,
241
253
  session_id: str,
242
- parameters: str,
254
+ parameters: dict[str, dict[str, str]],
243
255
  options: ToolCallOptions | None = None,
244
256
  ) -> object:
245
257
  """Mock tool call."""
@@ -262,7 +274,7 @@ def mock_tool_call(
262
274
  "cache_response": bool(options.cache_response),
263
275
  "tool_call": {
264
276
  "function_name": func.__name__,
265
- "parameters": json.loads(parameters),
277
+ "parameters": parameters,
266
278
  "return_type": json.dumps(extract_json_schema(return_type_obj)),
267
279
  "docstring": docstring,
268
280
  },
@@ -278,4 +290,41 @@ def mock_tool_call(
278
290
  return convert_to_type(mock_result, return_type_obj)
279
291
 
280
292
 
293
+ def log_tool_call(
294
+ session_id: str,
295
+ function_name: str,
296
+ parameters: dict[str, dict[str, str]],
297
+ docstring: str,
298
+ ) -> None:
299
+ """Log tool call synchronously to the VERIS logging endpoint."""
300
+ api_client = get_api_client()
301
+ endpoint = api_client.get_log_tool_call_endpoint(session_id)
302
+ payload = {
303
+ "function_name": function_name,
304
+ "parameters": parameters,
305
+ "docstring": docstring,
306
+ }
307
+ try:
308
+ api_client.post(endpoint, payload)
309
+ logger.debug(f"Tool call logged for {function_name}")
310
+ except Exception as e:
311
+ logger.warning(f"Failed to log tool call for {function_name}: {e}")
312
+
313
+
314
+ def log_tool_response(session_id: str, response: Any) -> None: # noqa: ANN401
315
+ """Log tool response synchronously to the VERIS logging endpoint."""
316
+ api_client = get_api_client()
317
+ endpoint = api_client.get_log_tool_response_endpoint(session_id)
318
+
319
+ payload = {
320
+ "response": json.dumps(response, default=str),
321
+ }
322
+
323
+ try:
324
+ api_client.post(endpoint, payload)
325
+ logger.debug("Tool response logged")
326
+ except Exception as e:
327
+ logger.warning(f"Failed to log tool response: {e}")
328
+
329
+
281
330
  veris = VerisSDK()
veris_ai/utils.py CHANGED
@@ -1,10 +1,19 @@
1
1
  import inspect
2
- import json
3
2
  import sys
4
3
  import types
5
4
  import typing
6
5
  from contextlib import suppress
7
- from typing import Any, ForwardRef, Literal, NotRequired, Required, Union, get_args, get_origin
6
+ from typing import (
7
+ Any,
8
+ ForwardRef,
9
+ Literal,
10
+ NotRequired,
11
+ Required,
12
+ Union,
13
+ get_args,
14
+ get_origin,
15
+ get_type_hints,
16
+ )
8
17
  from collections.abc import Callable
9
18
 
10
19
  from pydantic import BaseModel
@@ -279,12 +288,18 @@ def extract_json_schema(target_type: Any) -> dict: # noqa: PLR0911, PLR0912, C9
279
288
 
280
289
  def get_function_parameters(
281
290
  func: Callable, args: tuple[object, ...], kwargs: dict[str, object]
282
- ) -> str:
291
+ ) -> dict[str, dict[str, str]]:
283
292
  """Get the parameters for a function."""
293
+ params_info = {}
284
294
  sig = inspect.signature(func)
285
295
  bound_args = sig.bind(*args, **kwargs)
286
296
  bound_args.apply_defaults()
287
297
  _ = bound_args.arguments.pop("ctx", None)
288
298
  _ = bound_args.arguments.pop("self", None)
289
299
  _ = bound_args.arguments.pop("cls", None)
290
- return json.dumps(bound_args.arguments)
300
+ for param_name, param_value in bound_args.arguments.items():
301
+ params_info[param_name] = {
302
+ "value": str(param_value),
303
+ "type": str(get_type_hints(func).get(param_name, Any)),
304
+ }
305
+ return params_info
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: veris-ai
3
- Version: 1.8.2
3
+ Version: 1.10.0
4
4
  Summary: A Python package for Veris AI tools
5
5
  Project-URL: Homepage, https://github.com/veris-ai/veris-python-sdk
6
6
  Project-URL: Bug Tracker, https://github.com/veris-ai/veris-python-sdk/issues
@@ -20,6 +20,7 @@ Requires-Dist: opentelemetry-instrumentation>=0.55b1
20
20
  Requires-Dist: opentelemetry-sdk>=1.34.1
21
21
  Requires-Dist: pydantic>=2.0.0
22
22
  Requires-Dist: requests>=2.31.0
23
+ Requires-Dist: tenacity>=9.1.2
23
24
  Provides-Extra: agents
24
25
  Requires-Dist: openai-agents>=0.0.1; extra == 'agents'
25
26
  Provides-Extra: dev
@@ -47,7 +48,7 @@ A Python package for Veris AI tools with simulation capabilities and FastAPI MCP
47
48
  ## Quick Reference
48
49
 
49
50
  **Purpose**: Tool mocking, tracing, and FastAPI MCP integration for AI agent development
50
- **Core Components**: [`tool_mock`](#function-mocking) • [`api_client`](src/veris_ai/api_client.py) • [`observability`](#sdk-observability-helpers) • [`fastapi_mcp`](#fastapi-mcp-integration) • [`jaeger_interface`](#jaeger-trace-interface)
51
+ **Core Components**: [`tool_mock`](#function-mocking) • [`api_client`](src/veris_ai/api_client.py) • [`observability`](#sdk-observability-helpers) • [`agents_wrapper`](#openai-agents-integration) • [`fastapi_mcp`](#fastapi-mcp-integration) • [`jaeger_interface`](#jaeger-trace-interface)
51
52
  **Deep Dive**: [`Module Architecture`](src/veris_ai/README.md) • [`Testing Guide`](tests/README.md) • [`Usage Examples`](examples/README.md)
52
53
  **Source of Truth**: Implementation details in [`src/veris_ai/`](src/veris_ai/) source code
53
54
 
@@ -58,13 +59,14 @@ A Python package for Veris AI tools with simulation capabilities and FastAPI MCP
58
59
  uv add veris-ai
59
60
 
60
61
  # With optional extras
61
- uv add "veris-ai[dev,fastapi,instrument]"
62
+ uv add "veris-ai[dev,fastapi,observability,agents]"
62
63
  ```
63
64
 
64
65
  **Installation Profiles**:
65
66
  - `dev`: Development tools (ruff, pytest, mypy)
66
67
  - `fastapi`: FastAPI MCP integration
67
68
  - `observability`: OpenTelemetry tracing
69
+ - `agents`: OpenAI agents integration
68
70
 
69
71
  ## Import Patterns
70
72
 
@@ -75,10 +77,11 @@ uv add "veris-ai[dev,fastapi,instrument]"
75
77
  from veris_ai import veris, JaegerClient
76
78
 
77
79
  # Optional features (require extras)
78
- from veris_ai import init_observability, instrument_fastapi_app # Provided by SDK observability helpers
80
+ from veris_ai import init_observability, instrument_fastapi_app # Requires observability extras
81
+ from veris_ai import Runner, VerisConfig # Requires agents extras
79
82
  ```
80
83
 
81
- **Complete Import Strategies**: See [`examples/README.md`](examples/README.md) for five different import approaches, conditional features, and integration patterns.
84
+ **Complete Import Strategies**: See [`examples/README.md`](examples/README.md) for different import approaches, conditional features, and integration patterns.
82
85
 
83
86
  ## Configuration
84
87
 
@@ -88,7 +91,6 @@ from veris_ai import init_observability, instrument_fastapi_app # Provided by S
88
91
  |----------|---------|---------|
89
92
  | `VERIS_API_KEY` | API authentication key | None |
90
93
  | `VERIS_MOCK_TIMEOUT` | Request timeout (seconds) | `90.0` |
91
- | `ENV` | Set to `"simulation"` for mock mode | Production |
92
94
 
93
95
  **Advanced Configuration** (rarely needed):
94
96
  - `VERIS_API_URL`: Override default API endpoint (defaults to production)
@@ -156,32 +158,161 @@ End-to-end propagation with the simulator:
156
158
 
157
159
  **Semantic Tag**: `tool-mocking`
158
160
 
161
+ ### Session-Based Activation
162
+
163
+ The SDK uses session-based activation to determine when to enable mocking. Choose one of these methods to set a session ID:
164
+
165
+ **Option 1: Manual Setting**
166
+ ```python
167
+ from veris_ai import veris
168
+
169
+ # Explicitly set a session ID
170
+ veris.set_session_id("your-session-id")
171
+
172
+ # Now decorated functions will use mock responses
173
+ result = await your_mocked_function()
174
+
175
+ # Clear session to disable mocking
176
+ veris.clear_session_id()
177
+ ```
178
+
179
+ **Option 2: Automatic Extraction (FastAPI MCP)**
180
+ ```python
181
+ # When using FastAPI MCP integration, session IDs are
182
+ # automatically extracted from OAuth2 bearer tokens
183
+ veris.set_fastapi_mcp(...)
184
+ # No manual session management needed
185
+ ```
186
+
187
+ **How it works internally**: Regardless of which method you use, session IDs are stored in Python context variables (`contextvars`). This ensures proper isolation between concurrent requests and automatic propagation through the call stack.
188
+
159
189
  ### Core Decorators
160
190
 
161
191
  ```python
162
192
  from veris_ai import veris
163
193
 
164
- # Mock mode: Returns simulated responses in ENV=simulation
194
+ # Mock decorator: Returns simulated responses when session ID is set
165
195
  @veris.mock()
166
196
  async def your_function(param1: str, param2: int) -> dict:
167
197
  """Function documentation for LLM context."""
168
198
  return {"result": "actual implementation"}
169
199
 
170
- # Spy mode: Executes function but logs calls/responses
200
+ # Spy decorator: Executes function and logs calls/responses
171
201
  @veris.spy()
172
202
  async def monitored_function(data: str) -> dict:
173
203
  return process_data(data)
174
204
 
175
- # Stub mode: Returns fixed value in simulation
205
+ # Stub decorator: Returns fixed value in simulation
176
206
  @veris.stub(return_value={"status": "success"})
177
207
  async def get_data() -> dict:
178
208
  return await fetch_from_api()
179
209
  ```
180
210
 
181
- **Behavior**: In simulation mode, decorators intercept calls to mock endpoints. In production, functions execute normally.
211
+ **Behavior**: When a session ID is set, decorators activate their respective behaviors (mock responses, logging, or stubbed values). Without a session ID, functions execute normally.
182
212
 
183
213
  **Implementation**: See [`src/veris_ai/tool_mock.py`](src/veris_ai/tool_mock.py) for decorator logic and API integration.
184
214
 
215
+ ## OpenAI Agents Integration
216
+
217
+ **Semantic Tag**: `openai-agents`
218
+
219
+ The SDK provides seamless integration with [OpenAI's agents library](https://github.com/openai/agents) through the `Runner` class, which extends OpenAI's Runner to intercept tool calls and route them through Veris's mocking infrastructure.
220
+
221
+ ### Installation
222
+
223
+ ```bash
224
+ # Install with agents support
225
+ uv add "veris-ai[agents]"
226
+ ```
227
+
228
+ ### Basic Usage
229
+
230
+ ```python
231
+ from veris_ai import veris, Runner, VerisConfig
232
+ from agents import Agent, function_tool
233
+
234
+ # Define your tools
235
+ @function_tool
236
+ def calculator(x: int, y: int, operation: str = "add") -> int:
237
+ """Performs arithmetic operations."""
238
+ # ... implementation ...
239
+
240
+ # Create an agent with tools
241
+ agent = Agent(
242
+ name="Assistant",
243
+ model="gpt-4",
244
+ tools=[calculator],
245
+ instructions="You are a helpful assistant.",
246
+ )
247
+
248
+ # Use Veris Runner instead of OpenAI's Runner
249
+ result = await Runner.run(agent, "Calculate 10 + 5")
250
+
251
+ # Or with configuration
252
+ config = VerisConfig(include_tools=["calculator"])
253
+ result = await Runner.run(agent, "Calculate 10 + 5", veris_config=config)
254
+ ```
255
+
256
+ ### Selective Tool Interception
257
+
258
+ Control which tools are intercepted using VerisConfig:
259
+
260
+ ```python
261
+ from veris_ai import Runner, VerisConfig
262
+
263
+ # Only intercept specific tools
264
+ config = VerisConfig(include_tools=["calculator", "search_web"])
265
+ result = await Runner.run(agent, "Process this", veris_config=config)
266
+
267
+ # Or exclude specific tools from interception
268
+ config = VerisConfig(exclude_tools=["get_weather"])
269
+ result = await Runner.run(agent, "Check weather", veris_config=config)
270
+ ```
271
+
272
+ ### Advanced Tool Configuration
273
+
274
+ Fine-tune individual tool behavior using `ToolCallOptions`:
275
+
276
+ ```python
277
+ from veris_ai import Runner, VerisConfig, ResponseExpectation, ToolCallOptions
278
+
279
+ # Configure specific tool behaviors
280
+ config = VerisConfig(
281
+ tool_options={
282
+ "calculator": ToolCallOptions(
283
+ response_expectation=ResponseExpectation.REQUIRED, # Always expect response
284
+ cache_response=True, # Cache responses for identical calls
285
+ mode="tool" # Use tool mode (default)
286
+ ),
287
+ "search_web": ToolCallOptions(
288
+ response_expectation=ResponseExpectation.NONE, # Don't wait for response
289
+ cache_response=False,
290
+ mode="spy" # Log calls but execute normally
291
+ )
292
+ }
293
+ )
294
+
295
+ result = await Runner.run(agent, "Calculate and search", veris_config=config)
296
+ ```
297
+
298
+ **ToolCallOptions Parameters**:
299
+ - `response_expectation`: Control response behavior
300
+ - `AUTO` (default): Automatically determine based on context
301
+ - `REQUIRED`: Always wait for mock response
302
+ - `NONE`: Don't wait for response
303
+ - `cache_response`: Cache responses for identical tool calls
304
+ - `mode`: Tool execution mode
305
+ - `"tool"` (default): Standard tool execution
306
+ - `"function"`: Function mode
307
+
308
+ **Key Features**:
309
+ - **Drop-in replacement**: Use `Runner` from veris_ai instead of OpenAI's Runner
310
+ - **Extends OpenAI Runner**: Inherits all functionality while adding Veris capabilities
311
+ - **Automatic session management**: Integrates with Veris session IDs
312
+ - **Selective mocking**: Include or exclude specific tools from interception
313
+
314
+ **Implementation**: See [`src/veris_ai/agents_wrapper.py`](src/veris_ai/agents_wrapper.py) for the integration logic and [`examples/openai_agents_example.py`](examples/openai_agents_example.py) for complete examples.
315
+
185
316
  ## FastAPI MCP Integration
186
317
 
187
318
  **Semantic Tag**: `fastapi-mcp`
@@ -254,7 +385,7 @@ pytest --cov=veris_ai # Test with coverage
254
385
 
255
386
  **Semantic Tag**: `module-architecture`
256
387
 
257
- **Core Modules**: `tool_mock` (mocking), `api_client` (centralized API), `jaeger_interface` (trace queries), `utils` (schema conversion)
388
+ **Core Modules**: `tool_mock` (mocking), `api_client` (centralized API), `agents_wrapper` (OpenAI agents integration), `jaeger_interface` (trace queries), `utils` (schema conversion)
258
389
 
259
390
  **Complete Architecture**: See [`src/veris_ai/README.md`](src/veris_ai/README.md) for module overview, implementation flows, and configuration details.
260
391
 
@@ -0,0 +1,16 @@
1
+ veris_ai/README.md,sha256=Ht1abGu-2_O0ZHJoJC7eAfG5WhO2CstAuV3hXPq1yAs,3875
2
+ veris_ai/__init__.py,sha256=enl_gEa6BQAjWvzCtsn_hFVJVVlJZ_dMsLL--E5W3nU,1907
3
+ veris_ai/agents_wrapper.py,sha256=N-YZ0qvIEJ8rlFZAzVKg6eWKa7I_qIt-khlg_zL-qY8,11931
4
+ veris_ai/api_client.py,sha256=Gu_ovqaW-hZ6yPee2GVconMeg_xOxlt7IKu0HJQNiD0,2192
5
+ veris_ai/models.py,sha256=xKeheSJQle2tBeJG1DsGJzMDwv24p5jECjX6RAa39n4,495
6
+ veris_ai/observability.py,sha256=eSIXmk6fpOAoWM-sDbsvzyUASh1ZwU6tRIPduy09RxY,4206
7
+ veris_ai/tool_mock.py,sha256=rU_ZwYWYIfgiOojEjmi5BdBsNzP3RphLPmMG1pyYUVw,11954
8
+ veris_ai/utils.py,sha256=hJetCiN8Bubhy0nqSoS1C2awN9cdkKuHM1v7YhtwtTs,10066
9
+ veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
10
+ veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
11
+ veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
12
+ veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
13
+ veris_ai-1.10.0.dist-info/METADATA,sha256=SRkcA5PGLQ5eNcz-yCNuXMECuRypD6olI9fjbFff-qQ,14471
14
+ veris_ai-1.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
15
+ veris_ai-1.10.0.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
16
+ veris_ai-1.10.0.dist-info/RECORD,,
veris_ai/logging.py DELETED
@@ -1,46 +0,0 @@
1
- """Logging utilities for VERIS tool calls and responses."""
2
-
3
- import json
4
- import logging
5
- from typing import Any
6
-
7
- from veris_ai.api_client import get_api_client
8
-
9
- logger = logging.getLogger(__name__)
10
-
11
-
12
- def log_tool_call(
13
- session_id: str,
14
- function_name: str,
15
- parameters: str,
16
- docstring: str,
17
- ) -> None:
18
- """Log tool call synchronously to the VERIS logging endpoint."""
19
- api_client = get_api_client()
20
- endpoint = api_client.get_log_tool_call_endpoint(session_id)
21
- payload = {
22
- "function_name": function_name,
23
- "parameters": json.loads(parameters),
24
- "docstring": docstring,
25
- }
26
- try:
27
- api_client.post(endpoint, payload)
28
- logger.debug(f"Tool call logged for {function_name}")
29
- except Exception as e:
30
- logger.warning(f"Failed to log tool call for {function_name}: {e}")
31
-
32
-
33
- def log_tool_response(session_id: str, response: Any) -> None: # noqa: ANN401
34
- """Log tool response synchronously to the VERIS logging endpoint."""
35
- api_client = get_api_client()
36
- endpoint = api_client.get_log_tool_response_endpoint(session_id)
37
-
38
- payload = {
39
- "response": json.dumps(response, default=str),
40
- }
41
-
42
- try:
43
- api_client.post(endpoint, payload)
44
- logger.debug("Tool response logged")
45
- except Exception as e:
46
- logger.warning(f"Failed to log tool response: {e}")
@@ -1,17 +0,0 @@
1
- veris_ai/README.md,sha256=0EC-yWB8FtqAhtcE5Q204bS1JWJPIvfpp1bPDo6iwwc,3601
2
- veris_ai/__init__.py,sha256=mFEF2pHXOAXoXuBh9IOQgqpKGvzLOsy8b0vUpMRJggU,2260
3
- veris_ai/agents_wrapper.py,sha256=LmxPkOg3KLICgTrl0Bouy7enhyO4tF6_evq2cb8I_ig,10718
4
- veris_ai/api_client.py,sha256=Gu_ovqaW-hZ6yPee2GVconMeg_xOxlt7IKu0HJQNiD0,2192
5
- veris_ai/logging.py,sha256=ENduOHnc5UmzciTDZDJCZqUOXb5We-bdtwN9f9Qqwl0,1360
6
- veris_ai/models.py,sha256=TiQJuME2pGDE6EY5J8TYKNSeajaN2Xk9ISsLoIwgtgU,502
7
- veris_ai/observability.py,sha256=fDtWeUexfzaIQArE5YbWsja8Y-bcE_h0dXQWYbXbupY,4929
8
- veris_ai/tool_mock.py,sha256=L2p6Yg8S98floahDTrf6cnmDqC6Dq3YKA7Q6RRvv7BQ,10253
9
- veris_ai/utils.py,sha256=Ud4k2jKAJ6-nqSsFXIJWYrOmPGmvl5RSp36cQtgnMpg,9781
10
- veris_ai/jaeger_interface/README.md,sha256=kd9rKcE5xf3EyNaiHu0tjn-0oES9sfaK6Ih-OhhTyCM,2821
11
- veris_ai/jaeger_interface/__init__.py,sha256=KD7NSiMYRG_2uF6dOLKkGG5lNQe4K9ptEwucwMT4_aw,1128
12
- veris_ai/jaeger_interface/client.py,sha256=yJrh86wRR0Dk3Gq12DId99WogcMIVbL0QQFqVSevvlE,8772
13
- veris_ai/jaeger_interface/models.py,sha256=e64VV6IvOEFuzRUgvDAMQFyOZMRb56I-PUPZLBZ3rX0,1864
14
- veris_ai-1.8.2.dist-info/METADATA,sha256=Qw1UFcpv4cc7kpozLsUds6R8p5inAadVz0SSnYqwBQs,9924
15
- veris_ai-1.8.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
- veris_ai-1.8.2.dist-info/licenses/LICENSE,sha256=2g4i20atAgtD5einaKzhQrIB-JrPhyQgD3bC0wkHcCI,1065
17
- veris_ai-1.8.2.dist-info/RECORD,,