5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
297 lines
11 KiB
Python
297 lines
11 KiB
Python
import typing
|
|
|
|
from opik.rest_api import client as rest_client
|
|
from opik.rest_api import core as rest_api_core
|
|
from opik.rest_api.core.request_options import RequestOptions
|
|
from opik.rest_api.types.agent_blueprint_write import AgentBlueprintWrite
|
|
from opik.rest_api.types.agent_config_env import AgentConfigEnv
|
|
from opik.rest_api.types.agent_config_value_write import AgentConfigValueWrite
|
|
from opik.api_objects import rest_helpers
|
|
from opik import id_helpers
|
|
|
|
from .blueprint import Blueprint
|
|
from . import cache as cache_mod, types
|
|
from .. import type_helpers
|
|
|
|
|
|
class ConfigManager:
|
|
"""Project-level config entity — internal REST operations."""
|
|
|
|
def __init__(
|
|
self,
|
|
project_name: str,
|
|
rest_client_: rest_client.OpikApi,
|
|
) -> None:
|
|
self._project_name = project_name
|
|
self._rest_client = rest_client_
|
|
|
|
@property
|
|
def project_name(self) -> str:
|
|
return self._project_name
|
|
|
|
@staticmethod
|
|
def _resolve_fields_with_values(
|
|
parameters: typing.Optional[typing.Dict[str, typing.Any]],
|
|
fields_with_values: typing.Optional[typing.Dict[str, types.FieldValueSpec]],
|
|
) -> typing.Dict[str, types.FieldValueSpec]:
|
|
if fields_with_values is not None:
|
|
return fields_with_values
|
|
return {
|
|
k: types.FieldValueSpec(
|
|
python_type=type(v) if v is not None else str, value=v
|
|
)
|
|
for k, v in (parameters or {}).items()
|
|
}
|
|
|
|
def get_blueprint(
|
|
self,
|
|
*,
|
|
name: typing.Optional[str] = None,
|
|
env: typing.Optional[str] = None,
|
|
mask_id: typing.Optional[str] = None,
|
|
field_types: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
timeout_in_seconds: typing.Optional[int] = None,
|
|
) -> typing.Optional[Blueprint]:
|
|
"""Fetch a blueprint by name, environment name, or latest.
|
|
|
|
Priority: ``name`` > ``env`` > latest.
|
|
Returns ``None`` if not found.
|
|
|
|
Args:
|
|
name: Fetch the blueprint with this version name.
|
|
env: Fetch the blueprint tagged with this environment name.
|
|
mask_id: ID of a mask blueprint to overlay on the result.
|
|
field_types: Mapping of field name to Python type used
|
|
for deserialising backend values.
|
|
timeout_in_seconds: HTTP request timeout in seconds.
|
|
"""
|
|
request_options: typing.Optional[RequestOptions] = (
|
|
RequestOptions(timeout_in_seconds=timeout_in_seconds)
|
|
if timeout_in_seconds is not None
|
|
else None
|
|
)
|
|
try:
|
|
project_id = rest_helpers.resolve_project_id_by_name(
|
|
self._rest_client, self._project_name
|
|
)
|
|
if name is not None:
|
|
raw = self._rest_client.agent_configs.get_blueprint_by_name(
|
|
project_id=project_id,
|
|
name=name,
|
|
mask_id=mask_id,
|
|
request_options=request_options,
|
|
)
|
|
elif env is not None:
|
|
raw = self._rest_client.agent_configs.get_blueprint_by_env(
|
|
env_name=env,
|
|
project_id=project_id,
|
|
mask_id=mask_id,
|
|
request_options=request_options,
|
|
)
|
|
else:
|
|
raw = self._rest_client.agent_configs.get_latest_blueprint(
|
|
project_id=project_id,
|
|
mask_id=mask_id,
|
|
request_options=request_options,
|
|
)
|
|
except rest_api_core.ApiError as e:
|
|
if e.status_code == 404:
|
|
return None
|
|
raise
|
|
return Blueprint(
|
|
raw_blueprint=raw,
|
|
field_types=field_types,
|
|
rest_client_=self._rest_client,
|
|
)
|
|
|
|
def create_blueprint(
|
|
self,
|
|
parameters: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
fields_with_values: typing.Optional[
|
|
typing.Dict[str, types.FieldValueSpec]
|
|
] = None,
|
|
description: typing.Optional[str] = None,
|
|
field_types: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
) -> Blueprint:
|
|
"""Create and return the initial blueprint for this agent config (first version only).
|
|
|
|
Use this method to establish the first version of the agent config.
|
|
For subsequent updates use :meth:`update_blueprint`.
|
|
|
|
Pass either ``parameters`` (plain key-value pairs whose types are
|
|
inferred) or ``fields_with_values`` (explicit ``{key: types.FieldValueSpec(type, value)}``
|
|
mapping). If both are given ``fields_with_values`` takes precedence.
|
|
|
|
Args:
|
|
parameters: Plain ``{field_name: value}`` dict; types are inferred
|
|
via ``type(value)``.
|
|
fields_with_values: Explicit ``{field_name: types.FieldValueSpec(python_type, value)}``
|
|
mapping, bypassing type inference.
|
|
description: Human-readable description stored with the blueprint.
|
|
field_types: Mapping of field name to Python type used
|
|
when fetching back the created blueprint.
|
|
"""
|
|
resolved_fields_with_values = self._resolve_fields_with_values(
|
|
parameters, fields_with_values
|
|
)
|
|
blueprint_id = id_helpers.generate_id()
|
|
payload = _build_blueprint_payload(
|
|
resolved_fields_with_values, description, id=blueprint_id
|
|
)
|
|
self._rest_client.agent_configs.create_agent_config(
|
|
blueprint=payload,
|
|
project_name=self._project_name,
|
|
)
|
|
raw = self._rest_client.agent_configs.get_blueprint_by_id(blueprint_id)
|
|
return Blueprint(
|
|
raw_blueprint=raw,
|
|
field_types=field_types,
|
|
rest_client_=self._rest_client,
|
|
)
|
|
|
|
def update_blueprint(
|
|
self,
|
|
fields_with_values: typing.Optional[
|
|
typing.Dict[str, types.FieldValueSpec]
|
|
] = None,
|
|
description: typing.Optional[str] = None,
|
|
field_types: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
) -> Blueprint:
|
|
"""Create a new blueprint with only the supplied fields (not merged with previous).
|
|
|
|
Each call creates a new versioned snapshot containing exactly the fields
|
|
you provide in ``fields_with_values``. Fields omitted from that mapping
|
|
are **not** carried over from the previous version.
|
|
|
|
``fields_with_values`` must map each field name to a
|
|
:class:`~opik.api_objects.agent_config.types.FieldValueSpec` that
|
|
declares both the Python type and the value. For prompt fields the
|
|
value must be a :class:`~opik.api_objects.prompt.text.prompt.Prompt`
|
|
(or :class:`~opik.api_objects.prompt.chat.chat_prompt.ChatPrompt`)
|
|
instance::
|
|
|
|
config.update_blueprint(
|
|
fields_with_values={
|
|
"system_prompt": types.FieldValueSpec(
|
|
python_type=Prompt, value=my_prompt
|
|
),
|
|
"temperature": types.FieldValueSpec(
|
|
python_type=float, value=0.7
|
|
),
|
|
}
|
|
)
|
|
|
|
Args:
|
|
fields_with_values: ``{field_name: types.FieldValueSpec(python_type, value)}``
|
|
mapping. Each entry overrides that field in the new blueprint.
|
|
description: Human-readable description stored with the blueprint.
|
|
field_types: Mapping of field name to Python type used
|
|
when fetching back the created blueprint.
|
|
"""
|
|
resolved_fields_with_values = self._resolve_fields_with_values(
|
|
None, fields_with_values
|
|
)
|
|
blueprint_id = id_helpers.generate_id()
|
|
payload = _build_blueprint_payload(
|
|
resolved_fields_with_values, description, id=blueprint_id
|
|
)
|
|
self._rest_client.agent_configs.update_agent_config(
|
|
blueprint=payload,
|
|
project_name=self._project_name,
|
|
)
|
|
raw = self._rest_client.agent_configs.get_blueprint_by_id(blueprint_id)
|
|
return Blueprint(
|
|
raw_blueprint=raw,
|
|
field_types=field_types,
|
|
rest_client_=self._rest_client,
|
|
)
|
|
|
|
def set_env(self, version: str, env: str) -> None:
|
|
"""Tag a specific blueprint version with an environment name.
|
|
|
|
After tagging, ``get_blueprint(env=env)`` will return this version.
|
|
|
|
Args:
|
|
version: Version name of the blueprint to tag.
|
|
env: Environment name (e.g. ``"prod"``, ``"staging"``).
|
|
"""
|
|
project_id = rest_helpers.resolve_project_id_by_name(
|
|
self._rest_client, self._project_name
|
|
)
|
|
# Use the cached blueprint_id when available to skip a round-trip.
|
|
cached = cache_mod.get_cached_config(self._project_name, None, None, version)
|
|
blueprint_id = cached.blueprint_id
|
|
if blueprint_id is None:
|
|
blueprint = self._rest_client.agent_configs.get_blueprint_by_name(
|
|
project_id=project_id,
|
|
name=version,
|
|
)
|
|
blueprint_id = blueprint.id
|
|
self._rest_client.agent_configs.create_or_update_envs(
|
|
project_id=project_id,
|
|
envs=[AgentConfigEnv(env_name=env, blueprint_id=blueprint_id)],
|
|
)
|
|
|
|
def create_mask(
|
|
self,
|
|
parameters: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
fields_with_values: typing.Optional[
|
|
typing.Dict[str, types.FieldValueSpec]
|
|
] = None,
|
|
description: typing.Optional[str] = None,
|
|
) -> str:
|
|
"""Create a mask blueprint and return its ID.
|
|
|
|
A mask overlays a subset of fields on top of an existing blueprint.
|
|
Apply it by passing the returned ID to ``get_blueprint(mask_id=...)``.
|
|
|
|
Args:
|
|
parameters: Plain ``{field_name: value}`` dict; types are inferred
|
|
via ``type(value)``.
|
|
fields_with_values: Explicit ``{field_name: types.FieldValueSpec(python_type, value)}``
|
|
mapping, bypassing type inference.
|
|
description: Human-readable description stored with the mask.
|
|
"""
|
|
resolved_fields_with_values = self._resolve_fields_with_values(
|
|
parameters, fields_with_values
|
|
)
|
|
mask_id = id_helpers.generate_id()
|
|
payload = _build_blueprint_payload(
|
|
resolved_fields_with_values, description, id=mask_id, config_type="mask"
|
|
)
|
|
self._rest_client.agent_configs.update_agent_config(
|
|
blueprint=payload,
|
|
project_name=self._project_name,
|
|
)
|
|
return mask_id
|
|
|
|
|
|
def _build_blueprint_payload(
|
|
fields_with_values: typing.Dict[str, types.FieldValueSpec],
|
|
description: typing.Optional[str],
|
|
id: typing.Optional[str] = None,
|
|
config_type: str = "blueprint",
|
|
) -> AgentBlueprintWrite:
|
|
backend_values = []
|
|
for field_name, field_spec in fields_with_values.items():
|
|
backend_type = (
|
|
type_helpers.python_type_to_backend_type(field_spec.python_type)
|
|
if field_spec.value is not None
|
|
else "string"
|
|
)
|
|
backend_values.append(
|
|
AgentConfigValueWrite(
|
|
key=field_name,
|
|
type=backend_type,
|
|
value=type_helpers.python_value_to_backend_value(
|
|
field_spec.value, field_spec.python_type
|
|
),
|
|
)
|
|
)
|
|
return AgentBlueprintWrite(
|
|
id=id,
|
|
type=config_type,
|
|
values=backend_values,
|
|
description=description,
|
|
)
|