mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
docs(proxy): describe /v1/decisions and /v1/systemone in Swagger UI (#45838)
* docs(proxy): describe /v1/decisions and /v1/systemone in Swagger UI Add summaries, descriptions, inlined request body schemas with examples and typed 200 responses to the decision routes and their aliases, so the OpenAPI document and the lazy snapshot show both request formats Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): cap _inlined schema walk depth for the recursion check Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: kerry <kerry@berri.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
5b6ecab52d
commit
bc2cc42340
8 changed files with 2961 additions and 80 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -437,3 +437,41 @@ class CustomOpenAPISpec:
|
|||
|
||||
# Add responses API request schema
|
||||
return CustomOpenAPISpec.add_responses_api_request_schema(with_embeddings)
|
||||
|
||||
|
||||
_MAX_INLINE_DEPTH: Final = 32
|
||||
|
||||
|
||||
def _inlined(node: JsonValue, defs: Mapping[str, JsonValue], depth: int = 0) -> JsonValue:
|
||||
if depth >= _MAX_INLINE_DEPTH or not isinstance(node, (list, dict)):
|
||||
return node
|
||||
if isinstance(node, list):
|
||||
return [_inlined(item, defs, depth + 1) for item in node]
|
||||
ref: Final = node.get("$ref")
|
||||
if isinstance(ref, str) and ref.startswith("#/$defs/"):
|
||||
target: Final = _inlined(defs[ref.removeprefix("#/$defs/")], defs, depth + 1)
|
||||
siblings: Final = {key: _inlined(value, defs, depth + 1) for key, value in node.items() if key != "$ref"}
|
||||
return {**target, **siblings} if isinstance(target, dict) else siblings
|
||||
if "propertyName" in node and "mapping" in node:
|
||||
return {"propertyName": node["propertyName"]}
|
||||
return {key: _inlined(value, defs, depth + 1) for key, value in node.items() if key != "$defs"}
|
||||
|
||||
|
||||
def inline_request_body(model_class: type, example: Mapping[str, object]) -> JsonObject:
|
||||
"""
|
||||
Build an ``openapi_extra["requestBody"]`` for a route that reads its body with ``request.body()``.
|
||||
|
||||
Swagger UI resolves ``$ref`` against the whole document, so a schema placed inline in an operation
|
||||
cannot keep Pydantic's ``#/$defs/...`` references. Every reference is expanded in place instead,
|
||||
which also keeps the operation self-contained inside the lazy OpenAPI snapshot.
|
||||
"""
|
||||
schema: Final = CustomOpenAPISpec.get_pydantic_schema(model_class)
|
||||
media: Final[JsonObject] = {"example": cast(JsonValue, dict(example))} # cast-ok: JSON example literal
|
||||
if schema is None:
|
||||
return {"required": True, "content": {"application/json": media}}
|
||||
raw_defs: Final = schema.get("$defs")
|
||||
defs: Final[Mapping[str, JsonValue]] = raw_defs if isinstance(raw_defs, dict) else MappingProxyType({})
|
||||
return {
|
||||
"required": True,
|
||||
"content": {"application/json": {"schema": _inlined(schema, defs), **media}},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,15 @@ from pydantic import TypeAdapter, ValidationError
|
|||
from litellm.exceptions import BadRequestError
|
||||
from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth
|
||||
from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing
|
||||
from litellm.types.decisions import DecisionsRequestBody, OpenAIDecisionRequestBody
|
||||
from litellm.proxy.common_utils.custom_openapi_spec import inline_request_body
|
||||
from litellm.types.decisions import (
|
||||
DecisionsRequest,
|
||||
DecisionsRequestBody,
|
||||
DecisionsResponse,
|
||||
OpenAIDecisionRequest,
|
||||
OpenAIDecisionRequestBody,
|
||||
OpenAIDecisionResponse,
|
||||
)
|
||||
|
||||
router: Final = APIRouter()
|
||||
_REQUEST_DATA_ADAPTER: Final[TypeAdapter[dict[str, object]]] = TypeAdapter(dict[str, object])
|
||||
|
|
@ -20,6 +28,51 @@ _OPENAI_DECISION_REQUEST_BODY_ADAPTER: Final[TypeAdapter[OpenAIDecisionRequestBo
|
|||
_GENERAL_SETTINGS_ADAPTER: Final[TypeAdapter[dict[str, object]]] = TypeAdapter(dict[str, object])
|
||||
_OPTIONAL_STRING_ADAPTER: Final[TypeAdapter[str | None]] = TypeAdapter(str | None)
|
||||
_OPTIONAL_FLOAT_ADAPTER: Final[TypeAdapter[float | None]] = TypeAdapter(float | None)
|
||||
_SYSTEMONE_REQUEST_BODY: Final = inline_request_body(
|
||||
DecisionsRequest,
|
||||
{
|
||||
"model": "jev",
|
||||
"state": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today.",
|
||||
"questions": {
|
||||
"is_refund_request": {"type": "noul", "instructions": "Is the customer asking for a refund?"},
|
||||
"urgency": {
|
||||
"type": "choice",
|
||||
"instructions": "How urgent is this?",
|
||||
"criteria": {"low": "can wait a week", "high": "needs action today"},
|
||||
},
|
||||
"frustration": {
|
||||
"type": "score",
|
||||
"instructions": "How frustrated is the customer?",
|
||||
"criteria": ["calm", "mildly annoyed", "angry"],
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
_DECISIONS_REQUEST_BODY: Final = inline_request_body(
|
||||
OpenAIDecisionRequest,
|
||||
{
|
||||
"model": "luna",
|
||||
"input": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today.",
|
||||
"questions": [
|
||||
{"type": "predicate", "name": "is_refund_request", "instructions": "Is the customer asking for a refund?"},
|
||||
{
|
||||
"type": "choice",
|
||||
"name": "urgency",
|
||||
"instructions": "How urgent is this?",
|
||||
"choices": [
|
||||
{"value": "low", "description": "can wait a week"},
|
||||
{"value": "high", "description": "needs action today"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "score",
|
||||
"name": "frustration",
|
||||
"instructions": "How frustrated is the customer?",
|
||||
"levels": [{"label": "calm"}, {"label": "mildly annoyed"}, {"label": "angry"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
async def _invalid_request(
|
||||
|
|
@ -121,18 +174,35 @@ async def _process_decisions(
|
|||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_class=ORJSONResponse, # pyright: ignore[reportDeprecated] # required endpoint contract
|
||||
tags=["decisions"],
|
||||
summary="Ask named questions about a state (System One format)",
|
||||
responses={200: {"model": DecisionsResponse}},
|
||||
openapi_extra={"requestBody": _SYSTEMONE_REQUEST_BODY},
|
||||
)
|
||||
@router.post(
|
||||
"/systemone",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_class=ORJSONResponse, # pyright: ignore[reportDeprecated] # required endpoint contract
|
||||
tags=["decisions"],
|
||||
summary="Ask named questions about a state (System One format)",
|
||||
responses={200: {"model": DecisionsResponse}},
|
||||
openapi_extra={"requestBody": _SYSTEMONE_REQUEST_BODY},
|
||||
)
|
||||
async def systemone(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)],
|
||||
):
|
||||
"""
|
||||
Judge a `state` against 1 to 128 named `questions` and get one calibrated answer per question name.
|
||||
|
||||
Question types are `noul` (probability the answer is yes), `choice` (one label out of `criteria`, with
|
||||
`confidence` and `probabilities`) and `score` (an index into the `criteria` levels, with `confidence`,
|
||||
`legend` and `probabilities`). Requests that break these rules are rejected with a 400 before any provider
|
||||
is called. `model` is any decision model in the proxy model list. OpenAI decision models accept this format
|
||||
too, LiteLLM translates it and keeps the answers keyed by question name. Streaming is not supported.
|
||||
|
||||
[Docs](https://docs.litellm.ai/docs/decisions)
|
||||
"""
|
||||
return await _process_decisions(
|
||||
request=request,
|
||||
fastapi_response=fastapi_response,
|
||||
|
|
@ -146,18 +216,35 @@ async def systemone(
|
|||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_class=ORJSONResponse, # pyright: ignore[reportDeprecated] # required endpoint contract
|
||||
tags=["decisions"],
|
||||
summary="Ask questions about an input (OpenAI Decisions format)",
|
||||
responses={200: {"model": OpenAIDecisionResponse}},
|
||||
openapi_extra={"requestBody": _DECISIONS_REQUEST_BODY},
|
||||
)
|
||||
@router.post(
|
||||
"/decisions",
|
||||
dependencies=[Depends(user_api_key_auth)],
|
||||
response_class=ORJSONResponse, # pyright: ignore[reportDeprecated] # required endpoint contract
|
||||
tags=["decisions"],
|
||||
summary="Ask questions about an input (OpenAI Decisions format)",
|
||||
responses={200: {"model": OpenAIDecisionResponse}},
|
||||
openapi_extra={"requestBody": _DECISIONS_REQUEST_BODY},
|
||||
)
|
||||
async def decisions(
|
||||
request: Request,
|
||||
fastapi_response: Response,
|
||||
user_api_key_dict: Annotated[UserAPIKeyAuth, Depends(user_api_key_auth)],
|
||||
):
|
||||
"""
|
||||
Judge an `input` against 1 to 128 `questions` and get the answers back in the same order.
|
||||
|
||||
Question types are `predicate` (yes/no with `probability`), `choice` (one of the `choices`, with
|
||||
`probabilities`) and `score` (an index into `levels`, with `probabilities`). Requests that break these
|
||||
rules are rejected with a 400 before any provider is called. `model` is any decision model in the proxy
|
||||
model list. System One decision models accept this format too, LiteLLM translates the request and the
|
||||
answers. Streaming is not supported.
|
||||
|
||||
[Docs](https://docs.litellm.ai/docs/decisions)
|
||||
"""
|
||||
return await _process_decisions(
|
||||
request=request,
|
||||
fastapi_response=fastapi_response,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,13 @@ MAX_DECISION_QUESTIONS: Final = 128
|
|||
|
||||
class NoulQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["noul"]
|
||||
instructions: DecisionsJSON | None = None
|
||||
criteria: NoulCriteria | None = None
|
||||
instructions: Annotated[DecisionsJSON | None, Field(description="The yes/no question to ask about the state")] = (
|
||||
None
|
||||
)
|
||||
criteria: Annotated[
|
||||
NoulCriteria | None,
|
||||
Field(description="Optional descriptions of what makes the answer true and what makes it false"),
|
||||
] = None
|
||||
|
||||
model_config = ConfigDict(extra="allow", frozen=True)
|
||||
|
||||
|
|
@ -29,16 +34,24 @@ class NoulQuestion(LiteLLMPydanticObjectBase):
|
|||
|
||||
class ChoiceQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["choice"]
|
||||
instructions: DecisionsJSON | None = None
|
||||
criteria: Annotated[Mapping[str, DecisionsJSON | None], Field(min_length=1, max_length=255)]
|
||||
instructions: Annotated[DecisionsJSON | None, Field(description="The question to ask about the state")] = None
|
||||
criteria: Annotated[
|
||||
Mapping[str, DecisionsJSON | None],
|
||||
Field(min_length=1, max_length=255, description="Candidate labels mapped to an optional description of each"),
|
||||
]
|
||||
|
||||
model_config = ConfigDict(extra="allow", frozen=True)
|
||||
|
||||
|
||||
class ScoreQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["score"]
|
||||
instructions: DecisionsJSON | None = None
|
||||
criteria: Annotated[Sequence[DecisionsJSON], Field(min_length=1, max_length=10)]
|
||||
instructions: Annotated[DecisionsJSON | None, Field(description="The question to ask about the state")] = None
|
||||
criteria: Annotated[
|
||||
Sequence[DecisionsJSON],
|
||||
Field(
|
||||
min_length=1, max_length=10, description="Scale levels from lowest to highest. The score is a level index"
|
||||
),
|
||||
]
|
||||
|
||||
model_config = ConfigDict(extra="allow", frozen=True)
|
||||
|
||||
|
|
@ -55,14 +68,20 @@ DecisionQuestionMap: TypeAlias = Annotated[
|
|||
|
||||
|
||||
class DecisionsRequestBody(LiteLLMPydanticObjectBase):
|
||||
state: DecisionsJSON
|
||||
questions: DecisionQuestionMap
|
||||
state: Annotated[
|
||||
DecisionsJSON,
|
||||
Field(description="The thing being judged, such as a support ticket, a document or a chat transcript"),
|
||||
]
|
||||
questions: Annotated[
|
||||
DecisionQuestionMap,
|
||||
Field(description="Named noul, choice and score questions. Each key becomes a key in the answers"),
|
||||
]
|
||||
|
||||
model_config = ConfigDict(extra="allow", frozen=True)
|
||||
|
||||
|
||||
class DecisionsRequest(DecisionsRequestBody):
|
||||
model: str
|
||||
model: Annotated[str, Field(description="A decision model from the proxy model_list")]
|
||||
|
||||
|
||||
@with_config(ConfigDict(extra="allow"))
|
||||
|
|
@ -171,8 +190,8 @@ OpenAIDecisionInput: TypeAlias = str | Sequence[OpenAIDecisionInputMessage]
|
|||
|
||||
class OpenAIPredicateQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["predicate"]
|
||||
name: str | None = None
|
||||
instructions: str
|
||||
name: Annotated[str | None, Field(description="Echoed in the matching answer")] = None
|
||||
instructions: Annotated[str, Field(description="The yes/no question to ask about the input")]
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
|
|
@ -192,9 +211,12 @@ def systemone_choice_key(value: str | bool) -> str:
|
|||
|
||||
class OpenAIChoiceQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["choice"]
|
||||
name: str | None = None
|
||||
instructions: str
|
||||
choices: Annotated[Sequence[OpenAIChoiceOption], Field(min_length=2, max_length=255)]
|
||||
name: Annotated[str | None, Field(description="Echoed in the matching answer")] = None
|
||||
instructions: Annotated[str, Field(description="The question to ask about the input")]
|
||||
choices: Annotated[
|
||||
Sequence[OpenAIChoiceOption],
|
||||
Field(min_length=2, max_length=255, description="The options the model picks from. Values must be unique"),
|
||||
]
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
|
|
@ -215,9 +237,14 @@ class OpenAIScoreLevel(LiteLLMPydanticObjectBase):
|
|||
|
||||
class OpenAIScoreQuestion(LiteLLMPydanticObjectBase):
|
||||
type: Literal["score"]
|
||||
name: str | None = None
|
||||
instructions: str
|
||||
levels: Annotated[Sequence[OpenAIScoreLevel], Field(min_length=2, max_length=10)]
|
||||
name: Annotated[str | None, Field(description="Echoed in the matching answer")] = None
|
||||
instructions: Annotated[str, Field(description="The question to ask about the input")]
|
||||
levels: Annotated[
|
||||
Sequence[OpenAIScoreLevel],
|
||||
Field(
|
||||
min_length=2, max_length=10, description="Scale levels from lowest to highest. The score is a level index"
|
||||
),
|
||||
]
|
||||
|
||||
model_config = ConfigDict(extra="forbid", frozen=True)
|
||||
|
||||
|
|
@ -229,13 +256,32 @@ OpenAIDecisionQuestion: TypeAlias = Annotated[
|
|||
|
||||
|
||||
class OpenAIDecisionRequestBody(LiteLLMPydanticObjectBase):
|
||||
input: OpenAIDecisionInput
|
||||
questions: Annotated[Sequence[OpenAIDecisionQuestion], Field(min_length=1, max_length=MAX_DECISION_QUESTIONS)]
|
||||
safety_identifier: str | None = None
|
||||
input: Annotated[
|
||||
OpenAIDecisionInput,
|
||||
Field(
|
||||
description="The text being judged, or user messages whose content mixes input_text and input_image parts"
|
||||
),
|
||||
]
|
||||
questions: Annotated[
|
||||
Sequence[OpenAIDecisionQuestion],
|
||||
Field(
|
||||
min_length=1,
|
||||
max_length=MAX_DECISION_QUESTIONS,
|
||||
description="Predicate, choice and score questions. Answers come back in the same order",
|
||||
),
|
||||
]
|
||||
safety_identifier: Annotated[
|
||||
str | None,
|
||||
Field(description="A stable id for your end user. Sent to OpenAI and dropped for other providers"),
|
||||
] = None
|
||||
|
||||
model_config = ConfigDict(extra="allow", frozen=True)
|
||||
|
||||
|
||||
class OpenAIDecisionRequest(OpenAIDecisionRequestBody):
|
||||
model: Annotated[str, Field(description="A decision model from the proxy model_list")]
|
||||
|
||||
|
||||
class OpenAIPredicateAnswer(LiteLLMPydanticObjectBase):
|
||||
type: Literal["predicate"] = "predicate"
|
||||
name: str | None
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ IGNORE_FUNCTIONS = [
|
|||
"sanitize_oci_schema", # OCI: bounded by JSON-schema tree depth.
|
||||
"_freeze_for_dedupe", # OTEL: max depth set (default 16, _FREEZE_MAX_DEPTH); fails closed by returning repr(value) at the cap.
|
||||
"apply_json_merge_patch", # max depth set (_MAX_MERGE_DEPTH=64); fails closed by raising ValueError at the cap.
|
||||
"_inlined", # max depth set (_MAX_INLINE_DEPTH=32); passes the schema node through untouched at the cap. Walks a Pydantic JSON schema once at import time.
|
||||
"_filter_argument_value", # max depth set (DEFAULT_MAX_RECURSE_DEPTH); fails closed by blocking the tool call at the cap.
|
||||
"_redact_scanned_content", # max depth set (DEFAULT_MAX_RECURSE_DEPTH); fails closed by returning "[REDACTED]" at the cap.
|
||||
"replace_ciphertexts", # max depth set (DEFAULT_MAX_RECURSE_DEPTH); walks stored JSON, which has no cycles, and leaves values below the cap untouched.
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@ Simple unit tests for CustomOpenAPISpec class.
|
|||
Tests basic functionality of OpenAPI schema generation.
|
||||
"""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
import json
|
||||
from typing import Annotated, Literal
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from litellm.proxy.common_utils.custom_openapi_spec import CustomOpenAPISpec
|
||||
from litellm.proxy.common_utils.custom_openapi_spec import CustomOpenAPISpec, inline_request_body
|
||||
|
||||
|
||||
class TestCustomOpenAPISpec:
|
||||
|
|
@ -27,19 +30,13 @@ class TestCustomOpenAPISpec:
|
|||
},
|
||||
}
|
||||
|
||||
@patch(
|
||||
"litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema"
|
||||
)
|
||||
def test_add_chat_completion_request_schema(
|
||||
self, mock_add_schema, base_openapi_schema
|
||||
):
|
||||
@patch("litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema")
|
||||
def test_add_chat_completion_request_schema(self, mock_add_schema, base_openapi_schema):
|
||||
"""Test that chat completion schema is added correctly."""
|
||||
mock_add_schema.return_value = base_openapi_schema
|
||||
|
||||
with patch("litellm.proxy._types.ProxyChatCompletionRequest") as mock_model:
|
||||
result = CustomOpenAPISpec.add_chat_completion_request_schema(
|
||||
base_openapi_schema
|
||||
)
|
||||
result = CustomOpenAPISpec.add_chat_completion_request_schema(base_openapi_schema)
|
||||
|
||||
mock_add_schema.assert_called_once_with(
|
||||
openapi_schema=base_openapi_schema,
|
||||
|
|
@ -50,9 +47,7 @@ class TestCustomOpenAPISpec:
|
|||
)
|
||||
assert result == base_openapi_schema
|
||||
|
||||
@patch(
|
||||
"litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema"
|
||||
)
|
||||
@patch("litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema")
|
||||
def test_add_embedding_request_schema(self, mock_add_schema, base_openapi_schema):
|
||||
"""Test that embedding schema is added correctly."""
|
||||
mock_add_schema.return_value = base_openapi_schema
|
||||
|
|
@ -69,19 +64,13 @@ class TestCustomOpenAPISpec:
|
|||
)
|
||||
assert result == base_openapi_schema
|
||||
|
||||
@patch(
|
||||
"litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema"
|
||||
)
|
||||
def test_add_responses_api_request_schema(
|
||||
self, mock_add_schema, base_openapi_schema
|
||||
):
|
||||
@patch("litellm.proxy.common_utils.custom_openapi_spec.CustomOpenAPISpec.add_request_schema")
|
||||
def test_add_responses_api_request_schema(self, mock_add_schema, base_openapi_schema):
|
||||
"""Test that responses API schema is added correctly."""
|
||||
mock_add_schema.return_value = base_openapi_schema
|
||||
|
||||
with patch("litellm.types.llms.openai.ResponsesAPIRequestParams") as mock_model:
|
||||
result = CustomOpenAPISpec.add_responses_api_request_schema(
|
||||
base_openapi_schema
|
||||
)
|
||||
result = CustomOpenAPISpec.add_responses_api_request_schema(base_openapi_schema)
|
||||
|
||||
mock_add_schema.assert_called_once_with(
|
||||
openapi_schema=base_openapi_schema,
|
||||
|
|
@ -123,15 +112,11 @@ def test_defs_rewritten_in_add_schema_to_components():
|
|||
)
|
||||
assert "$defs" not in openapi_schema
|
||||
assert (
|
||||
openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"][
|
||||
"items"
|
||||
]["anyOf"][0]["$ref"]
|
||||
openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"]["items"]["anyOf"][0]["$ref"]
|
||||
== "#/components/schemas/UserMessage"
|
||||
)
|
||||
assert (
|
||||
openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"][
|
||||
"items"
|
||||
]["anyOf"][1]["$ref"]
|
||||
openapi_schema["components"]["schemas"]["SchemaName"]["properties"]["messages"]["items"]["anyOf"][1]["$ref"]
|
||||
== "#/components/schemas/AssistantMessage"
|
||||
)
|
||||
|
||||
|
|
@ -188,14 +173,8 @@ def test_rewrite_defs_refs():
|
|||
rewritten = CustomOpenAPISpec._rewrite_defs_refs(schema=schema, renames={})
|
||||
|
||||
assert "$defs" not in rewritten
|
||||
assert (
|
||||
rewritten["properties"]["messages"]["items"]["anyOf"][0]["$ref"]
|
||||
== "#/components/schemas/UserMessage"
|
||||
)
|
||||
assert (
|
||||
rewritten["properties"]["messages"]["items"]["anyOf"][1]["$ref"]
|
||||
== "#/components/schemas/AssistantMessage"
|
||||
)
|
||||
assert rewritten["properties"]["messages"]["items"]["anyOf"][0]["$ref"] == "#/components/schemas/UserMessage"
|
||||
assert rewritten["properties"]["messages"]["items"]["anyOf"][1]["$ref"] == "#/components/schemas/AssistantMessage"
|
||||
|
||||
|
||||
def test_get_pydantic_schema_generates_schema_for_responses_request_typed_dict():
|
||||
|
|
@ -390,3 +369,42 @@ def test_add_schema_to_components_renames_def_with_different_required_set():
|
|||
assert schemas["Block"]["required"] == ["type", "x", "y"]
|
||||
assert schemas["Req_Block"]["required"] == ["keys", "type", "x", "y"]
|
||||
assert schemas["Req"]["properties"]["b"]["$ref"] == "#/components/schemas/Req_Block"
|
||||
|
||||
|
||||
class _Cat(BaseModel):
|
||||
kind: Literal["cat"]
|
||||
lives: int = 9
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
|
||||
class _Dog(BaseModel):
|
||||
kind: Literal["dog"]
|
||||
good: bool = True
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
|
||||
class _Shelter(BaseModel):
|
||||
pets: dict[str, Annotated[_Cat | _Dog, Field(discriminator="kind")]]
|
||||
favorite: _Cat | None = None
|
||||
|
||||
model_config = ConfigDict(frozen=True)
|
||||
|
||||
|
||||
def test_inline_request_body_expands_every_ref_so_the_operation_stands_alone():
|
||||
body = inline_request_body(_Shelter, {"pets": {"tom": {"kind": "cat"}}})
|
||||
|
||||
media = body["content"]["application/json"]
|
||||
assert body["required"] is True
|
||||
assert media["example"] == {"pets": {"tom": {"kind": "cat"}}}
|
||||
assert "$ref" not in json.dumps(media["schema"])
|
||||
assert "$defs" not in media["schema"]
|
||||
pet_schema = media["schema"]["properties"]["pets"]["additionalProperties"]
|
||||
variants = {variant["properties"]["kind"]["const"]: variant for variant in pet_schema["oneOf"]}
|
||||
assert variants["cat"]["properties"]["lives"]["default"] == 9
|
||||
assert variants["dog"]["properties"]["good"]["type"] == "boolean"
|
||||
assert pet_schema["discriminator"] == {"propertyName": "kind"}
|
||||
favorite = media["schema"]["properties"]["favorite"]
|
||||
assert favorite["default"] is None
|
||||
assert any(option.get("properties", {}).get("lives") for option in favorite["anyOf"])
|
||||
|
|
|
|||
|
|
@ -12,17 +12,19 @@ import respx
|
|||
from fastapi import FastAPI
|
||||
from fastapi.routing import APIRoute
|
||||
from fastapi.testclient import TestClient
|
||||
from pydantic import TypeAdapter
|
||||
from starlette.routing import Match
|
||||
|
||||
import litellm
|
||||
from litellm.proxy._lazy_features import LAZY_FEATURES, LazyFeature, attach_lazy_features
|
||||
from litellm.proxy.decisions_endpoints.endpoints import decisions, systemone
|
||||
from litellm.proxy.decisions_endpoints.endpoints import decisions, router, systemone
|
||||
from litellm.proxy.pass_through_endpoints.pass_through_endpoints import SafeRouteAdder
|
||||
from litellm.proxy.proxy_server import (
|
||||
app,
|
||||
cleanup_router_config_variables,
|
||||
initialize,
|
||||
)
|
||||
from litellm.types.decisions import DecisionsRequestBody, OpenAIDecisionRequestBody
|
||||
|
||||
_INPUT_TOKENS: Final[int] = 367
|
||||
_OUTPUT_TOKENS: Final[int] = 3
|
||||
|
|
@ -688,3 +690,63 @@ def test_with_lazy_routes_disabled_a_config_pass_through_at_v1_decisions_still_w
|
|||
assert client.post("/v1/decisions", json={"model": "gpt-6-luna"}).json() == {"served_by": "pass-through"}
|
||||
assert _serving_endpoint(bare, "/v1/decisions") is pass_through
|
||||
assert _serving_endpoint(bare, "/decisions") is decisions
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def decisions_openapi() -> dict[str, object]:
|
||||
app = FastAPI()
|
||||
app.include_router(router)
|
||||
return app.openapi()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("path", "body_adapter", "response_schema", "required"),
|
||||
(
|
||||
("/v1/systemone", TypeAdapter(DecisionsRequestBody), "DecisionsResponse", {"model", "state", "questions"}),
|
||||
("/systemone", TypeAdapter(DecisionsRequestBody), "DecisionsResponse", {"model", "state", "questions"}),
|
||||
(
|
||||
"/v1/decisions",
|
||||
TypeAdapter(OpenAIDecisionRequestBody),
|
||||
"OpenAIDecisionResponse",
|
||||
{"model", "input", "questions"},
|
||||
),
|
||||
(
|
||||
"/decisions",
|
||||
TypeAdapter(OpenAIDecisionRequestBody),
|
||||
"OpenAIDecisionResponse",
|
||||
{"model", "input", "questions"},
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_swagger_documents_the_request_format_and_response_of_each_decisions_route(
|
||||
decisions_openapi: dict[str, object],
|
||||
path: str,
|
||||
body_adapter: TypeAdapter[DecisionsRequestBody] | TypeAdapter[OpenAIDecisionRequestBody],
|
||||
response_schema: str,
|
||||
required: set[str],
|
||||
) -> None:
|
||||
operation = decisions_openapi["paths"][path]["post"]
|
||||
media = operation["requestBody"]["content"]["application/json"]
|
||||
|
||||
assert "Decisions" in operation["summary"] or "System One" in operation["summary"]
|
||||
assert "docs.litellm.ai/docs/decisions" in operation["description"]
|
||||
assert required <= set(media["schema"]["required"])
|
||||
assert required <= set(media["schema"]["properties"])
|
||||
assert "$ref" not in json.dumps(media["schema"])
|
||||
assert media["schema"]["properties"]["model"]["description"]
|
||||
|
||||
example = {key: value for key, value in media["example"].items() if key != "model"}
|
||||
assert len(body_adapter.validate_python(example).questions) == 3
|
||||
|
||||
response_ref = operation["responses"]["200"]["content"]["application/json"]["schema"]["$ref"]
|
||||
assert response_ref == f"#/components/schemas/{response_schema}"
|
||||
assert "answers" in decisions_openapi["components"]["schemas"][response_schema]["properties"]
|
||||
|
||||
|
||||
def test_the_two_decisions_routes_document_different_request_formats(decisions_openapi: dict[str, object]) -> None:
|
||||
def properties(path: str) -> set[str]:
|
||||
operation = decisions_openapi["paths"][path]["post"]
|
||||
return set(operation["requestBody"]["content"]["application/json"]["schema"]["properties"])
|
||||
|
||||
assert "state" in properties("/v1/systemone") and "state" not in properties("/v1/decisions")
|
||||
assert "input" in properties("/v1/decisions") and "input" not in properties("/v1/systemone")
|
||||
|
|
|
|||
866
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
866
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -4703,7 +4703,18 @@ export interface paths {
|
|||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Decisions */
|
||||
/**
|
||||
* Ask questions about an input (OpenAI Decisions format)
|
||||
* @description Judge an `input` against 1 to 128 `questions` and get the answers back in the same order.
|
||||
*
|
||||
* Question types are `predicate` (yes/no with `probability`), `choice` (one of the `choices`, with
|
||||
* `probabilities`) and `score` (an index into `levels`, with `probabilities`). Requests that break these
|
||||
* rules are rejected with a 400 before any provider is called. `model` is any decision model in the proxy
|
||||
* model list. System One decision models accept this format too, LiteLLM translates the request and the
|
||||
* answers. Streaming is not supported.
|
||||
*
|
||||
* [Docs](https://docs.litellm.ai/docs/decisions)
|
||||
*/
|
||||
post: operations["decisions_decisions_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
|
|
@ -15837,7 +15848,18 @@ export interface paths {
|
|||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Systemone */
|
||||
/**
|
||||
* Ask named questions about a state (System One format)
|
||||
* @description Judge a `state` against 1 to 128 named `questions` and get one calibrated answer per question name.
|
||||
*
|
||||
* Question types are `noul` (probability the answer is yes), `choice` (one label out of `criteria`, with
|
||||
* `confidence` and `probabilities`) and `score` (an index into the `criteria` levels, with `confidence`,
|
||||
* `legend` and `probabilities`). Requests that break these rules are rejected with a 400 before any provider
|
||||
* is called. `model` is any decision model in the proxy model list. OpenAI decision models accept this format
|
||||
* too, LiteLLM translates it and keeps the answers keyed by question name. Streaming is not supported.
|
||||
*
|
||||
* [Docs](https://docs.litellm.ai/docs/decisions)
|
||||
*/
|
||||
post: operations["systemone_systemone_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
|
|
@ -19646,7 +19668,18 @@ export interface paths {
|
|||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Decisions */
|
||||
/**
|
||||
* Ask questions about an input (OpenAI Decisions format)
|
||||
* @description Judge an `input` against 1 to 128 `questions` and get the answers back in the same order.
|
||||
*
|
||||
* Question types are `predicate` (yes/no with `probability`), `choice` (one of the `choices`, with
|
||||
* `probabilities`) and `score` (an index into `levels`, with `probabilities`). Requests that break these
|
||||
* rules are rejected with a 400 before any provider is called. `model` is any decision model in the proxy
|
||||
* model list. System One decision models accept this format too, LiteLLM translates the request and the
|
||||
* answers. Streaming is not supported.
|
||||
*
|
||||
* [Docs](https://docs.litellm.ai/docs/decisions)
|
||||
*/
|
||||
post: operations["decisions_v1_decisions_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
|
|
@ -21991,7 +22024,18 @@ export interface paths {
|
|||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
/** Systemone */
|
||||
/**
|
||||
* Ask named questions about a state (System One format)
|
||||
* @description Judge a `state` against 1 to 128 named `questions` and get one calibrated answer per question name.
|
||||
*
|
||||
* Question types are `noul` (probability the answer is yes), `choice` (one label out of `criteria`, with
|
||||
* `confidence` and `probabilities`) and `score` (an index into the `criteria` levels, with `confidence`,
|
||||
* `legend` and `probabilities`). Requests that break these rules are rejected with a 400 before any provider
|
||||
* is called. `model` is any decision model in the proxy model list. OpenAI decision models accept this format
|
||||
* too, LiteLLM translates it and keeps the answers keyed by question name. Streaming is not supported.
|
||||
*
|
||||
* [Docs](https://docs.litellm.ai/docs/decisions)
|
||||
*/
|
||||
post: operations["systemone_v1_systemone_post"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
|
|
@ -28642,6 +28686,24 @@ export interface components {
|
|||
*/
|
||||
role: "user" | "assistant";
|
||||
};
|
||||
/** ChoiceAnswer */
|
||||
ChoiceAnswer: {
|
||||
/** Choice */
|
||||
choice: string;
|
||||
/** Confidence */
|
||||
confidence: number;
|
||||
/** Probabilities */
|
||||
probabilities: {
|
||||
[key: string]: number;
|
||||
};
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "choice";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/** ChoiceLogprobs */
|
||||
ChoiceLogprobs: {
|
||||
/** Content */
|
||||
|
|
@ -31008,6 +31070,33 @@ export interface components {
|
|||
*/
|
||||
total_tokens: number;
|
||||
};
|
||||
/** DecisionsResponse */
|
||||
DecisionsResponse: {
|
||||
/** Answers */
|
||||
answers: {
|
||||
[key: string]: components["schemas"]["NoulAnswer"] | components["schemas"]["ChoiceAnswer"] | components["schemas"]["ScoreAnswer"];
|
||||
};
|
||||
/** Model */
|
||||
model?: string | null;
|
||||
usage?: components["schemas"]["DecisionsUsage"] | null;
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/** DecisionsUsage */
|
||||
DecisionsUsage: {
|
||||
/**
|
||||
* Input Tokens
|
||||
* @default 0
|
||||
*/
|
||||
input_tokens: number;
|
||||
/**
|
||||
* Output Tokens
|
||||
* @default 0
|
||||
*/
|
||||
output_tokens: number;
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* DefaultInternalUserParams
|
||||
* @description Default parameters to apply when a new user signs in via SSO or is created on the /user/new API endpoint
|
||||
|
|
@ -38834,6 +38923,18 @@ export interface components {
|
|||
/** User Role */
|
||||
user_role?: ("proxy_admin" | "proxy_admin_viewer" | "internal_user" | "internal_user_viewer") | null;
|
||||
};
|
||||
/** NoulAnswer */
|
||||
NoulAnswer: {
|
||||
/** Noul */
|
||||
noul: number;
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "noul";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* OAuth2SecurityScheme
|
||||
* @description Defines a security scheme using OAuth 2.0.
|
||||
|
|
@ -38872,6 +38973,129 @@ export interface components {
|
|||
[key: string]: unknown;
|
||||
} | null;
|
||||
};
|
||||
/** OpenAIChoiceAnswer */
|
||||
OpenAIChoiceAnswer: {
|
||||
/** Choice */
|
||||
choice: string | boolean;
|
||||
/** Confidence */
|
||||
confidence: number;
|
||||
/** Name */
|
||||
name: string | null;
|
||||
/** Probabilities */
|
||||
probabilities: components["schemas"]["OpenAIChoiceProbability"][];
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "choice";
|
||||
};
|
||||
/** OpenAIChoiceProbability */
|
||||
OpenAIChoiceProbability: {
|
||||
/** Probability */
|
||||
probability: number;
|
||||
/** Value */
|
||||
value: string | boolean;
|
||||
};
|
||||
/** OpenAIDecisionInputTokensDetails */
|
||||
OpenAIDecisionInputTokensDetails: {
|
||||
/**
|
||||
* Cache Write Tokens
|
||||
* @default 0
|
||||
*/
|
||||
cache_write_tokens: number;
|
||||
/**
|
||||
* Cached Tokens
|
||||
* @default 0
|
||||
*/
|
||||
cached_tokens: number;
|
||||
};
|
||||
/** OpenAIDecisionOutputTokensDetails */
|
||||
OpenAIDecisionOutputTokensDetails: {
|
||||
/**
|
||||
* Reasoning Tokens
|
||||
* @default 0
|
||||
*/
|
||||
reasoning_tokens: number;
|
||||
};
|
||||
/** OpenAIDecisionResponse */
|
||||
OpenAIDecisionResponse: {
|
||||
/** Answers */
|
||||
answers: (components["schemas"]["OpenAIPredicateAnswer"] | components["schemas"]["OpenAIChoiceAnswer"] | components["schemas"]["OpenAIScoreAnswer"] | components["schemas"]["OpenAIRefusalAnswer"])[];
|
||||
/** Model */
|
||||
model: string;
|
||||
usage: components["schemas"]["OpenAIDecisionUsage"];
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/** OpenAIDecisionUsage */
|
||||
OpenAIDecisionUsage: {
|
||||
/** Input Tokens */
|
||||
input_tokens: number;
|
||||
/**
|
||||
* @default {
|
||||
* "cache_write_tokens": 0,
|
||||
* "cached_tokens": 0
|
||||
* }
|
||||
*/
|
||||
input_tokens_details: components["schemas"]["OpenAIDecisionInputTokensDetails"];
|
||||
/** Output Tokens */
|
||||
output_tokens: number;
|
||||
/**
|
||||
* @default {
|
||||
* "reasoning_tokens": 0
|
||||
* }
|
||||
*/
|
||||
output_tokens_details: components["schemas"]["OpenAIDecisionOutputTokensDetails"];
|
||||
/** Total Tokens */
|
||||
total_tokens: number;
|
||||
};
|
||||
/** OpenAIPredicateAnswer */
|
||||
OpenAIPredicateAnswer: {
|
||||
/** Name */
|
||||
name: string | null;
|
||||
/** Probability */
|
||||
probability: number;
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "predicate";
|
||||
};
|
||||
/** OpenAIRefusalAnswer */
|
||||
OpenAIRefusalAnswer: {
|
||||
/** Name */
|
||||
name: string | null;
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "refusal";
|
||||
};
|
||||
/** OpenAIScoreAnswer */
|
||||
OpenAIScoreAnswer: {
|
||||
/** Confidence */
|
||||
confidence: number;
|
||||
/** Name */
|
||||
name: string | null;
|
||||
/** Probabilities */
|
||||
probabilities: components["schemas"]["OpenAIScoreProbability"][];
|
||||
/** Score */
|
||||
score: number;
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "score";
|
||||
};
|
||||
/** OpenAIScoreProbability */
|
||||
OpenAIScoreProbability: {
|
||||
/** Label */
|
||||
label: string;
|
||||
/** Probability */
|
||||
probability: number;
|
||||
/** Value */
|
||||
value: number;
|
||||
};
|
||||
/**
|
||||
* OpenIdConnectSecurityScheme
|
||||
* @description Defines a security scheme using OpenID Connect.
|
||||
|
|
@ -43986,6 +44210,30 @@ export interface components {
|
|||
*/
|
||||
window_seconds: number;
|
||||
};
|
||||
/** ScoreAnswer */
|
||||
ScoreAnswer: {
|
||||
/** Confidence */
|
||||
confidence: number;
|
||||
/** Legend */
|
||||
legend: {
|
||||
[key: string]: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[];
|
||||
};
|
||||
/** Probabilities */
|
||||
probabilities: {
|
||||
[key: string]: number;
|
||||
};
|
||||
/** Score */
|
||||
score: number;
|
||||
/**
|
||||
* @description discriminator enum property added by openapi-typescript
|
||||
* @enum {string}
|
||||
*/
|
||||
type: "score";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
/**
|
||||
* Screenshot
|
||||
* @description A screenshot action.
|
||||
|
|
@ -56556,7 +56804,179 @@ export interface operations {
|
|||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "input": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today.",
|
||||
* "model": "luna",
|
||||
* "questions": [
|
||||
* {
|
||||
* "instructions": "Is the customer asking for a refund?",
|
||||
* "name": "is_refund_request",
|
||||
* "type": "predicate"
|
||||
* },
|
||||
* {
|
||||
* "choices": [
|
||||
* {
|
||||
* "description": "can wait a week",
|
||||
* "value": "low"
|
||||
* },
|
||||
* {
|
||||
* "description": "needs action today",
|
||||
* "value": "high"
|
||||
* }
|
||||
* ],
|
||||
* "instructions": "How urgent is this?",
|
||||
* "name": "urgency",
|
||||
* "type": "choice"
|
||||
* },
|
||||
* {
|
||||
* "instructions": "How frustrated is the customer?",
|
||||
* "levels": [
|
||||
* {
|
||||
* "label": "calm"
|
||||
* },
|
||||
* {
|
||||
* "label": "mildly annoyed"
|
||||
* },
|
||||
* {
|
||||
* "label": "angry"
|
||||
* }
|
||||
* ],
|
||||
* "name": "frustration",
|
||||
* "type": "score"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
/**
|
||||
* Input
|
||||
* @description The text being judged, or user messages whose content mixes input_text and input_image parts
|
||||
*/
|
||||
input: string | {
|
||||
/** Content */
|
||||
content: string | ({
|
||||
/** Text */
|
||||
text: string;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "input_text";
|
||||
} | {
|
||||
/** Detail */
|
||||
detail?: string | null;
|
||||
/** Image Url */
|
||||
image_url: string;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "input_image";
|
||||
})[];
|
||||
/**
|
||||
* Role
|
||||
* @default user
|
||||
* @constant
|
||||
*/
|
||||
role?: "user";
|
||||
/**
|
||||
* Type
|
||||
* @default message
|
||||
* @constant
|
||||
*/
|
||||
type?: "message";
|
||||
}[];
|
||||
/**
|
||||
* Model
|
||||
* @description A decision model from the proxy model_list
|
||||
*/
|
||||
model: string;
|
||||
/**
|
||||
* Questions
|
||||
* @description Predicate, choice and score questions. Answers come back in the same order
|
||||
*/
|
||||
questions: ({
|
||||
/**
|
||||
* Instructions
|
||||
* @description The yes/no question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "predicate";
|
||||
} | {
|
||||
/**
|
||||
* Choices
|
||||
* @description The options the model picks from. Values must be unique
|
||||
*/
|
||||
choices: {
|
||||
/** Description */
|
||||
description?: string | null;
|
||||
/** Value */
|
||||
value: string | boolean;
|
||||
}[];
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "choice";
|
||||
} | {
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Levels
|
||||
* @description Scale levels from lowest to highest. The score is a level index
|
||||
*/
|
||||
levels: {
|
||||
/** Description */
|
||||
description?: string | null;
|
||||
/** Label */
|
||||
label: string;
|
||||
}[];
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "score";
|
||||
})[];
|
||||
/**
|
||||
* Safety Identifier
|
||||
* @description A stable id for your end user. Sent to OpenAI and dropped for other providers
|
||||
*/
|
||||
safety_identifier?: string | null;
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
|
|
@ -56564,7 +56984,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["OpenAIDecisionResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -70673,7 +71093,132 @@ export interface operations {
|
|||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "model": "jev",
|
||||
* "questions": {
|
||||
* "frustration": {
|
||||
* "criteria": [
|
||||
* "calm",
|
||||
* "mildly annoyed",
|
||||
* "angry"
|
||||
* ],
|
||||
* "instructions": "How frustrated is the customer?",
|
||||
* "type": "score"
|
||||
* },
|
||||
* "is_refund_request": {
|
||||
* "instructions": "Is the customer asking for a refund?",
|
||||
* "type": "noul"
|
||||
* },
|
||||
* "urgency": {
|
||||
* "criteria": {
|
||||
* "high": "needs action today",
|
||||
* "low": "can wait a week"
|
||||
* },
|
||||
* "instructions": "How urgent is this?",
|
||||
* "type": "choice"
|
||||
* }
|
||||
* },
|
||||
* "state": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today."
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
/**
|
||||
* Model
|
||||
* @description A decision model from the proxy model_list
|
||||
*/
|
||||
model: string;
|
||||
/**
|
||||
* Questions
|
||||
* @description Named noul, choice and score questions. Each key becomes a key in the answers
|
||||
*/
|
||||
questions: {
|
||||
[key: string]: ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Optional descriptions of what makes the answer true and what makes it false
|
||||
*/
|
||||
criteria?: {
|
||||
[key: string]: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
} | null;
|
||||
/**
|
||||
* Instructions
|
||||
* @description The yes/no question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "noul";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
}) | ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Candidate labels mapped to an optional description of each
|
||||
*/
|
||||
criteria: {
|
||||
[key: string]: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
};
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "choice";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
}) | ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Scale levels from lowest to highest. The score is a level index
|
||||
*/
|
||||
criteria: (string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[])[];
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "score";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* State
|
||||
* @description The thing being judged, such as a support ticket, a document or a chat transcript
|
||||
*/
|
||||
state: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[];
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
|
|
@ -70681,7 +71226,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["DecisionsResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -75840,7 +76385,179 @@ export interface operations {
|
|||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "input": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today.",
|
||||
* "model": "luna",
|
||||
* "questions": [
|
||||
* {
|
||||
* "instructions": "Is the customer asking for a refund?",
|
||||
* "name": "is_refund_request",
|
||||
* "type": "predicate"
|
||||
* },
|
||||
* {
|
||||
* "choices": [
|
||||
* {
|
||||
* "description": "can wait a week",
|
||||
* "value": "low"
|
||||
* },
|
||||
* {
|
||||
* "description": "needs action today",
|
||||
* "value": "high"
|
||||
* }
|
||||
* ],
|
||||
* "instructions": "How urgent is this?",
|
||||
* "name": "urgency",
|
||||
* "type": "choice"
|
||||
* },
|
||||
* {
|
||||
* "instructions": "How frustrated is the customer?",
|
||||
* "levels": [
|
||||
* {
|
||||
* "label": "calm"
|
||||
* },
|
||||
* {
|
||||
* "label": "mildly annoyed"
|
||||
* },
|
||||
* {
|
||||
* "label": "angry"
|
||||
* }
|
||||
* ],
|
||||
* "name": "frustration",
|
||||
* "type": "score"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
/**
|
||||
* Input
|
||||
* @description The text being judged, or user messages whose content mixes input_text and input_image parts
|
||||
*/
|
||||
input: string | {
|
||||
/** Content */
|
||||
content: string | ({
|
||||
/** Text */
|
||||
text: string;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "input_text";
|
||||
} | {
|
||||
/** Detail */
|
||||
detail?: string | null;
|
||||
/** Image Url */
|
||||
image_url: string;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "input_image";
|
||||
})[];
|
||||
/**
|
||||
* Role
|
||||
* @default user
|
||||
* @constant
|
||||
*/
|
||||
role?: "user";
|
||||
/**
|
||||
* Type
|
||||
* @default message
|
||||
* @constant
|
||||
*/
|
||||
type?: "message";
|
||||
}[];
|
||||
/**
|
||||
* Model
|
||||
* @description A decision model from the proxy model_list
|
||||
*/
|
||||
model: string;
|
||||
/**
|
||||
* Questions
|
||||
* @description Predicate, choice and score questions. Answers come back in the same order
|
||||
*/
|
||||
questions: ({
|
||||
/**
|
||||
* Instructions
|
||||
* @description The yes/no question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "predicate";
|
||||
} | {
|
||||
/**
|
||||
* Choices
|
||||
* @description The options the model picks from. Values must be unique
|
||||
*/
|
||||
choices: {
|
||||
/** Description */
|
||||
description?: string | null;
|
||||
/** Value */
|
||||
value: string | boolean;
|
||||
}[];
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "choice";
|
||||
} | {
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the input
|
||||
*/
|
||||
instructions: string;
|
||||
/**
|
||||
* Levels
|
||||
* @description Scale levels from lowest to highest. The score is a level index
|
||||
*/
|
||||
levels: {
|
||||
/** Description */
|
||||
description?: string | null;
|
||||
/** Label */
|
||||
label: string;
|
||||
}[];
|
||||
/**
|
||||
* Name
|
||||
* @description Echoed in the matching answer
|
||||
*/
|
||||
name?: string | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "score";
|
||||
})[];
|
||||
/**
|
||||
* Safety Identifier
|
||||
* @description A stable id for your end user. Sent to OpenAI and dropped for other providers
|
||||
*/
|
||||
safety_identifier?: string | null;
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
|
|
@ -75848,7 +76565,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["OpenAIDecisionResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -79013,7 +79730,132 @@ export interface operations {
|
|||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
requestBody: {
|
||||
content: {
|
||||
/**
|
||||
* @example {
|
||||
* "model": "jev",
|
||||
* "questions": {
|
||||
* "frustration": {
|
||||
* "criteria": [
|
||||
* "calm",
|
||||
* "mildly annoyed",
|
||||
* "angry"
|
||||
* ],
|
||||
* "instructions": "How frustrated is the customer?",
|
||||
* "type": "score"
|
||||
* },
|
||||
* "is_refund_request": {
|
||||
* "instructions": "Is the customer asking for a refund?",
|
||||
* "type": "noul"
|
||||
* },
|
||||
* "urgency": {
|
||||
* "criteria": {
|
||||
* "high": "needs action today",
|
||||
* "low": "can wait a week"
|
||||
* },
|
||||
* "instructions": "How urgent is this?",
|
||||
* "type": "choice"
|
||||
* }
|
||||
* },
|
||||
* "state": "Customer wrote: I was charged twice for order #4411 and want one charge refunded today."
|
||||
* }
|
||||
*/
|
||||
"application/json": {
|
||||
/**
|
||||
* Model
|
||||
* @description A decision model from the proxy model_list
|
||||
*/
|
||||
model: string;
|
||||
/**
|
||||
* Questions
|
||||
* @description Named noul, choice and score questions. Each key becomes a key in the answers
|
||||
*/
|
||||
questions: {
|
||||
[key: string]: ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Optional descriptions of what makes the answer true and what makes it false
|
||||
*/
|
||||
criteria?: {
|
||||
[key: string]: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
} | null;
|
||||
/**
|
||||
* Instructions
|
||||
* @description The yes/no question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "noul";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
}) | ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Candidate labels mapped to an optional description of each
|
||||
*/
|
||||
criteria: {
|
||||
[key: string]: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
};
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "choice";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
}) | ({
|
||||
/**
|
||||
* Criteria
|
||||
* @description Scale levels from lowest to highest. The score is a level index
|
||||
*/
|
||||
criteria: (string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[])[];
|
||||
/**
|
||||
* Instructions
|
||||
* @description The question to ask about the state
|
||||
*/
|
||||
instructions?: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[] | null;
|
||||
/**
|
||||
* Type
|
||||
* @constant
|
||||
*/
|
||||
type: "score";
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
});
|
||||
};
|
||||
/**
|
||||
* State
|
||||
* @description The thing being judged, such as a support ticket, a document or a chat transcript
|
||||
*/
|
||||
state: string | {
|
||||
[key: string]: unknown;
|
||||
} | unknown[];
|
||||
} & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
|
|
@ -79021,7 +79863,7 @@ export interface operations {
|
|||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": unknown;
|
||||
"application/json": components["schemas"]["DecisionsResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue