diff --git a/api/entrypoints/routers.py b/api/entrypoints/routers.py index bfdd6d7f9d..631eb3596c 100644 --- a/api/entrypoints/routers.py +++ b/api/entrypoints/routers.py @@ -99,7 +99,6 @@ testset_router, user_profile, variants_router, - bases_router, configs_router, health_router, permissions_router, @@ -569,12 +568,6 @@ async def lifespan(*args, **kwargs): tags=["Environments"], ) -app.include_router( - bases_router.router, - prefix="/bases", - tags=["Bases"], -) - app.include_router( configs_router.router, prefix="/configs", diff --git a/api/oss/src/apis/fastapi/testsets/router.py b/api/oss/src/apis/fastapi/testsets/router.py index ca84ac23b1..b1eed6c2b7 100644 --- a/api/oss/src/apis/fastapi/testsets/router.py +++ b/api/oss/src/apis/fastapi/testsets/router.py @@ -525,7 +525,7 @@ async def create_testset( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -555,7 +555,7 @@ async def fetch_testset( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -584,7 +584,7 @@ async def edit_testset( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -615,7 +615,7 @@ async def archive_testset( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -643,7 +643,7 @@ async def unarchive_testset( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -671,7 +671,7 @@ async def query_testsets( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -713,7 +713,7 @@ async def create_testset_variant( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -738,7 +738,7 @@ async def fetch_testset_variant( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -767,11 +767,13 @@ async def edit_testset_variant( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore - if str(testset_variant_id) not in request.state.user_id: + if str(testset_variant_id) != str( + testset_variant_edit_request.testset_variant.id + ): return TestsetVariantResponse() testset_variant = await self.testsets_service.edit_testset_variant( @@ -798,7 +800,7 @@ async def archive_testset_variant( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -826,7 +828,7 @@ async def unarchive_testset_variant( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -854,7 +856,7 @@ async def query_testset_variants( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -890,7 +892,7 @@ async def retrieve_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -944,7 +946,7 @@ async def create_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -977,7 +979,7 @@ async def fetch_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1007,7 +1009,7 @@ async def edit_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1041,7 +1043,7 @@ async def archive_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1069,7 +1071,7 @@ async def unarchive_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1183,7 +1185,7 @@ async def query_testset_revisions( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1219,7 +1221,7 @@ async def commit_testset_revision( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.EDIT_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore @@ -1260,7 +1262,7 @@ async def log_testset_revisions( if not await check_action_access( # type: ignore user_uid=request.state.user_id, project_id=request.state.project_id, - permission=Permission.EDIT_EVALUATORS, # type: ignore + permission=Permission.VIEW_TESTSETS, # type: ignore ): raise FORBIDDEN_EXCEPTION # type: ignore diff --git a/api/oss/src/routers/bases_router.py b/api/oss/src/routers/bases_router.py deleted file mode 100644 index 078e66a637..0000000000 --- a/api/oss/src/routers/bases_router.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import List, Optional - -from fastapi.responses import JSONResponse -from fastapi import Request - - -from oss.src.utils.logging import get_module_logger -from oss.src.models import converters -from oss.src.services import db_manager -from oss.src.utils.common import APIRouter, is_ee -from oss.src.models.api.api_models import BaseOutput - -if is_ee(): - from ee.src.models.shared_models import Permission - from ee.src.utils.permissions import check_action_access - - -router = APIRouter() - -log = get_module_logger(__name__) - - -@router.get("/", response_model=List[BaseOutput], operation_id="list_bases") -async def list_bases( - request: Request, - app_id: str, - base_name: Optional[str] = None, -) -> List[BaseOutput]: - """ - Retrieve a list of bases filtered by app_id and base_name. - - Args: - request (Request): The incoming request. - app_id (str): The ID of the app to filter by. - base_name (Optional[str], optional): The name of the base to filter by. Defaults to None. - - Returns: - List[BaseOutput]: A list of BaseOutput objects representing the filtered bases. - - Raises: - HTTPException: If there was an error retrieving the bases. - """ - - app = await db_manager.fetch_app_by_id(app_id=app_id) - if is_ee() and app_id is not None: - has_permission = await check_action_access( - user_uid=request.state.user_id, - project_id=str(app.project_i), - permission=Permission.VIEW_APPLICATIONS, - ) - if not has_permission: - error_msg = f"You do not have permission to perform this action. Please contact your organization admin." - log.error(error_msg) - return JSONResponse( - {"detail": error_msg}, - status_code=403, - ) - - bases = await db_manager.list_bases_for_app_id(app_id, base_name) - return [converters.base_db_to_pydantic(base) for base in bases] diff --git a/api/oss/src/routers/testset_router.py b/api/oss/src/routers/testset_router.py index 3c63a2bd38..4853dce046 100644 --- a/api/oss/src/routers/testset_router.py +++ b/api/oss/src/routers/testset_router.py @@ -203,90 +203,6 @@ async def upload_file( raise HTTPException(status_code=403, detail=e.errors()) -@router.post( - "/endpoint", response_model=TestsetSimpleResponse, operation_id="import_testset" -) -async def import_testset( - request: Request, - endpoint: str = Form(None), - testset_name: str = Form(None), - authorization: Optional[str] = None, -): - """ - Import JSON testset data from an endpoint and save it to Postgres. - - Args: - endpoint (str): An endpoint URL to import data from. - testset_name (str): the name of the testset if provided. - - Returns: - dict: The result of the import process. - """ - - if is_ee(): - has_permission = await check_action_access( - user_uid=request.state.user_id, - project_id=request.state.project_id, - permission=Permission.CREATE_TESTSET, - ) - if not has_permission: - error_msg = f"You do not have permission to perform this action. Please contact your organization admin." - log.error(error_msg) - return JSONResponse( - {"detail": error_msg}, - status_code=403, - ) - - try: - response = httpx.get( - endpoint, - timeout=10, - headers={"Authorization": authorization} if authorization else None, - ) - if response.status_code != 200: - raise HTTPException( - status_code=400, detail="Failed to fetch testset from endpoint" - ) - - # Create a document - document = { - "name": testset_name, - "csvdata": [], - } - - # Populate the document with column names and values - json_response = response.json() - - _validate_testset_limits(json_response) - - for row in json_response: - document["csvdata"].append(row) - - testset = await db_manager.create_testset( - project_id=request.state.project_id, - testset_data=document, - ) - return TestsetSimpleResponse( - id=str(testset.id), - name=document["name"], - created_at=str(testset.created_at), - ) - - except HTTPException as error: - log.error(error) - raise error - except json.JSONDecodeError as error: - log.error(error) - raise HTTPException( - status_code=400, detail="Endpoint does not return valid JSON testset data" - ) from error - except Exception as error: - log.error(error) - raise HTTPException( - status_code=500, detail="Failed to import testset from endpoint" - ) from error - - @router.post( "/", response_model=TestsetSimpleResponse, operation_id="create_legacy_testset" ) diff --git a/api/oss/src/routers/variants_router.py b/api/oss/src/routers/variants_router.py index fa01b13d4f..7d94dcf44c 100644 --- a/api/oss/src/routers/variants_router.py +++ b/api/oss/src/routers/variants_router.py @@ -291,7 +291,7 @@ async def update_variant_url(request: Request, payload: UpdateVariantURLPayload) traceback.print_exc() detail = f"Error while trying to update the app variant: {str(e)}" raise HTTPException(status_code=500, detail=detail) - except: + except Exception as e: import traceback traceback.print_exc() diff --git a/api/oss/src/services/evaluators_service.py b/api/oss/src/services/evaluators_service.py index f3bddaf66c..29a8aa2d95 100644 --- a/api/oss/src/services/evaluators_service.py +++ b/api/oss/src/services/evaluators_service.py @@ -1,7 +1,11 @@ import json +import os import re +import socket import traceback -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Set, Union +from urllib.parse import urlparse +import ipaddress import httpx import litellm @@ -36,6 +40,69 @@ log = get_module_logger(__name__) +_WEBHOOK_RESPONSE_MAX_BYTES = 1 * 1024 * 1024.0 # 1 MB + +_WEBHOOK_ALLOW_INSECURE = ( + os.getenv("AGENTA_WEBHOOK_ALLOW_INSECURE") or "true" +).lower() in {"true", "1", "t", "y", "yes", "on", "enable", "enabled"} + + +def _is_blocked_ip(ip: ipaddress._BaseAddress) -> bool: + if _WEBHOOK_ALLOW_INSECURE: + return False + return ( + ip.is_private + or ip.is_loopback + or ip.is_link_local + or ip.is_reserved + or ip.is_multicast + or ip.is_unspecified + ) + + +def _validate_webhook_url(url: str) -> None: + if not url: + raise ValueError("Webhook URL is required.") + + parsed = urlparse(url) + scheme = parsed.scheme.lower() + if scheme not in {"http", "https"}: + raise ValueError("Webhook URL must use http or https.") + if scheme == "http" and not _WEBHOOK_ALLOW_INSECURE: + raise ValueError("Webhook URL must use https.") + if not parsed.netloc: + raise ValueError("Webhook URL must include a host.") + if parsed.username or parsed.password: + raise ValueError("Webhook URL must not include credentials.") + + hostname = (parsed.hostname or "").lower() + if not hostname: + raise ValueError("Webhook URL must include a valid hostname.") + if ( + hostname in {"localhost", "localhost.localdomain"} + and not _WEBHOOK_ALLOW_INSECURE + ): + raise ValueError("Webhook URL hostname is not allowed.") + + try: + ip = ipaddress.ip_address(hostname) + if _is_blocked_ip(ip): + raise ValueError("Webhook URL resolves to a blocked IP range.") + return + except ValueError: + pass + + try: + addresses = { + ipaddress.ip_address(info[4][0]) + for info in socket.getaddrinfo(hostname, None) + } + except socket.gaierror as exc: + raise ValueError("Webhook URL hostname could not be resolved.") from exc + + if not addresses or any(_is_blocked_ip(ip) for ip in addresses): + raise ValueError("Webhook URL resolves to a blocked IP range.") + def validate_string_output( evaluator_key: str, output: Union[str, Dict[str, Any]] @@ -538,15 +605,34 @@ async def auto_webhook_test( async def webhook_test(input: EvaluatorInputInterface) -> EvaluatorOutputInterface: + webhook_url = input.settings.get("webhook_url") + + _validate_webhook_url(webhook_url) + with httpx.Client() as client: payload = { "correct_answer": input.inputs["ground_truth"], "output": input.inputs["prediction"], "inputs": input.inputs, } - response = client.post(url=input.settings["webhook_url"], json=payload) + + response = client.post( + url=webhook_url, + json=payload, + timeout=httpx.Timeout(10.0, connect=5.0), + ) response.raise_for_status() - response_data = response.json() + + content_length = response.headers.get("content-length") + + if content_length and int(content_length) > _WEBHOOK_RESPONSE_MAX_BYTES: + raise ValueError("Webhook response exceeded size limit.") + + response_bytes = response.content + if len(response_bytes) > _WEBHOOK_RESPONSE_MAX_BYTES: + raise ValueError("Webhook response exceeded size limit.") + + response_data = json.loads(response_bytes) score = response_data.get("score", None) return {"outputs": {"score": score}} diff --git a/api/pyproject.toml b/api/pyproject.toml index 74b74fb5d7..9840227516 100644 --- a/api/pyproject.toml +++ b/api/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "api" -version = "0.77.0" +version = "0.78.0" description = "Agenta API" authors = [ { name = "Mahmoud Mabrouk", email = "mahmoud@agenta.ai" }, diff --git a/docs/blog/entries/playground-ux-improvements-jan-2026.mdx b/docs/blog/entries/playground-ux-improvements-jan-2026.mdx new file mode 100644 index 0000000000..11926c26c2 --- /dev/null +++ b/docs/blog/entries/playground-ux-improvements-jan-2026.mdx @@ -0,0 +1,49 @@ +--- +title: "Playground UX Improvements" +slug: playground-ux-improvements-jan-2026 +date: 2026-01-13 +tags: [v0.73.0] +description: "See provider costs upfront, run evaluations directly from the Playground, and collapse test cases for easier navigation." +--- + +# Playground UX Improvements + +
+ +
+ +## Overview + +This release brings three quality-of-life improvements to the Playground that make testing and iterating on prompts faster and more convenient. + +## What's New + +### Provider Cost Display + +You can now see the cost per million tokens directly in the provider selection dropdown. This helps you make informed decisions about which model to use based on both capability and cost. No more switching to external pricing pages to compare costs. + +### Run Evaluations from the Playground + +You can now trigger evaluations directly from the Playground without navigating to the evaluation menu. When you're testing a prompt and want to run a full evaluation, click the evaluate button to start an evaluation run with your current configuration. This keeps you in flow when iterating on prompts. + +### Collapsible Test Cases + +Test cases in the Playground can now be collapsed. This is especially useful when working with large test sets or test cases with long inputs and outputs. Collapse completed test cases to focus on what you're working on. You can still see a preview of each test case to maintain context while navigating through your data. + + +## Getting Started + +These features are available now in the Playground. Start using them today: + +- Open the Playground and check the provider dropdown to see costs +- Click the evaluate button to run evaluations directly +- Use the collapse controls on test cases to manage your view + diff --git a/docs/blog/main.mdx b/docs/blog/main.mdx index e3d60f566e..70934b5587 100644 --- a/docs/blog/main.mdx +++ b/docs/blog/main.mdx @@ -12,6 +12,16 @@ import Image from "@theme/IdealImage"; +### [Playground UX Improvements](/changelog/playground-ux-improvements-jan-2026) + +_13 January 2026_ + +**v0.73.0** + +Three quality-of-life improvements to the Playground: You can now see provider costs per million tokens directly in the model selection dropdown. You can run evaluations directly from the Playground without navigating to the evaluation menu. And you can collapse test cases to navigate large test sets more easily. + +--- + ### [Chat Sessions in Observability](/changelog/chat-sessions-observability) _9 January 2026_ diff --git a/examples/jupyter/observability/annotate-traces-tutorial.ipynb b/examples/jupyter/observability/annotate-traces-tutorial.ipynb index 46d1446930..1530d256cc 100644 --- a/examples/jupyter/observability/annotate-traces-tutorial.ipynb +++ b/examples/jupyter/observability/annotate-traces-tutorial.ipynb @@ -134,7 +134,7 @@ " Tuple of (answer, trace_id, span_id)\n", " \"\"\"\n", " response = openai.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " messages=[\n", " {\n", " \"role\": \"system\",\n", diff --git a/examples/jupyter/observability/capture_user_feedback.ipynb b/examples/jupyter/observability/capture_user_feedback.ipynb index b623586adb..d29ba897d5 100644 --- a/examples/jupyter/observability/capture_user_feedback.ipynb +++ b/examples/jupyter/observability/capture_user_feedback.ipynb @@ -225,7 +225,7 @@ "\n", " # Generate a story about the provided topic\n", " response = client.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " messages=[\n", " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", " {\n", diff --git a/examples/jupyter/observability/quickstart.ipynb b/examples/jupyter/observability/quickstart.ipynb index 333ba7f875..07bac801f9 100644 --- a/examples/jupyter/observability/quickstart.ipynb +++ b/examples/jupyter/observability/quickstart.ipynb @@ -236,7 +236,7 @@ "@ag.instrument()\n", "def generate():\n", " response = openai.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " messages=[\n", " {\"role\": \"system\", \"content\": \"You are a helpful assistant.\"},\n", " {\"role\": \"user\", \"content\": \"Write a short story about AI Engineering.\"},\n", diff --git a/examples/jupyter/observability/trace-with-python-sdk-tutorial.ipynb b/examples/jupyter/observability/trace-with-python-sdk-tutorial.ipynb index 5834b87936..a46865b77b 100644 --- a/examples/jupyter/observability/trace-with-python-sdk-tutorial.ipynb +++ b/examples/jupyter/observability/trace-with-python-sdk-tutorial.ipynb @@ -1,597 +1,597 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Trace with Python SDK - Tutorial\n", - "\n", - "This tutorial demonstrates how to use the Agenta Python SDK to trace your LLM applications. You'll learn how to:\n", - "\n", - "- Set up tracing with the Agenta SDK\n", - "- Instrument functions and OpenAI calls automatically\n", - "- Start and end spans manually to capture internals\n", - "- Reference prompt versions in your traces\n", - "- Redact sensitive data from traces\n", - "\n", - "## What You'll Build\n", - "\n", - "We'll create a simple LLM application that:\n", - "1. Uses OpenAI auto-instrumentation to trace API calls\n", - "2. Instruments custom functions to capture workflow steps\n", - "3. Stores internal data like retrieved context\n", - "4. Links traces to deployed prompt versions\n", - "5. Redacts sensitive information from traces" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Trace with Python SDK - Tutorial\n", + "\n", + "This tutorial demonstrates how to use the Agenta Python SDK to trace your LLM applications. You'll learn how to:\n", + "\n", + "- Set up tracing with the Agenta SDK\n", + "- Instrument functions and OpenAI calls automatically\n", + "- Start and end spans manually to capture internals\n", + "- Reference prompt versions in your traces\n", + "- Redact sensitive data from traces\n", + "\n", + "## What You'll Build\n", + "\n", + "We'll create a simple LLM application that:\n", + "1. Uses OpenAI auto-instrumentation to trace API calls\n", + "2. Instruments custom functions to capture workflow steps\n", + "3. Stores internal data like retrieved context\n", + "4. Links traces to deployed prompt versions\n", + "5. Redacts sensitive information from traces" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Install Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pip install -U agenta openai opentelemetry-instrumentation-openai" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "Before using the SDK, we need to initialize it with your API keys. The SDK requires:\n", + "- **Agenta API Key**: For sending traces to Agenta\n", + "- **OpenAI API Key**: For making LLM calls\n", + "\n", + "You can get your Agenta API key from the [API Keys page](https://cloud.agenta.ai/settings?tab=apiKeys)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "os.environ[\"AGENTA_HOST\"] = (\n", + " \"https://cloud.agenta.ai/\" # Default value, change for self-hosted\n", + ")\n", + "os.environ[\"AGENTA_API_KEY\"] = \"\"\n", + "os.environ[\"OPENAI_API_KEY\"] = \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import agenta as ag\n", + "from getpass import getpass\n", + "\n", + "# Initialize the SDK with your API key\n", + "api_key = os.getenv(\"AGENTA_API_KEY\")\n", + "if not api_key:\n", + " os.environ[\"AGENTA_API_KEY\"] = getpass(\"Enter your Agenta API key: \")\n", + "\n", + "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", + "if not openai_api_key:\n", + " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter your OpenAI API key: \")\n", + "\n", + "# Initialize the SDK\n", + "ag.init()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 1: Setup Tracing with OpenAI Auto-Instrumentation\n", + "\n", + "The Agenta SDK provides two powerful mechanisms for tracing:\n", + "\n", + "1. **Auto-instrumentation**: Automatically traces third-party libraries like OpenAI\n", + "2. **Function decorators**: Manually instrument your custom functions\n", + "\n", + "Let's start by setting up OpenAI auto-instrumentation, which will capture all OpenAI API calls automatically." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from opentelemetry.instrumentation.openai import OpenAIInstrumentor\n", + "import openai\n", + "\n", + "# Instrument OpenAI to automatically trace all API calls\n", + "OpenAIInstrumentor().instrument()\n", + "\n", + "print(\"OpenAI auto-instrumentation enabled!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 2: Instrument Functions\n", + "\n", + "Now let's create a simple function and instrument it using the `@ag.instrument()` decorator. This will create a span for the function and automatically capture its inputs and outputs.\n", + "\n", + "The decorator accepts a `spankind` parameter to categorize the span. Available types include: `agent`, `chain`, `workflow`, `tool`, `embedding`, `query`, `completion`, `chat`, `rerank`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@ag.instrument(spankind=\"workflow\")\n", + "def generate_story(topic: str):\n", + " \"\"\"Generate a short story about the given topic.\"\"\"\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-5\",\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": \"You are a creative storyteller.\"},\n", + " {\"role\": \"user\", \"content\": f\"Write a short story about {topic}.\"},\n", + " ],\n", + " )\n", + " return response.choices[0].message.content\n", + "\n", + "\n", + "# Test the instrumented function\n", + "story = generate_story(\"AI Engineering\")\n", + "print(\"Generated story:\")\n", + "print(story)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 3: Starting Spans and Storing Internals\n", + "\n", + "Sometimes you need to capture intermediate data that isn't part of the function's inputs or outputs. The SDK provides two methods:\n", + "\n", + "- `ag.tracing.store_meta()`: Add metadata to a span (saved under `ag.meta`)\n", + "- `ag.tracing.store_internals()`: Store internal data (saved under `ag.data.internals`)\n", + "\n", + "Internals are especially useful because they:\n", + "1. Are searchable using plain text queries\n", + "2. Appear in the overview tab of the observability drawer\n", + "\n", + "Let's create a RAG (Retrieval-Augmented Generation) example that captures the retrieved context:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@ag.instrument(spankind=\"tool\")\n", + "def retrieve_context(query: str):\n", + " \"\"\"Simulate retrieving context from a knowledge base.\"\"\"\n", + " # In a real application, this would query a vector database\n", + " context = [\n", + " \"Agenta is an open-source LLM developer platform.\",\n", + " \"Agenta provides tools for prompt management, evaluation, and observability.\",\n", + " \"The Agenta SDK supports tracing with OpenTelemetry.\",\n", + " ]\n", + "\n", + " # Store metadata about the retrieval\n", + " ag.tracing.store_meta(\n", + " {\"retrieval_method\": \"vector_search\", \"num_results\": len(context)}\n", + " )\n", + "\n", + " return context\n", + "\n", + "\n", + "@ag.instrument(spankind=\"workflow\")\n", + "def rag_workflow(query: str):\n", + " \"\"\"Answer a question using retrieved context.\"\"\"\n", + " # Retrieve context\n", + " context = retrieve_context(query)\n", + "\n", + " # Store the retrieved context as internals\n", + " # This makes it visible in the UI and searchable\n", + " ag.tracing.store_internals({\"retrieved_context\": context})\n", + "\n", + " # Generate answer using context\n", + " context_str = \"\\n\".join(context)\n", + " prompt = f\"Answer the following question based on the context:\\n\\nContext:\\n{context_str}\\n\\nQuestion: {query}\"\n", + "\n", + " response = openai.chat.completions.create(\n", + " model=\"gpt-5\",\n", + " messages=[\n", + " {\n", + " \"role\": \"system\",\n", + " \"content\": \"You are a helpful assistant. Answer questions based only on the provided context.\",\n", + " },\n", + " {\"role\": \"user\", \"content\": prompt},\n", + " ],\n", + " )\n", + "\n", + " return response.choices[0].message.content\n", + "\n", + "\n", + "# Test the RAG workflow\n", + "answer = rag_workflow(\"What is Agenta?\")\n", + "print(\"Answer:\")\n", + "print(answer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 4: Reference Prompt Versions\n", + "\n", + "One of Agenta's powerful features is linking traces to specific prompt versions. This allows you to:\n", + "- Filter traces by application, variant, or environment\n", + "- Compare performance across different variants\n", + "- Track production behavior\n", + "\n", + "Let's create a prompt using the SDK, deploy it, and then reference it in our traces." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Create and Deploy a Prompt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from agenta.sdk.types import PromptTemplate, Message, ModelConfig\n", + "from pydantic import BaseModel\n", + "\n", + "\n", + "# Define the prompt configuration\n", + "class Config(BaseModel):\n", + " prompt: PromptTemplate\n", + "\n", + "\n", + "config = Config(\n", + " prompt=PromptTemplate(\n", + " messages=[\n", + " Message(\n", + " role=\"system\",\n", + " content=\"You are a helpful assistant that explains topics clearly.\",\n", + " ),\n", + " Message(role=\"user\", content=\"Explain {{topic}} in simple terms.\"),\n", + " ],\n", + " llm_config=ModelConfig(\n", + " model=\"gpt-5\",\n", + " max_tokens=200,\n", + " temperature=0.7,\n", + " top_p=1.0,\n", + " frequency_penalty=0.0,\n", + " presence_penalty=0.0,\n", + " ),\n", + " template_format=\"curly\",\n", + " )\n", + ")\n", + "\n", + "# Create an application and variant\n", + "app = ag.AppManager.create(\n", + " app_slug=\"topic-explainer-traced\",\n", + " template_key=\"SERVICE:completion\",\n", + ")\n", + "\n", + "print(f\"Created application: {app.app_name}\")\n", + "\n", + "# Create a variant with the prompt configuration\n", + "variant = ag.VariantManager.create(\n", + " parameters=config.model_dump(),\n", + " app_slug=\"topic-explainer-traced\",\n", + " variant_slug=\"production-variant\",\n", + ")\n", + "\n", + "print(f\"Created variant: {variant.variant_slug} (version {variant.variant_version})\")\n", + "\n", + "# Deploy to production environment\n", + "deployment = ag.DeploymentManager.deploy(\n", + " app_slug=\"topic-explainer-traced\",\n", + " variant_slug=\"production-variant\",\n", + " environment_slug=\"production\",\n", + ")\n", + "\n", + "print(f\"Deployed to {deployment.environment_slug} environment\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Reference the Prompt in Traces\n", + "\n", + "Now we'll create a function that uses the deployed prompt and links its traces to the application and environment." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@ag.instrument(spankind=\"workflow\")\n", + "def explain_topic_with_prompt(topic: str):\n", + " \"\"\"Explain a topic using the deployed prompt configuration.\"\"\"\n", + "\n", + " # Fetch the prompt configuration from production\n", + " prompt_config = ag.ConfigManager.get_from_registry(\n", + " app_slug=\"topic-explainer-traced\", environment_slug=\"production\"\n", + " )\n", + "\n", + " # Format the prompt with the topic\n", + " prompt_template = PromptTemplate(**prompt_config[\"prompt\"])\n", + " formatted_prompt = prompt_template.format(topic=topic)\n", + "\n", + " # Make the OpenAI call\n", + " response = openai.chat.completions.create(**formatted_prompt.to_openai_kwargs())\n", + "\n", + " # Link this trace to the application and environment\n", + " ag.tracing.store_refs(\n", + " {\n", + " \"application.slug\": \"topic-explainer-traced\",\n", + " \"variant.slug\": \"production-variant\",\n", + " \"environment.slug\": \"production\",\n", + " }\n", + " )\n", + "\n", + " return response.choices[0].message.content\n", + "\n", + "\n", + "# Test the function\n", + "explanation = explain_topic_with_prompt(\"machine learning\")\n", + "print(\"Explanation:\")\n", + "print(explanation)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Part 5: Redact Sensitive Data\n", + "\n", + "When working with production data, you often need to exclude sensitive information from traces. The Agenta SDK provides several ways to redact data:\n", + "\n", + "1. **Simple redaction**: Ignore all inputs/outputs\n", + "2. **Selective redaction**: Ignore specific fields\n", + "3. **Custom redaction**: Use a callback function for fine-grained control\n", + "4. **Global redaction**: Apply rules across all instrumented functions\n", + "\n", + "Let's explore these different approaches." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simple Redaction: Ignore All Inputs/Outputs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@ag.instrument(spankind=\"workflow\", ignore_inputs=True, ignore_outputs=True)\n", + "def process_sensitive_data(user_email: str, credit_card: str):\n", + " \"\"\"Process sensitive data without logging inputs/outputs.\"\"\"\n", + " # The function inputs and outputs won't be captured in the trace\n", + " result = f\"Processed data for {user_email}\"\n", + " return result\n", + "\n", + "\n", + "# This trace will not contain inputs or outputs\n", + "result = process_sensitive_data(\"user@example.com\", \"4111-1111-1111-1111\")\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Selective Redaction: Ignore Specific Fields" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@ag.instrument(\n", + " spankind=\"workflow\",\n", + " ignore_inputs=[\"api_key\", \"password\"],\n", + " ignore_outputs=[\"internal_token\"],\n", + ")\n", + "def authenticate_user(username: str, password: str, api_key: str):\n", + " \"\"\"Authenticate a user (password and api_key will be redacted).\"\"\"\n", + " # Simulate authentication\n", + " return {\n", + " \"username\": username,\n", + " \"authenticated\": True,\n", + " \"internal_token\": \"secret-token-12345\", # This will be redacted\n", + " }\n", + "\n", + "\n", + "# The trace will show username but not password or api_key\n", + "auth_result = authenticate_user(\"john_doe\", \"secret123\", \"sk-abc123\")\n", + "print(f\"Authenticated: {auth_result['authenticated']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Redaction: Use a Callback Function" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "\n", + "def redact_pii(name: str, field: str, data: dict):\n", + " \"\"\"Custom redaction function that removes PII.\"\"\"\n", + " if field == \"inputs\":\n", + " # Redact email addresses\n", + " if \"email\" in data:\n", + " data[\"email\"] = \"[REDACTED]\"\n", + " # Redact phone numbers\n", + " if \"phone\" in data:\n", + " data[\"phone\"] = \"[REDACTED]\"\n", + "\n", + " if field == \"outputs\":\n", + " # Redact any credit card patterns\n", + " if isinstance(data, dict):\n", + " for key, value in data.items():\n", + " if isinstance(value, str):\n", + " # Simple credit card pattern\n", + " data[key] = re.sub(\n", + " r\"\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\",\n", + " \"[CARD-REDACTED]\",\n", + " value,\n", + " )\n", + "\n", + " return data\n", + "\n", + "\n", + "@ag.instrument(\n", + " spankind=\"workflow\",\n", + " redact=redact_pii,\n", + " redact_on_error=False, # Don't apply redaction if it raises an error\n", + ")\n", + "def process_customer_order(name: str, email: str, phone: str, card_number: str):\n", + " \"\"\"Process a customer order with PII redaction.\"\"\"\n", + " return {\n", + " \"status\": \"processed\",\n", + " \"customer\": name,\n", + " \"payment_info\": f\"Charged card ending in {card_number[-4:]}\",\n", + " \"full_card\": card_number, # This will be redacted\n", + " }\n", + "\n", + "\n", + "# Test with sample data\n", + "order = process_customer_order(\n", + " name=\"Jane Smith\",\n", + " email=\"jane@example.com\", # Will be redacted\n", + " phone=\"555-1234\", # Will be redacted\n", + " card_number=\"4111-1111-1111-1111\", # Will be redacted in output\n", + ")\n", + "print(f\"Order status: {order['status']}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Global Redaction: Apply Rules Across All Functions\n", + "\n", + "For organization-wide policies, you can set up global redaction rules during initialization. Note: Since we already called `ag.init()`, this is just for demonstration. In a real application, you would set this during the initial setup." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Example of global redaction setup (would be done during ag.init())\n", + "from typing import Dict, Any\n", + "\n", + "\n", + "def global_redact_function(name: str, field: str, data: Dict[str, Any]):\n", + " \"\"\"Global redaction that applies to all instrumented functions.\"\"\"\n", + " # Remove any field containing 'api_key' or 'secret'\n", + " if isinstance(data, dict):\n", + " keys_to_redact = [\n", + " k for k in data.keys() if \"api_key\" in k.lower() or \"secret\" in k.lower()\n", + " ]\n", + " for key in keys_to_redact:\n", + " data[key] = \"[REDACTED]\"\n", + "\n", + " return data\n", + "\n", + "\n", + "# In production, you would initialize like this:\n", + "# ag.init(\n", + "# redact=global_redact_function,\n", + "# redact_on_error=True\n", + "# )\n", + "\n", + "print(\"Global redaction would be configured during ag.init()\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "In this tutorial, you learned how to:\n", + "\n", + "1. ✅ **Set up tracing** with the Agenta SDK and OpenAI auto-instrumentation\n", + "2. ✅ **Instrument functions** using the `@ag.instrument()` decorator\n", + "3. ✅ **Store internals and metadata** to capture intermediate data in your workflows\n", + "4. ✅ **Reference prompt versions** by creating, deploying, and linking traces to applications\n", + "5. ✅ **Redact sensitive data** using multiple approaches for privacy protection\n", + "\n", + "## Next Steps\n", + "\n", + "- Explore [distributed tracing](/observability/trace-with-python-sdk/distributed-tracing) for multi-service applications\n", + "- Learn about [cost tracking](/observability/trace-with-python-sdk/track-costs) to monitor LLM expenses\n", + "- Understand [trace annotations](/observability/trace-with-python-sdk/annotate-traces) for collecting feedback\n", + "- Check out the [Agenta UI guide](/observability/using-the-ui/filtering-traces) for filtering and analyzing traces" + ] + } + ], + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.0" + } }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Install Dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pip install -U agenta openai opentelemetry-instrumentation-openai" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Setup\n", - "\n", - "Before using the SDK, we need to initialize it with your API keys. The SDK requires:\n", - "- **Agenta API Key**: For sending traces to Agenta\n", - "- **OpenAI API Key**: For making LLM calls\n", - "\n", - "You can get your Agenta API key from the [API Keys page](https://cloud.agenta.ai/settings?tab=apiKeys)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "os.environ[\"AGENTA_HOST\"] = (\n", - " \"https://cloud.agenta.ai/\" # Default value, change for self-hosted\n", - ")\n", - "os.environ[\"AGENTA_API_KEY\"] = \"\"\n", - "os.environ[\"OPENAI_API_KEY\"] = \"\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import agenta as ag\n", - "from getpass import getpass\n", - "\n", - "# Initialize the SDK with your API key\n", - "api_key = os.getenv(\"AGENTA_API_KEY\")\n", - "if not api_key:\n", - " os.environ[\"AGENTA_API_KEY\"] = getpass(\"Enter your Agenta API key: \")\n", - "\n", - "openai_api_key = os.getenv(\"OPENAI_API_KEY\")\n", - "if not openai_api_key:\n", - " os.environ[\"OPENAI_API_KEY\"] = getpass(\"Enter your OpenAI API key: \")\n", - "\n", - "# Initialize the SDK\n", - "ag.init()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 1: Setup Tracing with OpenAI Auto-Instrumentation\n", - "\n", - "The Agenta SDK provides two powerful mechanisms for tracing:\n", - "\n", - "1. **Auto-instrumentation**: Automatically traces third-party libraries like OpenAI\n", - "2. **Function decorators**: Manually instrument your custom functions\n", - "\n", - "Let's start by setting up OpenAI auto-instrumentation, which will capture all OpenAI API calls automatically." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from opentelemetry.instrumentation.openai import OpenAIInstrumentor\n", - "import openai\n", - "\n", - "# Instrument OpenAI to automatically trace all API calls\n", - "OpenAIInstrumentor().instrument()\n", - "\n", - "print(\"OpenAI auto-instrumentation enabled!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 2: Instrument Functions\n", - "\n", - "Now let's create a simple function and instrument it using the `@ag.instrument()` decorator. This will create a span for the function and automatically capture its inputs and outputs.\n", - "\n", - "The decorator accepts a `spankind` parameter to categorize the span. Available types include: `agent`, `chain`, `workflow`, `tool`, `embedding`, `query`, `completion`, `chat`, `rerank`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@ag.instrument(spankind=\"workflow\")\n", - "def generate_story(topic: str):\n", - " \"\"\"Generate a short story about the given topic.\"\"\"\n", - " response = openai.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[\n", - " {\"role\": \"system\", \"content\": \"You are a creative storyteller.\"},\n", - " {\"role\": \"user\", \"content\": f\"Write a short story about {topic}.\"},\n", - " ],\n", - " )\n", - " return response.choices[0].message.content\n", - "\n", - "\n", - "# Test the instrumented function\n", - "story = generate_story(\"AI Engineering\")\n", - "print(\"Generated story:\")\n", - "print(story)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 3: Starting Spans and Storing Internals\n", - "\n", - "Sometimes you need to capture intermediate data that isn't part of the function's inputs or outputs. The SDK provides two methods:\n", - "\n", - "- `ag.tracing.store_meta()`: Add metadata to a span (saved under `ag.meta`)\n", - "- `ag.tracing.store_internals()`: Store internal data (saved under `ag.data.internals`)\n", - "\n", - "Internals are especially useful because they:\n", - "1. Are searchable using plain text queries\n", - "2. Appear in the overview tab of the observability drawer\n", - "\n", - "Let's create a RAG (Retrieval-Augmented Generation) example that captures the retrieved context:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@ag.instrument(spankind=\"tool\")\n", - "def retrieve_context(query: str):\n", - " \"\"\"Simulate retrieving context from a knowledge base.\"\"\"\n", - " # In a real application, this would query a vector database\n", - " context = [\n", - " \"Agenta is an open-source LLM developer platform.\",\n", - " \"Agenta provides tools for prompt management, evaluation, and observability.\",\n", - " \"The Agenta SDK supports tracing with OpenTelemetry.\",\n", - " ]\n", - "\n", - " # Store metadata about the retrieval\n", - " ag.tracing.store_meta(\n", - " {\"retrieval_method\": \"vector_search\", \"num_results\": len(context)}\n", - " )\n", - "\n", - " return context\n", - "\n", - "\n", - "@ag.instrument(spankind=\"workflow\")\n", - "def rag_workflow(query: str):\n", - " \"\"\"Answer a question using retrieved context.\"\"\"\n", - " # Retrieve context\n", - " context = retrieve_context(query)\n", - "\n", - " # Store the retrieved context as internals\n", - " # This makes it visible in the UI and searchable\n", - " ag.tracing.store_internals({\"retrieved_context\": context})\n", - "\n", - " # Generate answer using context\n", - " context_str = \"\\n\".join(context)\n", - " prompt = f\"Answer the following question based on the context:\\n\\nContext:\\n{context_str}\\n\\nQuestion: {query}\"\n", - "\n", - " response = openai.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[\n", - " {\n", - " \"role\": \"system\",\n", - " \"content\": \"You are a helpful assistant. Answer questions based only on the provided context.\",\n", - " },\n", - " {\"role\": \"user\", \"content\": prompt},\n", - " ],\n", - " )\n", - "\n", - " return response.choices[0].message.content\n", - "\n", - "\n", - "# Test the RAG workflow\n", - "answer = rag_workflow(\"What is Agenta?\")\n", - "print(\"Answer:\")\n", - "print(answer)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 4: Reference Prompt Versions\n", - "\n", - "One of Agenta's powerful features is linking traces to specific prompt versions. This allows you to:\n", - "- Filter traces by application, variant, or environment\n", - "- Compare performance across different variants\n", - "- Track production behavior\n", - "\n", - "Let's create a prompt using the SDK, deploy it, and then reference it in our traces." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Create and Deploy a Prompt" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from agenta.sdk.types import PromptTemplate, Message, ModelConfig\n", - "from pydantic import BaseModel\n", - "\n", - "\n", - "# Define the prompt configuration\n", - "class Config(BaseModel):\n", - " prompt: PromptTemplate\n", - "\n", - "\n", - "config = Config(\n", - " prompt=PromptTemplate(\n", - " messages=[\n", - " Message(\n", - " role=\"system\",\n", - " content=\"You are a helpful assistant that explains topics clearly.\",\n", - " ),\n", - " Message(role=\"user\", content=\"Explain {{topic}} in simple terms.\"),\n", - " ],\n", - " llm_config=ModelConfig(\n", - " model=\"gpt-3.5-turbo\",\n", - " max_tokens=200,\n", - " temperature=0.7,\n", - " top_p=1.0,\n", - " frequency_penalty=0.0,\n", - " presence_penalty=0.0,\n", - " ),\n", - " template_format=\"curly\",\n", - " )\n", - ")\n", - "\n", - "# Create an application and variant\n", - "app = ag.AppManager.create(\n", - " app_slug=\"topic-explainer-traced\",\n", - " template_key=\"SERVICE:completion\",\n", - ")\n", - "\n", - "print(f\"Created application: {app.app_name}\")\n", - "\n", - "# Create a variant with the prompt configuration\n", - "variant = ag.VariantManager.create(\n", - " parameters=config.model_dump(),\n", - " app_slug=\"topic-explainer-traced\",\n", - " variant_slug=\"production-variant\",\n", - ")\n", - "\n", - "print(f\"Created variant: {variant.variant_slug} (version {variant.variant_version})\")\n", - "\n", - "# Deploy to production environment\n", - "deployment = ag.DeploymentManager.deploy(\n", - " app_slug=\"topic-explainer-traced\",\n", - " variant_slug=\"production-variant\",\n", - " environment_slug=\"production\",\n", - ")\n", - "\n", - "print(f\"Deployed to {deployment.environment_slug} environment\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Reference the Prompt in Traces\n", - "\n", - "Now we'll create a function that uses the deployed prompt and links its traces to the application and environment." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@ag.instrument(spankind=\"workflow\")\n", - "def explain_topic_with_prompt(topic: str):\n", - " \"\"\"Explain a topic using the deployed prompt configuration.\"\"\"\n", - "\n", - " # Fetch the prompt configuration from production\n", - " prompt_config = ag.ConfigManager.get_from_registry(\n", - " app_slug=\"topic-explainer-traced\", environment_slug=\"production\"\n", - " )\n", - "\n", - " # Format the prompt with the topic\n", - " prompt_template = PromptTemplate(**prompt_config[\"prompt\"])\n", - " formatted_prompt = prompt_template.format(topic=topic)\n", - "\n", - " # Make the OpenAI call\n", - " response = openai.chat.completions.create(**formatted_prompt.to_openai_kwargs())\n", - "\n", - " # Link this trace to the application and environment\n", - " ag.tracing.store_refs(\n", - " {\n", - " \"application.slug\": \"topic-explainer-traced\",\n", - " \"variant.slug\": \"production-variant\",\n", - " \"environment.slug\": \"production\",\n", - " }\n", - " )\n", - "\n", - " return response.choices[0].message.content\n", - "\n", - "\n", - "# Test the function\n", - "explanation = explain_topic_with_prompt(\"machine learning\")\n", - "print(\"Explanation:\")\n", - "print(explanation)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Part 5: Redact Sensitive Data\n", - "\n", - "When working with production data, you often need to exclude sensitive information from traces. The Agenta SDK provides several ways to redact data:\n", - "\n", - "1. **Simple redaction**: Ignore all inputs/outputs\n", - "2. **Selective redaction**: Ignore specific fields\n", - "3. **Custom redaction**: Use a callback function for fine-grained control\n", - "4. **Global redaction**: Apply rules across all instrumented functions\n", - "\n", - "Let's explore these different approaches." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Simple Redaction: Ignore All Inputs/Outputs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@ag.instrument(spankind=\"workflow\", ignore_inputs=True, ignore_outputs=True)\n", - "def process_sensitive_data(user_email: str, credit_card: str):\n", - " \"\"\"Process sensitive data without logging inputs/outputs.\"\"\"\n", - " # The function inputs and outputs won't be captured in the trace\n", - " result = f\"Processed data for {user_email}\"\n", - " return result\n", - "\n", - "\n", - "# This trace will not contain inputs or outputs\n", - "result = process_sensitive_data(\"user@example.com\", \"4111-1111-1111-1111\")\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Selective Redaction: Ignore Specific Fields" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "@ag.instrument(\n", - " spankind=\"workflow\",\n", - " ignore_inputs=[\"api_key\", \"password\"],\n", - " ignore_outputs=[\"internal_token\"],\n", - ")\n", - "def authenticate_user(username: str, password: str, api_key: str):\n", - " \"\"\"Authenticate a user (password and api_key will be redacted).\"\"\"\n", - " # Simulate authentication\n", - " return {\n", - " \"username\": username,\n", - " \"authenticated\": True,\n", - " \"internal_token\": \"secret-token-12345\", # This will be redacted\n", - " }\n", - "\n", - "\n", - "# The trace will show username but not password or api_key\n", - "auth_result = authenticate_user(\"john_doe\", \"secret123\", \"sk-abc123\")\n", - "print(f\"Authenticated: {auth_result['authenticated']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Custom Redaction: Use a Callback Function" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import re\n", - "\n", - "\n", - "def redact_pii(name: str, field: str, data: dict):\n", - " \"\"\"Custom redaction function that removes PII.\"\"\"\n", - " if field == \"inputs\":\n", - " # Redact email addresses\n", - " if \"email\" in data:\n", - " data[\"email\"] = \"[REDACTED]\"\n", - " # Redact phone numbers\n", - " if \"phone\" in data:\n", - " data[\"phone\"] = \"[REDACTED]\"\n", - "\n", - " if field == \"outputs\":\n", - " # Redact any credit card patterns\n", - " if isinstance(data, dict):\n", - " for key, value in data.items():\n", - " if isinstance(value, str):\n", - " # Simple credit card pattern\n", - " data[key] = re.sub(\n", - " r\"\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\",\n", - " \"[CARD-REDACTED]\",\n", - " value,\n", - " )\n", - "\n", - " return data\n", - "\n", - "\n", - "@ag.instrument(\n", - " spankind=\"workflow\",\n", - " redact=redact_pii,\n", - " redact_on_error=False, # Don't apply redaction if it raises an error\n", - ")\n", - "def process_customer_order(name: str, email: str, phone: str, card_number: str):\n", - " \"\"\"Process a customer order with PII redaction.\"\"\"\n", - " return {\n", - " \"status\": \"processed\",\n", - " \"customer\": name,\n", - " \"payment_info\": f\"Charged card ending in {card_number[-4:]}\",\n", - " \"full_card\": card_number, # This will be redacted\n", - " }\n", - "\n", - "\n", - "# Test with sample data\n", - "order = process_customer_order(\n", - " name=\"Jane Smith\",\n", - " email=\"jane@example.com\", # Will be redacted\n", - " phone=\"555-1234\", # Will be redacted\n", - " card_number=\"4111-1111-1111-1111\", # Will be redacted in output\n", - ")\n", - "print(f\"Order status: {order['status']}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Global Redaction: Apply Rules Across All Functions\n", - "\n", - "For organization-wide policies, you can set up global redaction rules during initialization. Note: Since we already called `ag.init()`, this is just for demonstration. In a real application, you would set this during the initial setup." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Example of global redaction setup (would be done during ag.init())\n", - "from typing import Dict, Any\n", - "\n", - "\n", - "def global_redact_function(name: str, field: str, data: Dict[str, Any]):\n", - " \"\"\"Global redaction that applies to all instrumented functions.\"\"\"\n", - " # Remove any field containing 'api_key' or 'secret'\n", - " if isinstance(data, dict):\n", - " keys_to_redact = [\n", - " k for k in data.keys() if \"api_key\" in k.lower() or \"secret\" in k.lower()\n", - " ]\n", - " for key in keys_to_redact:\n", - " data[key] = \"[REDACTED]\"\n", - "\n", - " return data\n", - "\n", - "\n", - "# In production, you would initialize like this:\n", - "# ag.init(\n", - "# redact=global_redact_function,\n", - "# redact_on_error=True\n", - "# )\n", - "\n", - "print(\"Global redaction would be configured during ag.init()\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "In this tutorial, you learned how to:\n", - "\n", - "1. ✅ **Set up tracing** with the Agenta SDK and OpenAI auto-instrumentation\n", - "2. ✅ **Instrument functions** using the `@ag.instrument()` decorator\n", - "3. ✅ **Store internals and metadata** to capture intermediate data in your workflows\n", - "4. ✅ **Reference prompt versions** by creating, deploying, and linking traces to applications\n", - "5. ✅ **Redact sensitive data** using multiple approaches for privacy protection\n", - "\n", - "## Next Steps\n", - "\n", - "- Explore [distributed tracing](/observability/trace-with-python-sdk/distributed-tracing) for multi-service applications\n", - "- Learn about [cost tracking](/observability/trace-with-python-sdk/track-costs) to monitor LLM expenses\n", - "- Understand [trace annotations](/observability/trace-with-python-sdk/annotate-traces) for collecting feedback\n", - "- Check out the [Agenta UI guide](/observability/using-the-ui/filtering-traces) for filtering and analyzing traces" - ] - } - ], - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.0" - } - }, - "nbformat": 4, - "nbformat_minor": 0 + "nbformat": 4, + "nbformat_minor": 0 } \ No newline at end of file diff --git a/examples/jupyter/prompt-management/how-to-prompt-management.ipynb b/examples/jupyter/prompt-management/how-to-prompt-management.ipynb index 61e0b8f42b..e92f31235a 100644 --- a/examples/jupyter/prompt-management/how-to-prompt-management.ipynb +++ b/examples/jupyter/prompt-management/how-to-prompt-management.ipynb @@ -204,7 +204,7 @@ " Message(role=\"user\", content=\"Explain {{topic}} in simple terms\"),\n", " ],\n", " llm_config=ModelConfig(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " max_tokens=150,\n", " temperature=0.7,\n", " top_p=1.0,\n", @@ -270,7 +270,7 @@ " \"template_format\": \"curly\",\n", " \"input_keys\": null,\n", " \"llm_config\": {\n", - " \"model\": \"gpt-3.5-turbo\",\n", + " \"model\": \"gpt-5\",\n", " \"temperature\": 0.7,\n", " \"max_tokens\": 150,\n", " \"top_p\": 1.0,\n", @@ -626,7 +626,7 @@ " \"template_format\": \"curly\",\n", " \"input_keys\": null,\n", " \"llm_config\": {\n", - " \"model\": \"gpt-3.5-turbo\",\n", + " \"model\": \"gpt-5\",\n", " \"temperature\": 0.7,\n", " \"max_tokens\": 150,\n", " \"top_p\": 1.0,\n", diff --git a/examples/jupyter/prompt-management/manage-prompts-with-sdk-tutorial.ipynb b/examples/jupyter/prompt-management/manage-prompts-with-sdk-tutorial.ipynb index 1e1e0571cd..60e2ea191a 100644 --- a/examples/jupyter/prompt-management/manage-prompts-with-sdk-tutorial.ipynb +++ b/examples/jupyter/prompt-management/manage-prompts-with-sdk-tutorial.ipynb @@ -285,7 +285,7 @@ " \"template_format\": \"curly\",\n", " \"input_keys\": null,\n", " \"llm_config\": {\n", - " \"model\": \"gpt-3.5-turbo\",\n", + " \"model\": \"gpt-5\",\n", " \"temperature\": 0.6,\n", " \"max_tokens\": 150,\n", " \"top_p\": 1.0,\n", @@ -322,7 +322,7 @@ " Message(role=\"user\", content=\"Explain {{topic}} in simple terms\"),\n", " ],\n", " llm_config=ModelConfig(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " max_tokens=150,\n", " temperature=0.6,\n", " top_p=1.0,\n", @@ -476,7 +476,7 @@ " \"template_format\": \"curly\",\n", " \"input_keys\": null,\n", " \"llm_config\": {\n", - " \"model\": \"gpt-3.5-turbo\",\n", + " \"model\": \"gpt-5\",\n", " \"temperature\": 0.9,\n", " \"max_tokens\": 150,\n", " \"top_p\": 1.0,\n", @@ -507,7 +507,7 @@ " ),\n", " ],\n", " llm_config=ModelConfig(\n", - " model=\"gpt-3.5-turbo\",\n", + " model=\"gpt-5\",\n", " max_tokens=150,\n", " temperature=0.9,\n", " top_p=1.0,\n", @@ -557,7 +557,7 @@ "output_type": "stream", "text": [ "Fetched configuration from production:\n", - "{'prompt': {'messages': [{'role': 'system', 'content': 'You are an assistant that provides concise answers', 'name': None, 'tool_calls': None, 'tool_call_id': None}, {'role': 'user', 'content': 'Explain {{topic}} in simple terms', 'name': None, 'tool_calls': None, 'tool_call_id': None}], 'system_prompt': None, 'user_prompt': None, 'template_format': 'curly', 'input_keys': None, 'llm_config': {'model': 'gpt-3.5-turbo', 'temperature': 0.6, 'max_tokens': 150, 'top_p': 1.0, 'frequency_penalty': 0.0, 'presence_penalty': 0.0, 'response_format': None, 'stream': None, 'tools': None, 'tool_choice': None}}}\n" + "{'prompt': {'messages': [{'role': 'system', 'content': 'You are an assistant that provides concise answers', 'name': None, 'tool_calls': None, 'tool_call_id': None}, {'role': 'user', 'content': 'Explain {{topic}} in simple terms', 'name': None, 'tool_calls': None, 'tool_call_id': None}], 'system_prompt': None, 'user_prompt': None, 'template_format': 'curly', 'input_keys': None, 'llm_config': {'model': 'gpt-5', 'temperature': 0.6, 'max_tokens': 150, 'top_p': 1.0, 'frequency_penalty': 0.0, 'presence_penalty': 0.0, 'response_format': None, 'stream': None, 'tools': None, 'tool_choice': None}}}\n" ] } ], diff --git a/examples/node/observability-opentelemetry/README.md b/examples/node/observability-opentelemetry/README.md index 9d61b4984f..27b161cbb1 100644 --- a/examples/node/observability-opentelemetry/README.md +++ b/examples/node/observability-opentelemetry/README.md @@ -19,7 +19,7 @@ This example uses the latest stable versions: 1. Install dependencies: ```bash -npm install +pnpm install ``` 2. Create a `.env` file with your credentials: @@ -30,7 +30,7 @@ cp .env.example .env 3. Run the example: ```bash -npm start +pnpm start ``` ## What's Happening? @@ -65,4 +65,3 @@ See [SEMANTIC_CONVENTIONS.md](./SEMANTIC_CONVENTIONS.md) for detailed documentat ## View Your Traces After running the example, log in to [Agenta](https://cloud.agenta.ai) and navigate to the Observability section to see your traces! - diff --git a/examples/node/observability-opentelemetry/app.js b/examples/node/observability-opentelemetry/app.js index b3dce7e794..2a1e05b965 100644 --- a/examples/node/observability-opentelemetry/app.js +++ b/examples/node/observability-opentelemetry/app.js @@ -1,6 +1,8 @@ // app.js -import OpenAI from "openai"; +import 'dotenv/config'; + import { trace } from "@opentelemetry/api"; +import OpenAI from "openai"; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, @@ -27,11 +29,11 @@ async function generate() { // Stores the input parameters as JSON span.setAttribute("ag.data.inputs", JSON.stringify({ messages: messages, - model: "gpt-3.5-turbo" + model: "gpt-5" })); const response = await openai.chat.completions.create({ - model: "gpt-3.5-turbo", + model: "gpt-5", messages: messages, }); diff --git a/examples/node/observability-opentelemetry/instrumentation.js b/examples/node/observability-opentelemetry/instrumentation.js index 4921de9bf7..fcc52f1e07 100644 --- a/examples/node/observability-opentelemetry/instrumentation.js +++ b/examples/node/observability-opentelemetry/instrumentation.js @@ -1,4 +1,6 @@ // instrumentation.js +import "dotenv/config"; + import { registerInstrumentations } from "@opentelemetry/instrumentation"; import { OpenAIInstrumentation } from "@arizeai/openinference-instrumentation-openai"; import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api"; diff --git a/examples/node/observability-opentelemetry/package-lock.json b/examples/node/observability-opentelemetry/package-lock.json deleted file mode 100644 index c9c0ef0ae5..0000000000 --- a/examples/node/observability-opentelemetry/package-lock.json +++ /dev/null @@ -1,935 +0,0 @@ -{ - "name": "agenta-opentelemetry-quickstart", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "agenta-opentelemetry-quickstart", - "version": "1.0.0", - "dependencies": { - "@arizeai/openinference-instrumentation-openai": "^3.2.3", - "@arizeai/openinference-semantic-conventions": "^2.1.2", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-trace-otlp-proto": "^0.54.0", - "@opentelemetry/instrumentation": "^0.54.0", - "@opentelemetry/resources": "^1.28.0", - "@opentelemetry/sdk-trace-base": "^1.28.0", - "@opentelemetry/sdk-trace-node": "^1.28.0", - "@opentelemetry/semantic-conventions": "^1.28.0", - "openai": "^6.7.0" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - } - }, - "node_modules/@arizeai/openinference-core": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@arizeai/openinference-core/-/openinference-core-1.0.7.tgz", - "integrity": "sha512-O9WYkrHNh/0mGTV+T9SWC3tkxVrT16gBrFiByG3aukBsqdOfSzoRj6QINk+Oi+VEDNIoQUzVQPFh81/gEL/thA==", - "license": "Apache-2.0", - "dependencies": { - "@arizeai/openinference-semantic-conventions": "2.1.2", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^1.25.1" - } - }, - "node_modules/@arizeai/openinference-instrumentation-openai": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@arizeai/openinference-instrumentation-openai/-/openinference-instrumentation-openai-3.2.3.tgz", - "integrity": "sha512-4GiUyUIafNkAf7milnsS4Xjh0rZwjPvhHu+xkcj2MQ19a0Teu6PcFDa7DCt7YV/xw2o+wcxeaLfVURmGGLAHOg==", - "license": "Apache-2.0", - "dependencies": { - "@arizeai/openinference-core": "1.0.7", - "@arizeai/openinference-semantic-conventions": "2.1.2", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/core": "^1.25.1", - "@opentelemetry/instrumentation": "^0.46.0" - } - }, - "node_modules/@arizeai/openinference-instrumentation-openai/node_modules/@opentelemetry/instrumentation": { - "version": "0.46.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.46.0.tgz", - "integrity": "sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==", - "license": "Apache-2.0", - "dependencies": { - "@types/shimmer": "^1.0.2", - "import-in-the-middle": "1.7.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@arizeai/openinference-instrumentation-openai/node_modules/import-in-the-middle": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.7.1.tgz", - "integrity": "sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.8.2", - "acorn-import-assertions": "^1.9.0", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/@arizeai/openinference-semantic-conventions": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@arizeai/openinference-semantic-conventions/-/openinference-semantic-conventions-2.1.2.tgz", - "integrity": "sha512-u7UeuU9bJ1LxzHk0MPWb+1ZcotCcJwPnKDXi7Rl2cPs1pWMFg9Ogq7zzYZX+sDcibD2AEa1U+ElyOD8DwZc9gw==", - "license": "Apache-2.0" - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@opentelemetry/api-logs": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.54.2.tgz", - "integrity": "sha512-4MTVwwmLgUh5QrJnZpYo6YRO5IBLAggf2h8gWDblwRagDStY13aEvt7gGk3jewrMaPlHiF83fENhIx0HO97/cQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api": "^1.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.30.1.tgz", - "integrity": "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.27.0.tgz", - "integrity": "sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/core/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-trace-otlp-proto/-/exporter-trace-otlp-proto-0.54.2.tgz", - "integrity": "sha512-XSmm1N2wAhoWDXP1q/N6kpLebWaxl6VIADv4WA5QWKHLRpF3gLz5NAWNJBR8ygsvv8jQcrwnXgwfnJ18H3v1fg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/otlp-exporter-base": "0.54.2", - "@opentelemetry/otlp-transformer": "0.54.2", - "@opentelemetry/resources": "1.27.0", - "@opentelemetry/sdk-trace-base": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/resources": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", - "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.27.0.tgz", - "integrity": "sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/resources": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/exporter-trace-otlp-proto/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/instrumentation": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.54.2.tgz", - "integrity": "sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.54.2", - "@types/shimmer": "^1.2.0", - "import-in-the-middle": "^1.8.1", - "require-in-the-middle": "^7.1.1", - "semver": "^7.5.2", - "shimmer": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-exporter-base": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-exporter-base/-/otlp-exporter-base-0.54.2.tgz", - "integrity": "sha512-NrNyxu6R/bGAwanhz1HI0aJWKR6xUED4TjCH4iWMlAfyRukGbI9Kt/Akd2sYLwRKNhfS+sKetKGCUQPMDyYYMA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/otlp-transformer": "0.54.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/otlp-transformer/-/otlp-transformer-0.54.2.tgz", - "integrity": "sha512-2tIjahJlMRRUz0A2SeE+qBkeBXBFkSjR0wqJ08kuOqaL8HNGan5iZf+A8cfrfmZzPUuMKCyY9I+okzFuFs6gKQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.54.2", - "@opentelemetry/core": "1.27.0", - "@opentelemetry/resources": "1.27.0", - "@opentelemetry/sdk-logs": "0.54.2", - "@opentelemetry/sdk-metrics": "1.27.0", - "@opentelemetry/sdk-trace-base": "1.27.0", - "protobufjs": "^7.3.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.3.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/resources": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", - "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.27.0.tgz", - "integrity": "sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/resources": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/otlp-transformer/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/propagator-b3": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.30.1.tgz", - "integrity": "sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-b3/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.30.1.tgz", - "integrity": "sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/propagator-jaeger/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/resources": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.30.1.tgz", - "integrity": "sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/resources/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-logs": { - "version": "0.54.2", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-logs/-/sdk-logs-0.54.2.tgz", - "integrity": "sha512-yIbYqDLS/AtBbPjCjh6eSToGNRMqW2VR8RrKEy+G+J7dFG7pKoptTH5T+XlKPleP9NY8JZYIpgJBlI+Osi0rFw==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/api-logs": "0.54.2", - "@opentelemetry/core": "1.27.0", - "@opentelemetry/resources": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.4.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/resources": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", - "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-logs/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-metrics": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-1.27.0.tgz", - "integrity": "sha512-JzWgzlutoXCydhHWIbLg+r76m+m3ncqvkCcsswXAQ4gqKS+LOHKhq+t6fx1zNytvLuaOUBur7EvWxECc4jPQKg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/resources": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.3.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/resources": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.27.0.tgz", - "integrity": "sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.27.0", - "@opentelemetry/semantic-conventions": "1.27.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-metrics/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", - "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.30.1.tgz", - "integrity": "sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/core": "1.30.1", - "@opentelemetry/resources": "1.30.1", - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-base/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.30.1.tgz", - "integrity": "sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.30.1", - "@opentelemetry/core": "1.30.1", - "@opentelemetry/propagator-b3": "1.30.1", - "@opentelemetry/propagator-jaeger": "1.30.1", - "@opentelemetry/sdk-trace-base": "1.30.1", - "semver": "^7.5.2" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/core": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.30.1.tgz", - "integrity": "sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==", - "license": "Apache-2.0", - "dependencies": { - "@opentelemetry/semantic-conventions": "1.28.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "@opentelemetry/api": ">=1.0.0 <1.10.0" - } - }, - "node_modules/@opentelemetry/sdk-trace-node/node_modules/@opentelemetry/semantic-conventions": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.28.0.tgz", - "integrity": "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.37.0.tgz", - "integrity": "sha512-JD6DerIKdJGmRp4jQyX5FlrQjA4tjOw1cvfsPAZXfOOEErMUHjPcPSICS+6WnM0nB0efSFARh0KAZss+bvExOA==", - "license": "Apache-2.0", - "engines": { - "node": ">=14" - } - }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", - "license": "BSD-3-Clause" - }, - "node_modules/@types/node": { - "version": "22.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.12.tgz", - "integrity": "sha512-BICHQ67iqxQGFSzfCFTT7MRQ5XcBjG5aeKh5Ok38UBbPe5fxTyE+aHFxwVrGyr8GNlqFMLKD1D3P2K/1ks8tog==", - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==", - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", - "deprecated": "package has been renamed to acorn-import-attributes", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/import-in-the-middle": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz", - "integrity": "sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==", - "license": "Apache-2.0", - "dependencies": { - "acorn": "^8.14.0", - "acorn-import-attributes": "^1.9.5", - "cjs-module-lexer": "^1.2.2", - "module-details-from-path": "^1.0.3" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/long": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", - "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", - "license": "Apache-2.0" - }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/openai": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-6.7.0.tgz", - "integrity": "sha512-mgSQXa3O/UXTbA8qFzoa7aydbXBJR5dbLQXCRapAOtoNT+v69sLdKMZzgiakpqhclRnhPggPAXoniVGn2kMY2A==", - "license": "Apache-2.0", - "bin": { - "openai": "bin/cli" - }, - "peerDependencies": { - "ws": "^8.18.0", - "zod": "^3.25 || ^4.0" - }, - "peerDependenciesMeta": { - "ws": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/require-in-the-middle": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-7.5.2.tgz", - "integrity": "sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.3.5", - "module-details-from-path": "^1.0.3", - "resolve": "^1.22.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shimmer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", - "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==", - "license": "BSD-2-Clause" - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "license": "MIT" - } - } -} diff --git a/examples/node/observability-opentelemetry/package.json b/examples/node/observability-opentelemetry/package.json index bc74d98dae..2f1a839079 100644 --- a/examples/node/observability-opentelemetry/package.json +++ b/examples/node/observability-opentelemetry/package.json @@ -1,25 +1,26 @@ { - "name": "agenta-opentelemetry-quickstart", - "version": "1.0.0", - "description": "Quick start example for using OpenTelemetry with Agenta", - "type": "module", - "scripts": { - "start": "node --import ./instrumentation.js app.js" - }, - "dependencies": { - "@arizeai/openinference-instrumentation-openai": "^3.2.3", - "@arizeai/openinference-semantic-conventions": "^2.1.2", - "@opentelemetry/api": "^1.9.0", - "@opentelemetry/exporter-trace-otlp-proto": "^0.54.0", - "@opentelemetry/instrumentation": "^0.54.0", - "@opentelemetry/resources": "^1.28.0", - "@opentelemetry/sdk-trace-base": "^1.28.0", - "@opentelemetry/sdk-trace-node": "^1.28.0", - "@opentelemetry/semantic-conventions": "^1.28.0", - "openai": "^6.7.0" - }, - "devDependencies": { - "@types/node": "^22.0.0", - "typescript": "^5.7.0" - } + "name": "agenta-opentelemetry-quickstart", + "version": "1.0.0", + "description": "Quick start example for using OpenTelemetry with Agenta", + "type": "module", + "scripts": { + "start": "node --import ./instrumentation.js app.js" + }, + "dependencies": { + "@arizeai/openinference-instrumentation-openai": "^2.3.1", + "@arizeai/openinference-semantic-conventions": "^2.1.2", + "@opentelemetry/api": "^1.9.0", + "@opentelemetry/exporter-trace-otlp-proto": "^0.54.0", + "@opentelemetry/instrumentation": "^0.54.0", + "@opentelemetry/resources": "^1.28.0", + "@opentelemetry/sdk-trace-base": "^1.28.0", + "@opentelemetry/sdk-trace-node": "^1.28.0", + "@opentelemetry/semantic-conventions": "^1.28.0", + "dotenv": "^17.2.3", + "openai": "^4.104.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "^5.7.0" + } } diff --git a/examples/node/observability-opentelemetry/pnpm-lock.yaml b/examples/node/observability-opentelemetry/pnpm-lock.yaml new file mode 100644 index 0000000000..9e357c23ac --- /dev/null +++ b/examples/node/observability-opentelemetry/pnpm-lock.yaml @@ -0,0 +1,868 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@arizeai/openinference-instrumentation-openai': + specifier: ^2.3.1 + version: 2.3.1 + '@arizeai/openinference-semantic-conventions': + specifier: ^2.1.2 + version: 2.1.7 + '@opentelemetry/api': + specifier: ^1.9.0 + version: 1.9.0 + '@opentelemetry/exporter-trace-otlp-proto': + specifier: ^0.54.0 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': + specifier: ^0.54.0 + version: 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': + specifier: ^1.28.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': + specifier: ^1.28.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-node': + specifier: ^1.28.0 + version: 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': + specifier: ^1.28.0 + version: 1.38.0 + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + openai: + specifier: ^4.104.0 + version: 4.104.0 + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.3 + typescript: + specifier: ^5.7.0 + version: 5.9.3 + +packages: + + '@arizeai/openinference-core@1.0.3': + resolution: {integrity: sha512-X4nmUVgE8jdBDKStJ8huBZ60gm915eUCpi29BdgLm8/+8gZXctTUATL1v4dlGgCT+cv3VZmUgak74mJ2izx7kA==} + + '@arizeai/openinference-instrumentation-openai@2.3.1': + resolution: {integrity: sha512-8z5TEPwhj8fE7+6li2ufLP2jknkPTV7KAQwiNLj4/9IIz0o0dbFltm5O834bpE7LjzkGAqPt8CMVRDhpnw82IA==} + + '@arizeai/openinference-semantic-conventions@2.0.0': + resolution: {integrity: sha512-9FJaC5O94xjR0rR3emNl4J/qQIutr5WAPaFgBnOtznU9u8ondXXDafRKcHfOqWUc2JxuQqEUzPEOYfkOR+pPZQ==} + + '@arizeai/openinference-semantic-conventions@2.1.7': + resolution: {integrity: sha512-KyBfwxkSusPvxHBaW/TJ0japEbXCNziW9o6/IRKiPu+gp5TMKIagV2NKvt47rWYa4Jc0Nl+SvAPm+yxkdJqVbg==} + + '@opentelemetry/api-logs@0.54.2': + resolution: {integrity: sha512-4MTVwwmLgUh5QrJnZpYo6YRO5IBLAggf2h8gWDblwRagDStY13aEvt7gGk3jewrMaPlHiF83fENhIx0HO97/cQ==} + engines: {node: '>=14'} + + '@opentelemetry/api@1.9.0': + resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} + engines: {node: '>=8.0.0'} + + '@opentelemetry/context-async-hooks@1.30.1': + resolution: {integrity: sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.27.0': + resolution: {integrity: sha512-yQPKnK5e+76XuiqUH/gKyS8wv/7qITd5ln56QkBTf3uggr0VkXOXfcaAuG330UfdYu83wsyoBwqwxigpIG+Jkg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.30.1': + resolution: {integrity: sha512-OOCM2C/QIURhJMuKaekP3TRBxBKxG/TWWA0TL2J6nXUtDnuCtccy49LUJF8xPFXMX+0LMcxFpCo8M9cGY1W6rQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/exporter-trace-otlp-proto@0.54.2': + resolution: {integrity: sha512-XSmm1N2wAhoWDXP1q/N6kpLebWaxl6VIADv4WA5QWKHLRpF3gLz5NAWNJBR8ygsvv8jQcrwnXgwfnJ18H3v1fg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.46.0': + resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.54.2': + resolution: {integrity: sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-exporter-base@0.54.2': + resolution: {integrity: sha512-NrNyxu6R/bGAwanhz1HI0aJWKR6xUED4TjCH4iWMlAfyRukGbI9Kt/Akd2sYLwRKNhfS+sKetKGCUQPMDyYYMA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/otlp-transformer@0.54.2': + resolution: {integrity: sha512-2tIjahJlMRRUz0A2SeE+qBkeBXBFkSjR0wqJ08kuOqaL8HNGan5iZf+A8cfrfmZzPUuMKCyY9I+okzFuFs6gKQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/propagator-b3@1.30.1': + resolution: {integrity: sha512-oATwWWDIJzybAZ4pO76ATN5N6FFbOA1otibAVlS8v90B4S1wClnhRUk7K+2CHAwN1JKYuj4jh/lpCEG5BAqFuQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/propagator-jaeger@1.30.1': + resolution: {integrity: sha512-Pj/BfnYEKIOImirH76M4hDaBSx6HyZ2CXUqk+Kj02m6BB80c/yo4BdWkn/1gDFfU+YPY+bPR2U0DKBfdxCKwmg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.27.0': + resolution: {integrity: sha512-jOwt2VJ/lUD5BLc+PMNymDrUCpm5PKi1E9oSVYAvz01U/VdndGmrtV3DU1pG4AwlYhJRHbHfOUIlpBeXCPw6QQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.30.1': + resolution: {integrity: sha512-5UxZqiAgLYGFjS4s9qm5mBVo433u+dSPUFWVWXmLAD4wB65oMCoXaJP1KJa9DIYYMeHu3z4BZcStG3LC593cWA==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-logs@0.54.2': + resolution: {integrity: sha512-yIbYqDLS/AtBbPjCjh6eSToGNRMqW2VR8RrKEy+G+J7dFG7pKoptTH5T+XlKPleP9NY8JZYIpgJBlI+Osi0rFw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.4.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.27.0': + resolution: {integrity: sha512-JzWgzlutoXCydhHWIbLg+r76m+m3ncqvkCcsswXAQ4gqKS+LOHKhq+t6fx1zNytvLuaOUBur7EvWxECc4jPQKg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.27.0': + resolution: {integrity: sha512-btz6XTQzwsyJjombpeqCX6LhiMQYpzt2pIYNPnw0IPO/3AhT6yjnf8Mnv3ZC2A4eRYOjqrg+bfaXg9XHDRJDWQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-base@1.30.1': + resolution: {integrity: sha512-jVPgBbH1gCy2Lb7X0AVQ8XAfgg0pJ4nvl8/IiQA6nxOsPvS+0zMJaFSs2ltXe0J6C8dqjcnpyqINDJmU30+uOg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-trace-node@1.30.1': + resolution: {integrity: sha512-cBjYOINt1JxXdpw1e5MlHmFRc5fgj4GW/86vsKFxJCJ8AL4PdVtYH41gWwl4qd4uQjqEL1oJVrXkSy5cnduAnQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.27.0': + resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.38.0': + resolution: {integrity: sha512-kocjix+/sSggfJhwXqClZ3i9Y/MI0fp7b+g7kCRm6psy2dsf8uApTRclwG18h8Avm7C9+fnt+O36PspJ/OzoWg==} + engines: {node: '>=14'} + + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.4': + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + + '@protobufjs/eventemitter@1.1.0': + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + + '@protobufjs/fetch@1.1.0': + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.0': + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.0': + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + + '@types/node-fetch@2.6.13': + resolution: {integrity: sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==} + + '@types/node@18.19.130': + resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} + + '@types/node@22.19.3': + resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} + + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + acorn-import-assertions@1.9.0: + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes + peerDependencies: + acorn: ^8 + + acorn-import-attributes@1.9.5: + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + import-in-the-middle@1.15.0: + resolution: {integrity: sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==} + + import-in-the-middle@1.7.1: + resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + long@5.3.2: + resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + module-details-from-path@1.0.4: + resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + openai@4.104.0: + resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + protobufjs@7.5.4: + resolution: {integrity: sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==} + engines: {node: '>=12.0.0'} + + require-in-the-middle@7.5.2: + resolution: {integrity: sha512-gAZ+kLqBdHarXB64XpAe2VCjB7rIRv+mU8tfRWziHRJ5umKsIHN2tLLv6EtMw7WCdP19S0ERVMldNvxYCHnhSQ==} + engines: {node: '>=8.6.0'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + +snapshots: + + '@arizeai/openinference-core@1.0.3': + dependencies: + '@arizeai/openinference-semantic-conventions': 2.0.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@arizeai/openinference-instrumentation-openai@2.3.1': + dependencies: + '@arizeai/openinference-core': 1.0.3 + '@arizeai/openinference-semantic-conventions': 2.0.0 + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.46.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@arizeai/openinference-semantic-conventions@2.0.0': {} + + '@arizeai/openinference-semantic-conventions@2.1.7': {} + + '@opentelemetry/api-logs@0.54.2': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + + '@opentelemetry/core@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/core@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/exporter-trace-otlp-proto@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-exporter-base': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.7.1 + require-in-the-middle: 7.5.2 + semver: 7.7.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.15.0 + require-in-the-middle: 7.5.2 + semver: 7.7.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/otlp-exporter-base@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/otlp-transformer': 0.54.2(@opentelemetry/api@1.9.0) + + '@opentelemetry/otlp-transformer@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-logs': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-metrics': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.27.0(@opentelemetry/api@1.9.0) + protobufjs: 7.5.4 + + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-logs@0.54.2(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.54.2 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-metrics@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + + '@opentelemetry/sdk-trace-base@1.27.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.27.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + semver: 7.7.3 + + '@opentelemetry/semantic-conventions@1.27.0': {} + + '@opentelemetry/semantic-conventions@1.28.0': {} + + '@opentelemetry/semantic-conventions@1.38.0': {} + + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.4': {} + + '@protobufjs/eventemitter@1.1.0': {} + + '@protobufjs/fetch@1.1.0': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.0': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.0': {} + + '@types/node-fetch@2.6.13': + dependencies: + '@types/node': 22.19.3 + form-data: 4.0.5 + + '@types/node@18.19.130': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.19.3': + dependencies: + undici-types: 6.21.0 + + '@types/shimmer@1.2.0': {} + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-import-assertions@1.9.0(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn-import-attributes@1.9.5(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + asynckit@0.4.0: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + cjs-module-lexer@1.4.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + dotenv@17.2.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + event-target-shim@5.0.1: {} + + form-data-encoder@1.7.2: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + import-in-the-middle@1.15.0: + dependencies: + acorn: 8.15.0 + acorn-import-attributes: 1.9.5(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + + import-in-the-middle@1.7.1: + dependencies: + acorn: 8.15.0 + acorn-import-assertions: 1.9.0(acorn@8.15.0) + cjs-module-lexer: 1.4.3 + module-details-from-path: 1.0.4 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + long@5.3.2: {} + + math-intrinsics@1.1.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + module-details-from-path@1.0.4: {} + + ms@2.1.3: {} + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + openai@4.104.0: + dependencies: + '@types/node': 18.19.130 + '@types/node-fetch': 2.6.13 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + path-parse@1.0.7: {} + + protobufjs@7.5.4: + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 22.19.3 + long: 5.3.2 + + require-in-the-middle@7.5.2: + dependencies: + debug: 4.4.3 + module-details-from-path: 1.0.4 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + semver@7.7.3: {} + + shimmer@1.2.1: {} + + supports-preserve-symlinks-flag@1.0.0: {} + + tr46@0.0.3: {} + + typescript@5.9.3: {} + + undici-types@5.26.5: {} + + undici-types@6.21.0: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 diff --git a/examples/python/annotation-example.py b/examples/python/annotation-example.py index cfaa099e36..4fde38f680 100644 --- a/examples/python/annotation-example.py +++ b/examples/python/annotation-example.py @@ -88,7 +88,7 @@ def generate(topic="witches", genre="comedy"): OpenAIInstrumentor().instrument() response = openai.chat.completions.create( - model="gpt-3.5-turbo", + model="gpt-5", messages=[ {"role": "system", "content": "You are a helpful assistant."}, { diff --git a/examples/python/custom_workflows/ai-code-reviewer/ai-code-reviewer.py b/examples/python/custom_workflows/ai-code-reviewer/ai-code-reviewer.py index 2703c420d2..4a9222150b 100644 --- a/examples/python/custom_workflows/ai-code-reviewer/ai-code-reviewer.py +++ b/examples/python/custom_workflows/ai-code-reviewer/ai-code-reviewer.py @@ -29,7 +29,7 @@ class Config(BaseModel): system_prompt: str = prompt_system user_prompt: str = prompt_user - model: str = MCField(default="gpt-3.5-turbo", choices=supported_llm_models) + model: str = MCField(default="gpt-5", choices=supported_llm_models) @ag.instrument() diff --git a/examples/python/custom_workflows/chain_of_prompts/cop.py b/examples/python/custom_workflows/chain_of_prompts/cop.py index 1af034895d..86a3beb56d 100644 --- a/examples/python/custom_workflows/chain_of_prompts/cop.py +++ b/examples/python/custom_workflows/chain_of_prompts/cop.py @@ -25,12 +25,12 @@ def generate(blog_post: str): config = ag.ConfigManager.get_from_route(schema=CoPConfig) formatted_prompt1 = config.prompt1.format(blog_post=blog_post) completion = client.chat.completions.create( - model="gpt-3.5-turbo", messages=[{"role": "user", "content": formatted_prompt1}] + model="gpt-5", messages=[{"role": "user", "content": formatted_prompt1}] ) output_1 = completion.choices[0].message.content formatted_prompt2 = config.prompt2.format(output_1=output_1) completion = client.chat.completions.create( - model="gpt-3.5-turbo", messages=[{"role": "user", "content": formatted_prompt2}] + model="gpt-5", messages=[{"role": "user", "content": formatted_prompt2}] ) return completion.choices[0].message.content diff --git a/examples/python/custom_workflows/rag-docs-qa/generate_testset.py b/examples/python/custom_workflows/rag-docs-qa/generate_testset.py index 0eb362fd6d..94f5a57b29 100644 --- a/examples/python/custom_workflows/rag-docs-qa/generate_testset.py +++ b/examples/python/custom_workflows/rag-docs-qa/generate_testset.py @@ -46,7 +46,7 @@ def generate_questions(title, content): try: response = completion( - model="gpt-3.5-turbo-0125", # Using the latest model that supports JSON mode + model="gpt-5", # Using the latest model that supports JSON mode messages=[ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt}, diff --git a/examples/python/custom_workflows/rag-docs-qa/query.py b/examples/python/custom_workflows/rag-docs-qa/query.py index 2bee1789e3..4de9bf1fb9 100644 --- a/examples/python/custom_workflows/rag-docs-qa/query.py +++ b/examples/python/custom_workflows/rag-docs-qa/query.py @@ -31,7 +31,7 @@ class Config(BaseModel): system_prompt: str = Field(default=system_prompt) user_prompt: str = Field(default=user_prompt) embedding_model: str = MCField(default="openai", choices=["openai", "cohere"]) - llm_model: str = MCField(default="gpt-3.5-turbo", choices=supported_llm_models) + llm_model: str = MCField(default="gpt-5", choices=supported_llm_models) top_k: int = Field(default=10, ge=1, le=25) rerank_top_k: int = Field(default=3, ge=1, le=10) use_rerank: bool = Field(default=True) diff --git a/sdk/agenta/sdk/workflows/handlers.py b/sdk/agenta/sdk/workflows/handlers.py index fa95fa9654..6d31f0a5e8 100644 --- a/sdk/agenta/sdk/workflows/handlers.py +++ b/sdk/agenta/sdk/workflows/handlers.py @@ -1,10 +1,14 @@ import json import math +import os import re +import socket +import ipaddress import traceback from difflib import SequenceMatcher from json import dumps, loads -from typing import Any, Dict, List, Optional, Union +from typing import Any, Dict, List, Optional, Set, Union +from urllib.parse import urlparse import httpx @@ -49,6 +53,68 @@ log = get_module_logger(__name__) +_WEBHOOK_RESPONSE_MAX_BYTES = 1 * 1024 * 1024.0 # 1 MB +_WEBHOOK_ALLOW_INSECURE = ( + os.getenv("AGENTA_WEBHOOK_ALLOW_INSECURE") or "true" +).lower() in {"true", "1", "t", "y", "yes", "on", "enable", "enabled"} + + +def _is_blocked_ip(ip: ipaddress._BaseAddress) -> bool: + if _WEBHOOK_ALLOW_INSECURE: + return False + return ( + ip.is_private + or ip.is_loopback + or ip.is_link_local + or ip.is_reserved + or ip.is_multicast + or ip.is_unspecified + ) + + +def _validate_webhook_url(url: str) -> None: + if not url: + raise ValueError("Webhook URL is required.") + + parsed = urlparse(url) + scheme = parsed.scheme.lower() + if scheme not in {"http", "https"}: + raise ValueError("Webhook URL must use http or https.") + if scheme == "http" and not _WEBHOOK_ALLOW_INSECURE: + raise ValueError("Webhook URL must use https.") + if not parsed.netloc: + raise ValueError("Webhook URL must include a host.") + if parsed.username or parsed.password: + raise ValueError("Webhook URL must not include credentials.") + + hostname = (parsed.hostname or "").lower() + if not hostname: + raise ValueError("Webhook URL must include a valid hostname.") + if ( + hostname in {"localhost", "localhost.localdomain"} + and not _WEBHOOK_ALLOW_INSECURE + ): + raise ValueError("Webhook URL hostname is not allowed.") + + try: + ip = ipaddress.ip_address(hostname) + if _is_blocked_ip(ip): + raise ValueError("Webhook URL resolves to a blocked IP range.") + return + except ValueError: + pass + + try: + addresses = { + ipaddress.ip_address(info[4][0]) + for info in socket.getaddrinfo(hostname, None) + } + except socket.gaierror as exc: + raise ValueError("Webhook URL hostname could not be resolved.") from exc + + if not addresses or any(_is_blocked_ip(ip) for ip in addresses): + raise ValueError("Webhook URL resolves to a blocked IP range.") + def _configure_litellm(): """Lazy configuration of litellm - only imported when needed.""" @@ -701,6 +767,14 @@ async def auto_webhook_test_v0( raise MissingConfigurationParameterV0Error(path="webhook_url") webhook_url = str(parameters["webhook_url"]) + try: + _validate_webhook_url(webhook_url) + except ValueError as exc: + raise InvalidConfigurationParameterV0Error( + path="webhook_url", + expected="http/https URL", + got=webhook_url, + ) from exc if "correct_answer_key" not in parameters: raise MissingConfigurationParameterV0Error(path="correct_answer_key") @@ -750,6 +824,7 @@ async def auto_webhook_test_v0( response = await client.post( url=webhook_url, json=json_payload, + timeout=httpx.Timeout(10.0, connect=5.0), ) except Exception as e: raise WebhookClientV0Error( @@ -762,12 +837,18 @@ async def auto_webhook_test_v0( message=response.json(), ) + content_length = response.headers.get("content-length") + if content_length and int(content_length) > _WEBHOOK_RESPONSE_MAX_BYTES: + raise WebhookClientV0Error(message="Webhook response exceeded size limit.") + + response_bytes = response.content + if len(response_bytes) > _WEBHOOK_RESPONSE_MAX_BYTES: + raise WebhookClientV0Error(message="Webhook response exceeded size limit.") + try: - _outputs = response.json() + _outputs = json.loads(response_bytes) except Exception as e: - raise WebhookClientV0Error( - message=str(e), - ) from e + raise WebhookClientV0Error(message=str(e)) from e # -------------------------------------------------------------------------- if isinstance(_outputs, (int, float)): diff --git a/sdk/pyproject.toml b/sdk/pyproject.toml index 4b447f402b..fbdc60af4f 100644 --- a/sdk/pyproject.toml +++ b/sdk/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "agenta" -version = "0.77.0" +version = "0.78.0" description = "The SDK for agenta is an open-source LLMOps platform." readme = "README.md" authors = [ diff --git a/web/ee/package.json b/web/ee/package.json index 1f3c1b000e..d7e4f3dfc4 100644 --- a/web/ee/package.json +++ b/web/ee/package.json @@ -1,6 +1,6 @@ { "name": "@agenta/ee", - "version": "0.77.0", + "version": "0.78.0", "private": true, "engines": { "node": ">=18" diff --git a/web/oss/package.json b/web/oss/package.json index b675f4536a..ed15f4fd8f 100644 --- a/web/oss/package.json +++ b/web/oss/package.json @@ -1,6 +1,6 @@ { "name": "@agenta/oss", - "version": "0.77.0", + "version": "0.78.0", "private": true, "engines": { "node": ">=18" diff --git a/web/oss/src/components/EvalRunDetails/Table.tsx b/web/oss/src/components/EvalRunDetails/Table.tsx index e274a3d36a..8b5a83313e 100644 --- a/web/oss/src/components/EvalRunDetails/Table.tsx +++ b/web/oss/src/components/EvalRunDetails/Table.tsx @@ -833,6 +833,7 @@ const EvalRunDetailsTable = ({ datasetStore={evaluationPreviewDatasetStore} tableScope={tableScope} + store={store} columns={previewColumns.columns} rowKey={(record) => record.key} tableClassName={clsx( diff --git a/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/useAnnotationState.ts b/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/useAnnotationState.ts index 9e47ee726b..6a906e69e4 100644 --- a/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/useAnnotationState.ts +++ b/web/oss/src/components/EvalRunDetails/components/views/SingleScenarioViewerPOC/ScenarioAnnotationPanel/useAnnotationState.ts @@ -31,9 +31,29 @@ function getMetricFieldsFromEvaluator( ? ((rawProp as Record).anyOf as unknown[])[0] : rawProp const propObj = prop as Record - const type = propObj?.type as string | undefined + const rawType = propObj?.type as string | string[] | undefined + + if (!rawType) continue + + if (Array.isArray(rawType)) { + const enumValues = + (propObj.enum as unknown[] | undefined)?.filter( + (value) => value !== null && value !== undefined && value !== "", + ) || [] + const filteredTypes = rawType.filter((value) => value !== "null") + if (filteredTypes.length === 0) continue + const baseType = filteredTypes[0] + fields[key] = { + value: baseType === "string" ? "" : null, + type: filteredTypes, + enum: enumValues as string[], + minimum: propObj.minimum as number | undefined, + maximum: propObj.maximum as number | undefined, + } + continue + } - if (!type) continue + const type = rawType if (type === "array") { const items = propObj.items as Record | undefined @@ -95,14 +115,35 @@ function getMetricsFromAnnotation( ? ((rawProp as Record).anyOf as unknown[])[0] : rawProp const propObj = prop as Record - const type = propObj?.type as string | undefined + const rawType = propObj?.type as string | string[] | undefined - if (!type) continue + if (!rawType) continue // Check if value exists - be careful with boolean false which is falsy but valid const hasValue = key in outputs const value = hasValue ? outputs[key] : undefined + if (Array.isArray(rawType)) { + const enumValues = + (propObj.enum as unknown[] | undefined)?.filter( + (item) => item !== null && item !== undefined && item !== "", + ) || [] + const filteredTypes = rawType.filter((item) => item !== "null") + if (filteredTypes.length === 0) continue + const baseType = filteredTypes[0] + const defaultValue = baseType === "string" ? "" : null + fields[key] = { + value: hasValue ? value : defaultValue, + type: filteredTypes, + enum: enumValues as string[], + minimum: propObj.minimum as number | undefined, + maximum: propObj.maximum as number | undefined, + } + continue + } + + const type = rawType + if (type === "array") { const items = propObj.items as Record | undefined fields[key] = { diff --git a/web/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/index.tsx b/web/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/index.tsx index 3e0a0e644d..5659c70f47 100644 --- a/web/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/index.tsx +++ b/web/oss/src/components/EvaluationRunsTablePOC/hooks/useEvaluationRunsColumns/index.tsx @@ -176,23 +176,6 @@ const useEvaluationRunsColumns = ({ : newBlueprint }, [stableRows, evaluationKind]) - const ensuredReferenceBlueprint = useMemo(() => { - if (evaluationKind !== "all") { - return referenceBlueprint - } - const hasQueryColumn = referenceBlueprint.some((descriptor) => descriptor.role === "query") - if (hasQueryColumn) { - return referenceBlueprint - } - const fallbackDescriptor: ReferenceColumnDescriptor = { - slotIndex: referenceBlueprint.length, - role: "query", - roleOrdinal: 1, - label: "Query", - } - return [...referenceBlueprint, fallbackDescriptor] - }, [evaluationKind, referenceBlueprint]) - const invocationMetricDescriptors = useMemo( () => INVOCATION_METRIC_KEYS.map( @@ -741,7 +724,7 @@ const useEvaluationRunsColumns = ({ }) } - ensuredReferenceBlueprint + referenceBlueprint .filter( (descriptor) => descriptor.role !== "evaluator" && descriptor.role !== "variant", ) @@ -857,7 +840,7 @@ const useEvaluationRunsColumns = ({ }, [ evaluationKind, metricNodes, - ensuredReferenceBlueprint, + referenceBlueprint, onOpenDetails, onVariantNavigation, onTestsetNavigation, diff --git a/web/oss/src/components/EvaluationRunsTablePOC/utils/referenceSchema.ts b/web/oss/src/components/EvaluationRunsTablePOC/utils/referenceSchema.ts index 34de7d9e0a..3a22c90c12 100644 --- a/web/oss/src/components/EvaluationRunsTablePOC/utils/referenceSchema.ts +++ b/web/oss/src/components/EvaluationRunsTablePOC/utils/referenceSchema.ts @@ -171,96 +171,69 @@ export const buildReferenceSequence = (meta?: PreviewRunColumnMeta | null): Refe return slots } -interface DraftEntry { - slotIndex: number - totals: number - roleCounts: Partial> +interface RoleStats { + count: number stepTypes: Set origins: Set } -const pickDominantRole = (counts: Partial>): ReferenceRole | null => { - let winner: ReferenceRole | null = null - let max = 0 - ROLE_ORDER.forEach((role) => { - const count = counts[role] ?? 0 - if (count > max) { - winner = role - max = count - } - }) - return winner -} - const buildFallbackBlueprint = (evaluationKind: EvaluationRunKind): ReferenceColumnDescriptor[] => { const fallbackRoles = ROLE_EVALUATION_FALLBACK[evaluationKind] ?? ROLE_ORDER - const perRoleOrdinal: Record = { - application: 0, - variant: 0, - testset: 0, - query: 0, - evaluator: 0, - } - return fallbackRoles.map((role, index) => { - const ordinal = ++perRoleOrdinal[role] - return { - slotIndex: index, - role, - roleOrdinal: ordinal, - label: ordinal === 1 ? ROLE_LABEL[role] : `${ROLE_LABEL[role]} ${ordinal}`, - } - }) + return fallbackRoles.map((role, index) => ({ + slotIndex: index, + role, + roleOrdinal: 1, + label: ROLE_LABEL[role], + })) } export const buildReferenceBlueprint = ( rows: EvaluationRunTableRow[], evaluationKind: EvaluationRunKind, ): ReferenceColumnDescriptor[] => { - const drafts: DraftEntry[] = [] + // Collect which roles appear across all rows + const roleStats: Record = { + testset: {count: 0, stepTypes: new Set(), origins: new Set()}, + query: {count: 0, stepTypes: new Set(), origins: new Set()}, + application: {count: 0, stepTypes: new Set(), origins: new Set()}, + variant: {count: 0, stepTypes: new Set(), origins: new Set()}, + evaluator: {count: 0, stepTypes: new Set(), origins: new Set()}, + } + + let hasAnyData = false + rows.forEach((row) => { if (row.__isSkeleton || !row.previewMeta) return + hasAnyData = true const sequence = buildReferenceSequence(row.previewMeta) - sequence.forEach((slot, index) => { - const draft = drafts[index] ?? { - slotIndex: index, - totals: 0, - roleCounts: {}, - stepTypes: new Set(), - origins: new Set(), - } - draft.totals += 1 - draft.roleCounts[slot.role] = (draft.roleCounts[slot.role] ?? 0) + 1 - if (slot.stepType) draft.stepTypes.add(slot.stepType) - if (slot.origin) draft.origins.add(slot.origin) - drafts[index] = draft + sequence.forEach((slot) => { + const stats = roleStats[slot.role] + stats.count += 1 + if (slot.stepType) stats.stepTypes.add(slot.stepType) + if (slot.origin) stats.origins.add(slot.origin) }) }) - const effectiveDrafts = drafts.filter((draft) => draft.totals > 0) - if (!effectiveDrafts.length) { + if (!hasAnyData) { return buildFallbackBlueprint(evaluationKind) } - const perRoleOrdinal: Record = { - application: 0, - variant: 0, - testset: 0, - query: 0, - evaluator: 0, - } - - return effectiveDrafts.map((draft) => { - const role = pickDominantRole(draft.roleCounts) ?? "application" - const ordinal = ++perRoleOrdinal[role] - return { - slotIndex: draft.slotIndex, + // Build columns for roles that appear in the data, ordered by ROLE_ORDER + const result: ReferenceColumnDescriptor[] = [] + ROLE_ORDER.forEach((role) => { + const stats = roleStats[role] + if (stats.count === 0) return + result.push({ + slotIndex: result.length, role, - roleOrdinal: ordinal, - label: ordinal === 1 ? ROLE_LABEL[role] : `${ROLE_LABEL[role]} ${ordinal}`, - sampleOrigin: draft.origins.values().next().value ?? null, - sampleStepType: draft.stepTypes.values().next().value ?? null, - } + roleOrdinal: 1, + label: ROLE_LABEL[role], + sampleStepType: stats.stepTypes.values().next().value ?? null, + sampleOrigin: stats.origins.values().next().value ?? null, + }) }) + + return result.length > 0 ? result : buildFallbackBlueprint(evaluationKind) } export const getSlotByRoleOrdinal = ( diff --git a/web/oss/src/components/Playground/Components/PlaygroundGenerations/assets/GenerationCompletionRow/DefaultView.tsx b/web/oss/src/components/Playground/Components/PlaygroundGenerations/assets/GenerationCompletionRow/DefaultView.tsx index 091ec205aa..5b0496fae0 100644 --- a/web/oss/src/components/Playground/Components/PlaygroundGenerations/assets/GenerationCompletionRow/DefaultView.tsx +++ b/web/oss/src/components/Playground/Components/PlaygroundGenerations/assets/GenerationCompletionRow/DefaultView.tsx @@ -46,6 +46,10 @@ const DefaultView = ({ ), ) as string[] + if (inputOnly && variableIds.length === 0) { + return null + } + return ( <>
({...prev, [id]: !prev[id]})) }, []) + if (inputOnly && variableIds.length === 0) { + return null + } + if (isCollapsed && !inputOnly) { return (
@@ -132,7 +136,7 @@ const SingleView = ({
} + icon={} size="small" type="text" onClick={() => openFocusDrawer({rowId, variantId})} @@ -179,7 +183,7 @@ const SingleView = ({ return (
0}, @@ -201,7 +205,7 @@ const SingleView = ({
} + icon={} size="small" type="text" onClick={() => openFocusDrawer({rowId, variantId})} diff --git a/web/oss/src/components/Playground/Components/PlaygroundTool/index.tsx b/web/oss/src/components/Playground/Components/PlaygroundTool/index.tsx index 999bd06f3d..95f43d817f 100644 --- a/web/oss/src/components/Playground/Components/PlaygroundTool/index.tsx +++ b/web/oss/src/components/Playground/Components/PlaygroundTool/index.tsx @@ -13,6 +13,7 @@ import {stripAgentaMetadataDeep} from "@/oss/lib/shared/variant/valueHelpers" import {promptsAtomFamily} from "@/oss/state/newPlayground/core/prompts" import {appUriInfoAtom} from "@/oss/state/variant/atoms/fetcher" +import toolsSpecs from "../PlaygroundVariantConfigPrompt/assets/tools.specs.json" import PlaygroundVariantPropertyControlWrapper from "../PlaygroundVariantPropertyControl/assets/PlaygroundVariantPropertyControlWrapper" import PromptMessageContentOptions from "../PlaygroundVariantPropertyControl/assets/PromptMessageContent/assets/PromptMessageContentOptions" import SharedEditor from "../SharedEditor" @@ -77,6 +78,67 @@ function deepEqual(a: any, b: any): boolean { return stableStringify(a) === stableStringify(b) } +function formatBuiltinLabel(value: string): string { + return value + .split("_") + .filter(Boolean) + .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)) + .join(" ") +} + +function inferBuiltinLabel(toolObj: ToolObj): string | undefined { + if (!toolObj || typeof toolObj !== "object") return undefined + const typeValue = (toolObj as any).type + if (typeof typeValue === "string" && typeValue !== "function") { + return formatBuiltinLabel(typeValue) + } + const keys = Object.keys(toolObj).filter((key) => key !== "type" && key !== "function") + if (keys.length === 0) return undefined + return formatBuiltinLabel(keys[0]) +} + +function inferIsBuiltinTool(toolObj: ToolObj): boolean { + if (!toolObj || typeof toolObj !== "object") return false + const keys = Object.keys(toolObj) + if (keys.length === 0) return false + const typeValue = (toolObj as any).type + const hasFunction = typeValue === "function" || "function" in (toolObj as any) + if (hasFunction) return false + if (typeof typeValue === "string") return true + return keys.some((key) => key !== "type") +} + +interface BuiltinToolInfo { + providerKey?: string + toolCode?: string +} + +function matchesToolPayload(toolObj: ToolObj, payload: Record): boolean { + if (!toolObj || typeof toolObj !== "object" || !payload) return false + const toolObjAny = toolObj as any + if (typeof payload.type === "string" && toolObjAny.type === payload.type) return true + if (typeof payload.name === "string" && toolObjAny.name === payload.name) return true + const payloadKeys = Object.keys(payload) + if (payloadKeys.length === 1 && payloadKeys[0] in toolObjAny) return true + return false +} + +function inferBuiltinToolInfo(toolObj: ToolObj): BuiltinToolInfo | undefined { + if (!toolObj || typeof toolObj !== "object") return undefined + const specs = toolsSpecs as Record> + for (const [providerKey, tools] of Object.entries(specs)) { + for (const [toolCode, toolSpec] of Object.entries(tools)) { + const payloads = Array.isArray(toolSpec) ? toolSpec : [toolSpec] + for (const payload of payloads) { + if (matchesToolPayload(toolObj, payload as Record)) { + return {providerKey, toolCode} + } + } + } + } + return undefined +} + function toToolObj(value: unknown): ToolObj { try { if (typeof value === "string") return value ? (JSON5.parse(value) as ToolObj) : {} @@ -322,6 +384,16 @@ const PlaygroundTool: React.FC = ({ ), ) + const inferredBuiltinTool = useMemo(() => inferIsBuiltinTool(toolObj), [toolObj]) + const isBuiltinTool = builtinMeta?.isBuiltinTool || inferredBuiltinTool + const inferredToolInfo = useMemo(() => inferBuiltinToolInfo(toolObj), [toolObj]) + const fallbackToolLabel = useMemo(() => inferBuiltinLabel(toolObj), [toolObj]) + const fallbackProvider = inferredToolInfo?.providerKey + ? TOOL_PROVIDERS_META[inferredToolInfo.providerKey] + : undefined + const fallbackIcon = + fallbackProvider?.iconKey != null ? LLMIconMap[fallbackProvider.iconKey] : undefined + useAtomValue(variantByRevisionIdAtomFamily(variantId)) const appUriInfo = useAtomValue(appUriInfoAtom) const setPrompts = useSetAtom( @@ -377,7 +449,7 @@ const PlaygroundTool: React.FC = ({ editorProps={{ codeOnly: true, noProvider: true, - validationSchema: builtinMeta?.isBuiltinTool ? undefined : TOOL_SCHEMA, + validationSchema: isBuiltinTool ? undefined : TOOL_SCHEMA, }} handleChange={(e) => { if (isReadOnly) return @@ -402,10 +474,17 @@ const PlaygroundTool: React.FC = ({ minimized={minimized} onToggleMinimize={() => setMinimized((v) => !v)} onDelete={deleteMessage} - isBuiltinTool={builtinMeta?.isBuiltinTool} - builtinProviderLabel={builtinMeta?.providerLabel} - builtinToolLabel={builtinMeta?.toolLabel} - builtinIcon={builtinMeta?.Icon} + isBuiltinTool={isBuiltinTool} + builtinProviderLabel={ + builtinMeta?.providerLabel ?? + (fallbackProvider?.label || inferredToolInfo?.providerKey) + } + builtinToolLabel={ + builtinMeta?.toolLabel ?? + inferredToolInfo?.toolCode ?? + fallbackToolLabel + } + builtinIcon={builtinMeta?.Icon ?? fallbackIcon} /> } /> diff --git a/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/transforms.ts b/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/transforms.ts index f391ba99ff..f92e0d52b3 100644 --- a/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/transforms.ts +++ b/web/oss/src/components/SharedDrawers/AnnotateDrawer/assets/transforms.ts @@ -568,8 +568,8 @@ export const generateNewEvaluatorPayloadData = ({ acc[metric.name] = { anyOf: [ { - type: ["string", "null"], - enum: [...(acc[metric.name].enum?.filter(Boolean) || []), null], + type: ["string"], + enum: acc[metric.name].enum?.filter(Boolean) || [], }, ], } diff --git a/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionContent/index.tsx b/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionContent/index.tsx index 7d879a2923..fdd2773b86 100644 --- a/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionContent/index.tsx +++ b/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionContent/index.tsx @@ -22,12 +22,29 @@ const SessionContent = () => { .map((trace: any, index: number) => { const messages = extractTraceData(trace) + let defaultHiddenCount = 0 + if (index > 0) { + // Find the index of the last user message + let lastUserIndex = -1 + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].role === "user") { + lastUserIndex = i + break + } + } + + if (lastUserIndex !== -1) { + defaultHiddenCount = lastUserIndex + } + } + return (
) diff --git a/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionMessagePanel/index.tsx b/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionMessagePanel/index.tsx index 403f1d8199..55157d179e 100644 --- a/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionMessagePanel/index.tsx +++ b/web/oss/src/components/SharedDrawers/SessionDrawer/components/SessionMessagePanel/index.tsx @@ -1,6 +1,7 @@ -import {useMemo, useRef} from "react" +import {useMemo, useRef, useState} from "react" -import {Collapse, CollapseProps, Tag, Typography} from "antd" +import {MinusOutlined, PlusOutlined} from "@ant-design/icons" +import {Button, Collapse, CollapseProps, Tag, Typography} from "antd" import clsx from "clsx" import {useAtomValue} from "jotai" @@ -20,16 +21,19 @@ interface SessionMessagePanelProps extends CollapseProps { bgColor?: string fullEditorHeight?: boolean trace?: any + defaultHiddenCount?: number } const SessionMessagePanel = ({ value: incomingValue, label, trace, + defaultHiddenCount = 0, ...props }: SessionMessagePanelProps) => { const isAnnotationVisible = useAtomValue(isAnnotationVisibleAtom) const editorRef = useRef(null) + const [showHidden, setShowHidden] = useState(false) // Get span ID for AddToTestsetButton - drawer will fetch data from entity cache const spanIds = useMemo(() => { @@ -72,9 +76,11 @@ const SessionMessagePanel = ({
- {(incomingValue as any[])?.map( - (val: any, index: number) => { - return ( + {/* Hidden messages (shown when toggled) */} + {showHidden && + (incomingValue as any[]) + ?.slice(0, defaultHiddenCount) + .map((val: any, index: number) => (
- ) - }, + ))} + + {/* Toggle Button */} + {defaultHiddenCount > 0 && ( +
+
+ +
+
)} + + {/* Always visible messages */} + {(incomingValue as any[]) + ?.slice(defaultHiddenCount) + .map((val: any, index: number) => ( +
+ +
+ ))}
diff --git a/web/oss/src/components/SidebarBanners/data/changelog.json b/web/oss/src/components/SidebarBanners/data/changelog.json index 4bdf221a57..f53f824f4d 100644 --- a/web/oss/src/components/SidebarBanners/data/changelog.json +++ b/web/oss/src/components/SidebarBanners/data/changelog.json @@ -1,4 +1,10 @@ [ + { + "id": "changelog-2026-01-13-playground-ux", + "title": "Playground UX Improvements", + "description": "See provider costs, run evaluations directly, and collapse test cases.", + "link": "https://agenta.ai/docs/changelog/playground-ux-improvements-jan-2026" + }, { "id": "changelog-2026-01-09-chat-sessions", "title": "Chat Sessions in Observability", diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx index cac33be7b2..cffdfdcd23 100644 --- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx +++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/NewEvaluationModalInner.tsx @@ -107,14 +107,13 @@ const NewEvaluationModalInner = ({ const [selectedTestsetRevisionId, setSelectedTestsetRevisionId] = useState("") const [selectedTestsetName, setSelectedTestsetName] = useState("") const [selectedTestsetVersion, setSelectedTestsetVersion] = useState(null) - // Initialize with at most one pre-selected variant (e.g., from playground) - const [selectedVariantRevisionIds, setSelectedVariantRevisionIds] = useState(() => { - const first = preSelectedVariantIds?.[0] - return first ? [first] : [] - }) + // Initialize with pre-selected variants (e.g., from playground comparison mode) + const [selectedVariantRevisionIds, setSelectedVariantRevisionIds] = useState(() => + preSelectedVariantIds?.length ? [...preSelectedVariantIds] : [], + ) const [selectedEvalConfigs, setSelectedEvalConfigs] = useState([]) // If variants are pre-selected, start on testset panel; otherwise follow normal flow - const hasPreSelectedVariants = Boolean(preSelectedVariantIds?.[0]) + const hasPreSelectedVariants = Boolean(preSelectedVariantIds?.length) const [activePanel, setActivePanel] = useState(() => getInitialPanel(hasPreSelectedVariants, isAppScoped), ) @@ -212,6 +211,9 @@ const NewEvaluationModalInner = ({ // Memoised base (deterministic) part of generated name (without random suffix) const generatedNameBase = useMemo(() => { if (!selectedVariantRevisionIds.length || !selectedTestsetName) return "" + if (selectedVariantRevisionIds.length > 1) { + return `${selectedVariantRevisionIds.length}-variants-${selectedTestsetName}` + } const variant = filteredVariants?.find((v) => selectedVariantRevisionIds.includes(v.id)) if (!variant) return "" return `${variant.variantName}-v${variant.revision}-${selectedTestsetName}` diff --git a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectVariantSection.tsx b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectVariantSection.tsx index 3e240de872..ef9973d2d2 100644 --- a/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectVariantSection.tsx +++ b/web/oss/src/components/pages/evaluations/NewEvaluation/Components/SelectVariantSection.tsx @@ -23,7 +23,6 @@ const NoResultsFound = dynamic( const SelectVariantSection = ({ selectedVariantRevisionIds, - selectedTestsetId, className, setSelectedVariantRevisionIds, handlePanelChange, @@ -49,6 +48,10 @@ const SelectVariantSection = ({ const onSelectVariant = useCallback( (selectedRowKeys: React.Key[]) => { + if (evaluationType === "auto") { + setSelectedVariantRevisionIds(selectedRowKeys as string[]) + return + } const selectedId = selectedRowKeys[0] as string | undefined if (selectedId) { setSelectedVariantRevisionIds([selectedId]) @@ -57,7 +60,7 @@ const SelectVariantSection = ({ setSelectedVariantRevisionIds([]) } }, - [setSelectedVariantRevisionIds, handlePanelChange], + [evaluationType, handlePanelChange, setSelectedVariantRevisionIds], ) const onRowClick = useCallback( @@ -65,9 +68,21 @@ const SelectVariantSection = ({ const _record = record as EnhancedVariant & { children: EnhancedVariant[] } + if (evaluationType === "auto") { + const nextSelected = selectedVariantRevisionIds.includes(_record.id) + ? selectedVariantRevisionIds.filter((id) => id !== _record.id) + : [...selectedVariantRevisionIds, _record.id] + setSelectedVariantRevisionIds(nextSelected) + return + } onSelectVariant([_record.id]) }, - [selectedVariantRevisionIds, onSelectVariant], + [ + evaluationType, + onSelectVariant, + selectedVariantRevisionIds, + setSelectedVariantRevisionIds, + ], ) const variantsNonNull = (filteredVariant || []) as EnhancedVariant[] @@ -85,7 +100,7 @@ const SelectVariantSection = ({ { onSelectVariant(selectedRowKeys) diff --git a/web/oss/src/state/newObservability/atoms/queries.ts b/web/oss/src/state/newObservability/atoms/queries.ts index 2593fc279a..2512350b02 100644 --- a/web/oss/src/state/newObservability/atoms/queries.ts +++ b/web/oss/src/state/newObservability/atoms/queries.ts @@ -501,8 +501,8 @@ export const sessionTimeRangeAtomFamily = atomFamily((sessionId: string) => const sorted = get(sessionSortedTracesAtomFamily(sessionId)) if (!sorted.length) return {startTime: undefined, endTime: undefined} return { - startTime: sorted[0].created_at, - endTime: sorted[sorted.length - 1].created_at, + startTime: sorted[0].start_time || sorted[0].created_at, + endTime: sorted[sorted.length - 1].end_time || sorted[sorted.length - 1].created_at, } }), ) diff --git a/web/oss/src/styles/editor-theme.css b/web/oss/src/styles/editor-theme.css index c1bedb8ad3..d466eb0aa0 100644 --- a/web/oss/src/styles/editor-theme.css +++ b/web/oss/src/styles/editor-theme.css @@ -38,7 +38,7 @@ h1 { /* margin: 20px auto 20px auto; */ margin-left: 0px; border-radius: 2px; - max-width: 600px; + max-width: 100%; color: #000; position: relative; line-height: 20px; diff --git a/web/package.json b/web/package.json index c65a78d989..0ae3c4ee7d 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "agenta-web", - "version": "0.77.0", + "version": "0.78.0", "workspaces": [ "ee", "oss",