Add a python client library
We still don't have any documentation and things are in flux, but you can report your OpenAI API calls to OpenPipe now.
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
.venv/
|
||||||
|
*.pyc
|
||||||
@@ -37,7 +37,7 @@ export default function LoggedCalls() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppShell requireAuth>
|
<AppShell title="Logged Calls" requireAuth>
|
||||||
<PageHeaderContainer>
|
<PageHeaderContainer>
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export const externalApiRouter = createTRPCRouter({
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
path: "/v1/check-cache",
|
path: "/v1/check-cache",
|
||||||
description: "Check if a prompt is cached",
|
description: "Check if a prompt is cached",
|
||||||
|
protect: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
@@ -102,6 +103,7 @@ export const externalApiRouter = createTRPCRouter({
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
path: "/v1/report",
|
path: "/v1/report",
|
||||||
description: "Report an API call",
|
description: "Report an API call",
|
||||||
|
protect: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
@@ -122,6 +124,7 @@ export const externalApiRouter = createTRPCRouter({
|
|||||||
)
|
)
|
||||||
.output(z.void())
|
.output(z.void())
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
console.log("GOT TAGS", input.tags);
|
||||||
const apiKey = ctx.apiKey;
|
const apiKey = ctx.apiKey;
|
||||||
if (!apiKey) {
|
if (!apiKey) {
|
||||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => {
|
|||||||
// Get the session from the server using the getServerSession wrapper function
|
// Get the session from the server using the getServerSession wrapper function
|
||||||
const session = await getServerAuthSession({ req, res });
|
const session = await getServerAuthSession({ req, res });
|
||||||
|
|
||||||
const apiKey = req.headers["x-openpipe-api-key"] as string | null;
|
const apiKey = req.headers.authorization?.split(" ")[1] as string | null;
|
||||||
|
|
||||||
return createInnerTRPCContext({
|
return createInnerTRPCContext({
|
||||||
session,
|
session,
|
||||||
|
|||||||
BIN
client-libs/python/.testmondata
Normal file
BIN
client-libs/python/.testmondata
Normal file
Binary file not shown.
BIN
client-libs/python/.testmondata-shm
Normal file
BIN
client-libs/python/.testmondata-shm
Normal file
Binary file not shown.
0
client-libs/python/.testmondata-wal
Normal file
0
client-libs/python/.testmondata-wal
Normal file
11
client-libs/python/codegen.sh
Executable file
11
client-libs/python/codegen.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
poetry run openapi-python-client generate --url http://localhost:3000/api/openapi.json
|
||||||
|
|
||||||
|
rm -rf openpipe/api_client
|
||||||
|
mv open-pipe-api-client/open_pipe_api_client openpipe/api_client
|
||||||
|
rm -rf open-pipe-api-client
|
||||||
10
client-libs/python/openpipe/__init__.py
Normal file
10
client-libs/python/openpipe/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from .openai import OpenAIWrapper
|
||||||
|
from .shared import configured_client
|
||||||
|
|
||||||
|
openai = OpenAIWrapper()
|
||||||
|
|
||||||
|
def configure_openpipe(base_url=None, api_key=None):
|
||||||
|
if base_url is not None:
|
||||||
|
configured_client._base_url = base_url
|
||||||
|
if api_key is not None:
|
||||||
|
configured_client.token = api_key
|
||||||
7
client-libs/python/openpipe/api_client/__init__.py
Normal file
7
client-libs/python/openpipe/api_client/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
""" A client library for accessing OpenPipe API """
|
||||||
|
from .client import AuthenticatedClient, Client
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"AuthenticatedClient",
|
||||||
|
"Client",
|
||||||
|
)
|
||||||
1
client-libs/python/openpipe/api_client/api/__init__.py
Normal file
1
client-libs/python/openpipe/api_client/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
""" Contains methods for accessing the API """
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.external_api_check_cache_json_body import ExternalApiCheckCacheJsonBody
|
||||||
|
from ...models.external_api_check_cache_response_200 import ExternalApiCheckCacheResponse200
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
*,
|
||||||
|
json_body: ExternalApiCheckCacheJsonBody,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
json_json_body = json_body.to_dict()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/v1/check-cache",
|
||||||
|
"json": json_json_body,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Optional[ExternalApiCheckCacheResponse200]:
|
||||||
|
if response.status_code == HTTPStatus.OK:
|
||||||
|
response_200 = ExternalApiCheckCacheResponse200.from_dict(response.json())
|
||||||
|
|
||||||
|
return response_200
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(
|
||||||
|
*, client: Union[AuthenticatedClient, Client], response: httpx.Response
|
||||||
|
) -> Response[ExternalApiCheckCacheResponse200]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiCheckCacheJsonBody,
|
||||||
|
) -> Response[ExternalApiCheckCacheResponse200]:
|
||||||
|
"""Check if a prompt is cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiCheckCacheJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[ExternalApiCheckCacheResponse200]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
json_body=json_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
def sync(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiCheckCacheJsonBody,
|
||||||
|
) -> Optional[ExternalApiCheckCacheResponse200]:
|
||||||
|
"""Check if a prompt is cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiCheckCacheJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalApiCheckCacheResponse200
|
||||||
|
"""
|
||||||
|
|
||||||
|
return sync_detailed(
|
||||||
|
client=client,
|
||||||
|
json_body=json_body,
|
||||||
|
).parsed
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiCheckCacheJsonBody,
|
||||||
|
) -> Response[ExternalApiCheckCacheResponse200]:
|
||||||
|
"""Check if a prompt is cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiCheckCacheJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[ExternalApiCheckCacheResponse200]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
json_body=json_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiCheckCacheJsonBody,
|
||||||
|
) -> Optional[ExternalApiCheckCacheResponse200]:
|
||||||
|
"""Check if a prompt is cached
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiCheckCacheJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ExternalApiCheckCacheResponse200
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
await asyncio_detailed(
|
||||||
|
client=client,
|
||||||
|
json_body=json_body,
|
||||||
|
)
|
||||||
|
).parsed
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
from http import HTTPStatus
|
||||||
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from ... import errors
|
||||||
|
from ...client import AuthenticatedClient, Client
|
||||||
|
from ...models.external_api_report_json_body import ExternalApiReportJsonBody
|
||||||
|
from ...types import Response
|
||||||
|
|
||||||
|
|
||||||
|
def _get_kwargs(
|
||||||
|
*,
|
||||||
|
json_body: ExternalApiReportJsonBody,
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
pass
|
||||||
|
|
||||||
|
json_json_body = json_body.to_dict()
|
||||||
|
|
||||||
|
return {
|
||||||
|
"method": "post",
|
||||||
|
"url": "/v1/report",
|
||||||
|
"json": json_json_body,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Optional[Any]:
|
||||||
|
if response.status_code == HTTPStatus.OK:
|
||||||
|
return None
|
||||||
|
if client.raise_on_unexpected_status:
|
||||||
|
raise errors.UnexpectedStatus(response.status_code, response.content)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _build_response(*, client: Union[AuthenticatedClient, Client], response: httpx.Response) -> Response[Any]:
|
||||||
|
return Response(
|
||||||
|
status_code=HTTPStatus(response.status_code),
|
||||||
|
content=response.content,
|
||||||
|
headers=response.headers,
|
||||||
|
parsed=_parse_response(client=client, response=response),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def sync_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiReportJsonBody,
|
||||||
|
) -> Response[Any]:
|
||||||
|
"""Report an API call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiReportJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Any]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
json_body=json_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_httpx_client().request(
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
|
|
||||||
|
|
||||||
|
async def asyncio_detailed(
|
||||||
|
*,
|
||||||
|
client: AuthenticatedClient,
|
||||||
|
json_body: ExternalApiReportJsonBody,
|
||||||
|
) -> Response[Any]:
|
||||||
|
"""Report an API call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
json_body (ExternalApiReportJsonBody):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
|
||||||
|
httpx.TimeoutException: If the request takes longer than Client.timeout.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Response[Any]
|
||||||
|
"""
|
||||||
|
|
||||||
|
kwargs = _get_kwargs(
|
||||||
|
json_body=json_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = await client.get_async_httpx_client().request(**kwargs)
|
||||||
|
|
||||||
|
return _build_response(client=client, response=response)
|
||||||
268
client-libs/python/openpipe/api_client/client.py
Normal file
268
client-libs/python/openpipe/api_client/client.py
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
import ssl
|
||||||
|
from typing import Any, Dict, Optional, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from attrs import define, evolve, field
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class Client:
|
||||||
|
"""A class for keeping track of data related to the API
|
||||||
|
|
||||||
|
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
|
||||||
|
|
||||||
|
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
|
||||||
|
|
||||||
|
``cookies``: A dictionary of cookies to be sent with every request
|
||||||
|
|
||||||
|
``headers``: A dictionary of headers to be sent with every request
|
||||||
|
|
||||||
|
``timeout``: The maximum amount of a time a request can take. API functions will raise
|
||||||
|
httpx.TimeoutException if this is exceeded.
|
||||||
|
|
||||||
|
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
|
||||||
|
but can be set to False for testing purposes.
|
||||||
|
|
||||||
|
``follow_redirects``: Whether or not to follow redirects. Default value is False.
|
||||||
|
|
||||||
|
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
|
||||||
|
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
|
||||||
|
argument to the constructor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
|
||||||
|
_base_url: str
|
||||||
|
_cookies: Dict[str, str] = field(factory=dict, kw_only=True)
|
||||||
|
_headers: Dict[str, str] = field(factory=dict, kw_only=True)
|
||||||
|
_timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True)
|
||||||
|
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True)
|
||||||
|
_follow_redirects: bool = field(default=False, kw_only=True)
|
||||||
|
_httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True)
|
||||||
|
_client: Optional[httpx.Client] = field(default=None, init=False)
|
||||||
|
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
|
||||||
|
|
||||||
|
def with_headers(self, headers: Dict[str, str]) -> "Client":
|
||||||
|
"""Get a new client matching this one with additional headers"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.headers.update(headers)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.headers.update(headers)
|
||||||
|
return evolve(self, headers={**self._headers, **headers})
|
||||||
|
|
||||||
|
def with_cookies(self, cookies: Dict[str, str]) -> "Client":
|
||||||
|
"""Get a new client matching this one with additional cookies"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.cookies.update(cookies)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.cookies.update(cookies)
|
||||||
|
return evolve(self, cookies={**self._cookies, **cookies})
|
||||||
|
|
||||||
|
def with_timeout(self, timeout: httpx.Timeout) -> "Client":
|
||||||
|
"""Get a new client matching this one with a new timeout (in seconds)"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.timeout = timeout
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.timeout = timeout
|
||||||
|
return evolve(self, timeout=timeout)
|
||||||
|
|
||||||
|
def set_httpx_client(self, client: httpx.Client) -> "Client":
|
||||||
|
"""Manually the underlying httpx.Client
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._client = client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_httpx_client(self) -> httpx.Client:
|
||||||
|
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
|
||||||
|
if self._client is None:
|
||||||
|
self._client = httpx.Client(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def __enter__(self) -> "Client":
|
||||||
|
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__enter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__exit__(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "Client":
|
||||||
|
"""Manually the underlying httpx.AsyncClient
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._async_client = async_client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
||||||
|
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
|
||||||
|
if self._async_client is None:
|
||||||
|
self._async_client = httpx.AsyncClient(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._async_client
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "Client":
|
||||||
|
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aenter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class AuthenticatedClient:
|
||||||
|
"""A Client which has been authenticated for use on secured endpoints
|
||||||
|
|
||||||
|
The following are accepted as keyword arguments and will be used to construct httpx Clients internally:
|
||||||
|
|
||||||
|
``base_url``: The base URL for the API, all requests are made to a relative path to this URL
|
||||||
|
|
||||||
|
``cookies``: A dictionary of cookies to be sent with every request
|
||||||
|
|
||||||
|
``headers``: A dictionary of headers to be sent with every request
|
||||||
|
|
||||||
|
``timeout``: The maximum amount of a time a request can take. API functions will raise
|
||||||
|
httpx.TimeoutException if this is exceeded.
|
||||||
|
|
||||||
|
``verify_ssl``: Whether or not to verify the SSL certificate of the API server. This should be True in production,
|
||||||
|
but can be set to False for testing purposes.
|
||||||
|
|
||||||
|
``follow_redirects``: Whether or not to follow redirects. Default value is False.
|
||||||
|
|
||||||
|
``httpx_args``: A dictionary of additional arguments to be passed to the ``httpx.Client`` and ``httpx.AsyncClient`` constructor.
|
||||||
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
raise_on_unexpected_status: Whether or not to raise an errors.UnexpectedStatus if the API returns a
|
||||||
|
status code that was not documented in the source OpenAPI document. Can also be provided as a keyword
|
||||||
|
argument to the constructor.
|
||||||
|
token: The token to use for authentication
|
||||||
|
prefix: The prefix to use for the Authorization header
|
||||||
|
auth_header_name: The name of the Authorization header
|
||||||
|
"""
|
||||||
|
|
||||||
|
raise_on_unexpected_status: bool = field(default=False, kw_only=True)
|
||||||
|
_base_url: str
|
||||||
|
_cookies: Dict[str, str] = field(factory=dict, kw_only=True)
|
||||||
|
_headers: Dict[str, str] = field(factory=dict, kw_only=True)
|
||||||
|
_timeout: Optional[httpx.Timeout] = field(default=None, kw_only=True)
|
||||||
|
_verify_ssl: Union[str, bool, ssl.SSLContext] = field(default=True, kw_only=True)
|
||||||
|
_follow_redirects: bool = field(default=False, kw_only=True)
|
||||||
|
_httpx_args: Dict[str, Any] = field(factory=dict, kw_only=True)
|
||||||
|
_client: Optional[httpx.Client] = field(default=None, init=False)
|
||||||
|
_async_client: Optional[httpx.AsyncClient] = field(default=None, init=False)
|
||||||
|
|
||||||
|
token: str
|
||||||
|
prefix: str = "Bearer"
|
||||||
|
auth_header_name: str = "Authorization"
|
||||||
|
|
||||||
|
def with_headers(self, headers: Dict[str, str]) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with additional headers"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.headers.update(headers)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.headers.update(headers)
|
||||||
|
return evolve(self, headers={**self._headers, **headers})
|
||||||
|
|
||||||
|
def with_cookies(self, cookies: Dict[str, str]) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with additional cookies"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.cookies.update(cookies)
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.cookies.update(cookies)
|
||||||
|
return evolve(self, cookies={**self._cookies, **cookies})
|
||||||
|
|
||||||
|
def with_timeout(self, timeout: httpx.Timeout) -> "AuthenticatedClient":
|
||||||
|
"""Get a new client matching this one with a new timeout (in seconds)"""
|
||||||
|
if self._client is not None:
|
||||||
|
self._client.timeout = timeout
|
||||||
|
if self._async_client is not None:
|
||||||
|
self._async_client.timeout = timeout
|
||||||
|
return evolve(self, timeout=timeout)
|
||||||
|
|
||||||
|
def set_httpx_client(self, client: httpx.Client) -> "AuthenticatedClient":
|
||||||
|
"""Manually the underlying httpx.Client
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._client = client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_httpx_client(self) -> httpx.Client:
|
||||||
|
"""Get the underlying httpx.Client, constructing a new one if not previously set"""
|
||||||
|
if self._client is None:
|
||||||
|
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
|
||||||
|
self._client = httpx.Client(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
def __enter__(self) -> "AuthenticatedClient":
|
||||||
|
"""Enter a context manager for self.client—you cannot enter twice (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__enter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for internal httpx.Client (see httpx docs)"""
|
||||||
|
self.get_httpx_client().__exit__(*args, **kwargs)
|
||||||
|
|
||||||
|
def set_async_httpx_client(self, async_client: httpx.AsyncClient) -> "AuthenticatedClient":
|
||||||
|
"""Manually the underlying httpx.AsyncClient
|
||||||
|
|
||||||
|
**NOTE**: This will override any other settings on the client, including cookies, headers, and timeout.
|
||||||
|
"""
|
||||||
|
self._async_client = async_client
|
||||||
|
return self
|
||||||
|
|
||||||
|
def get_async_httpx_client(self) -> httpx.AsyncClient:
|
||||||
|
"""Get the underlying httpx.AsyncClient, constructing a new one if not previously set"""
|
||||||
|
if self._async_client is None:
|
||||||
|
self._headers[self.auth_header_name] = f"{self.prefix} {self.token}" if self.prefix else self.token
|
||||||
|
self._async_client = httpx.AsyncClient(
|
||||||
|
base_url=self._base_url,
|
||||||
|
cookies=self._cookies,
|
||||||
|
headers=self._headers,
|
||||||
|
timeout=self._timeout,
|
||||||
|
verify=self._verify_ssl,
|
||||||
|
follow_redirects=self._follow_redirects,
|
||||||
|
**self._httpx_args,
|
||||||
|
)
|
||||||
|
return self._async_client
|
||||||
|
|
||||||
|
async def __aenter__(self) -> "AuthenticatedClient":
|
||||||
|
"""Enter a context manager for underlying httpx.AsyncClient—you cannot enter twice (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aenter__()
|
||||||
|
return self
|
||||||
|
|
||||||
|
async def __aexit__(self, *args: Any, **kwargs: Any) -> None:
|
||||||
|
"""Exit a context manager for underlying httpx.AsyncClient (see httpx docs)"""
|
||||||
|
await self.get_async_httpx_client().__aexit__(*args, **kwargs)
|
||||||
14
client-libs/python/openpipe/api_client/errors.py
Normal file
14
client-libs/python/openpipe/api_client/errors.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
""" Contains shared errors types that can be raised from API functions """
|
||||||
|
|
||||||
|
|
||||||
|
class UnexpectedStatus(Exception):
|
||||||
|
"""Raised by api functions when the response status an undocumented status and Client.raise_on_unexpected_status is True"""
|
||||||
|
|
||||||
|
def __init__(self, status_code: int, content: bytes):
|
||||||
|
self.status_code = status_code
|
||||||
|
self.content = content
|
||||||
|
|
||||||
|
super().__init__(f"Unexpected status code: {status_code}")
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["UnexpectedStatus"]
|
||||||
15
client-libs/python/openpipe/api_client/models/__init__.py
Normal file
15
client-libs/python/openpipe/api_client/models/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
""" Contains all the data models used in inputs/outputs """
|
||||||
|
|
||||||
|
from .external_api_check_cache_json_body import ExternalApiCheckCacheJsonBody
|
||||||
|
from .external_api_check_cache_json_body_tags import ExternalApiCheckCacheJsonBodyTags
|
||||||
|
from .external_api_check_cache_response_200 import ExternalApiCheckCacheResponse200
|
||||||
|
from .external_api_report_json_body import ExternalApiReportJsonBody
|
||||||
|
from .external_api_report_json_body_tags import ExternalApiReportJsonBodyTags
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"ExternalApiCheckCacheJsonBody",
|
||||||
|
"ExternalApiCheckCacheJsonBodyTags",
|
||||||
|
"ExternalApiCheckCacheResponse200",
|
||||||
|
"ExternalApiReportJsonBody",
|
||||||
|
"ExternalApiReportJsonBodyTags",
|
||||||
|
)
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar, Union
|
||||||
|
|
||||||
|
from attrs import define
|
||||||
|
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.external_api_check_cache_json_body_tags import ExternalApiCheckCacheJsonBodyTags
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ExternalApiCheckCacheJsonBody")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class ExternalApiCheckCacheJsonBody:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
requested_at (float): Unix timestamp in milliseconds
|
||||||
|
req_payload (Union[Unset, Any]): JSON-encoded request payload
|
||||||
|
tags (Union[Unset, ExternalApiCheckCacheJsonBodyTags]): Extra tags to attach to the call for filtering. Eg {
|
||||||
|
"userId": "123", "promptId": "populate-title" }
|
||||||
|
"""
|
||||||
|
|
||||||
|
requested_at: float
|
||||||
|
req_payload: Union[Unset, Any] = UNSET
|
||||||
|
tags: Union[Unset, "ExternalApiCheckCacheJsonBodyTags"] = UNSET
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
requested_at = self.requested_at
|
||||||
|
req_payload = self.req_payload
|
||||||
|
tags: Union[Unset, Dict[str, Any]] = UNSET
|
||||||
|
if not isinstance(self.tags, Unset):
|
||||||
|
tags = self.tags.to_dict()
|
||||||
|
|
||||||
|
field_dict: Dict[str, Any] = {}
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"requestedAt": requested_at,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if req_payload is not UNSET:
|
||||||
|
field_dict["reqPayload"] = req_payload
|
||||||
|
if tags is not UNSET:
|
||||||
|
field_dict["tags"] = tags
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||||
|
from ..models.external_api_check_cache_json_body_tags import ExternalApiCheckCacheJsonBodyTags
|
||||||
|
|
||||||
|
d = src_dict.copy()
|
||||||
|
requested_at = d.pop("requestedAt")
|
||||||
|
|
||||||
|
req_payload = d.pop("reqPayload", UNSET)
|
||||||
|
|
||||||
|
_tags = d.pop("tags", UNSET)
|
||||||
|
tags: Union[Unset, ExternalApiCheckCacheJsonBodyTags]
|
||||||
|
if isinstance(_tags, Unset):
|
||||||
|
tags = UNSET
|
||||||
|
else:
|
||||||
|
tags = ExternalApiCheckCacheJsonBodyTags.from_dict(_tags)
|
||||||
|
|
||||||
|
external_api_check_cache_json_body = cls(
|
||||||
|
requested_at=requested_at,
|
||||||
|
req_payload=req_payload,
|
||||||
|
tags=tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
return external_api_check_cache_json_body
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
from typing import Any, Dict, List, Type, TypeVar
|
||||||
|
|
||||||
|
from attrs import define, field
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ExternalApiCheckCacheJsonBodyTags")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class ExternalApiCheckCacheJsonBodyTags:
|
||||||
|
"""Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }"""
|
||||||
|
|
||||||
|
additional_properties: Dict[str, str] = field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
field_dict: Dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update({})
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
external_api_check_cache_json_body_tags = cls()
|
||||||
|
|
||||||
|
external_api_check_cache_json_body_tags.additional_properties = d
|
||||||
|
return external_api_check_cache_json_body_tags
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> List[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> str:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: str) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from typing import Any, Dict, Type, TypeVar, Union
|
||||||
|
|
||||||
|
from attrs import define
|
||||||
|
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ExternalApiCheckCacheResponse200")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class ExternalApiCheckCacheResponse200:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
resp_payload (Union[Unset, Any]): JSON-encoded response payload
|
||||||
|
"""
|
||||||
|
|
||||||
|
resp_payload: Union[Unset, Any] = UNSET
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
resp_payload = self.resp_payload
|
||||||
|
|
||||||
|
field_dict: Dict[str, Any] = {}
|
||||||
|
field_dict.update({})
|
||||||
|
if resp_payload is not UNSET:
|
||||||
|
field_dict["respPayload"] = resp_payload
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
resp_payload = d.pop("respPayload", UNSET)
|
||||||
|
|
||||||
|
external_api_check_cache_response_200 = cls(
|
||||||
|
resp_payload=resp_payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
return external_api_check_cache_response_200
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
from typing import TYPE_CHECKING, Any, Dict, Type, TypeVar, Union
|
||||||
|
|
||||||
|
from attrs import define
|
||||||
|
|
||||||
|
from ..types import UNSET, Unset
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from ..models.external_api_report_json_body_tags import ExternalApiReportJsonBodyTags
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ExternalApiReportJsonBody")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class ExternalApiReportJsonBody:
|
||||||
|
"""
|
||||||
|
Attributes:
|
||||||
|
requested_at (float): Unix timestamp in milliseconds
|
||||||
|
received_at (float): Unix timestamp in milliseconds
|
||||||
|
req_payload (Union[Unset, Any]): JSON-encoded request payload
|
||||||
|
resp_payload (Union[Unset, Any]): JSON-encoded response payload
|
||||||
|
status_code (Union[Unset, float]): HTTP status code of response
|
||||||
|
error_message (Union[Unset, str]): User-friendly error message
|
||||||
|
tags (Union[Unset, ExternalApiReportJsonBodyTags]): Extra tags to attach to the call for filtering. Eg {
|
||||||
|
"userId": "123", "promptId": "populate-title" }
|
||||||
|
"""
|
||||||
|
|
||||||
|
requested_at: float
|
||||||
|
received_at: float
|
||||||
|
req_payload: Union[Unset, Any] = UNSET
|
||||||
|
resp_payload: Union[Unset, Any] = UNSET
|
||||||
|
status_code: Union[Unset, float] = UNSET
|
||||||
|
error_message: Union[Unset, str] = UNSET
|
||||||
|
tags: Union[Unset, "ExternalApiReportJsonBodyTags"] = UNSET
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
requested_at = self.requested_at
|
||||||
|
received_at = self.received_at
|
||||||
|
req_payload = self.req_payload
|
||||||
|
resp_payload = self.resp_payload
|
||||||
|
status_code = self.status_code
|
||||||
|
error_message = self.error_message
|
||||||
|
tags: Union[Unset, Dict[str, Any]] = UNSET
|
||||||
|
if not isinstance(self.tags, Unset):
|
||||||
|
tags = self.tags.to_dict()
|
||||||
|
|
||||||
|
field_dict: Dict[str, Any] = {}
|
||||||
|
field_dict.update(
|
||||||
|
{
|
||||||
|
"requestedAt": requested_at,
|
||||||
|
"receivedAt": received_at,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if req_payload is not UNSET:
|
||||||
|
field_dict["reqPayload"] = req_payload
|
||||||
|
if resp_payload is not UNSET:
|
||||||
|
field_dict["respPayload"] = resp_payload
|
||||||
|
if status_code is not UNSET:
|
||||||
|
field_dict["statusCode"] = status_code
|
||||||
|
if error_message is not UNSET:
|
||||||
|
field_dict["errorMessage"] = error_message
|
||||||
|
if tags is not UNSET:
|
||||||
|
field_dict["tags"] = tags
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||||
|
from ..models.external_api_report_json_body_tags import ExternalApiReportJsonBodyTags
|
||||||
|
|
||||||
|
d = src_dict.copy()
|
||||||
|
requested_at = d.pop("requestedAt")
|
||||||
|
|
||||||
|
received_at = d.pop("receivedAt")
|
||||||
|
|
||||||
|
req_payload = d.pop("reqPayload", UNSET)
|
||||||
|
|
||||||
|
resp_payload = d.pop("respPayload", UNSET)
|
||||||
|
|
||||||
|
status_code = d.pop("statusCode", UNSET)
|
||||||
|
|
||||||
|
error_message = d.pop("errorMessage", UNSET)
|
||||||
|
|
||||||
|
_tags = d.pop("tags", UNSET)
|
||||||
|
tags: Union[Unset, ExternalApiReportJsonBodyTags]
|
||||||
|
if isinstance(_tags, Unset):
|
||||||
|
tags = UNSET
|
||||||
|
else:
|
||||||
|
tags = ExternalApiReportJsonBodyTags.from_dict(_tags)
|
||||||
|
|
||||||
|
external_api_report_json_body = cls(
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=req_payload,
|
||||||
|
resp_payload=resp_payload,
|
||||||
|
status_code=status_code,
|
||||||
|
error_message=error_message,
|
||||||
|
tags=tags,
|
||||||
|
)
|
||||||
|
|
||||||
|
return external_api_report_json_body
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
from typing import Any, Dict, List, Type, TypeVar
|
||||||
|
|
||||||
|
from attrs import define, field
|
||||||
|
|
||||||
|
T = TypeVar("T", bound="ExternalApiReportJsonBodyTags")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class ExternalApiReportJsonBodyTags:
|
||||||
|
"""Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }"""
|
||||||
|
|
||||||
|
additional_properties: Dict[str, str] = field(init=False, factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
field_dict: Dict[str, Any] = {}
|
||||||
|
field_dict.update(self.additional_properties)
|
||||||
|
field_dict.update({})
|
||||||
|
|
||||||
|
return field_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T:
|
||||||
|
d = src_dict.copy()
|
||||||
|
external_api_report_json_body_tags = cls()
|
||||||
|
|
||||||
|
external_api_report_json_body_tags.additional_properties = d
|
||||||
|
return external_api_report_json_body_tags
|
||||||
|
|
||||||
|
@property
|
||||||
|
def additional_keys(self) -> List[str]:
|
||||||
|
return list(self.additional_properties.keys())
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> str:
|
||||||
|
return self.additional_properties[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: str) -> None:
|
||||||
|
self.additional_properties[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key: str) -> None:
|
||||||
|
del self.additional_properties[key]
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
return key in self.additional_properties
|
||||||
1
client-libs/python/openpipe/api_client/py.typed
Normal file
1
client-libs/python/openpipe/api_client/py.typed
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Marker file for PEP 561
|
||||||
44
client-libs/python/openpipe/api_client/types.py
Normal file
44
client-libs/python/openpipe/api_client/types.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
""" Contains some shared types for properties """
|
||||||
|
from http import HTTPStatus
|
||||||
|
from typing import BinaryIO, Generic, Literal, MutableMapping, Optional, Tuple, TypeVar
|
||||||
|
|
||||||
|
from attrs import define
|
||||||
|
|
||||||
|
|
||||||
|
class Unset:
|
||||||
|
def __bool__(self) -> Literal[False]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
UNSET: Unset = Unset()
|
||||||
|
|
||||||
|
FileJsonType = Tuple[Optional[str], BinaryIO, Optional[str]]
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class File:
|
||||||
|
"""Contains information for file uploads"""
|
||||||
|
|
||||||
|
payload: BinaryIO
|
||||||
|
file_name: Optional[str] = None
|
||||||
|
mime_type: Optional[str] = None
|
||||||
|
|
||||||
|
def to_tuple(self) -> FileJsonType:
|
||||||
|
"""Return a tuple representation that httpx will accept for multipart/form-data"""
|
||||||
|
return self.file_name, self.payload, self.mime_type
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
@define
|
||||||
|
class Response(Generic[T]):
|
||||||
|
"""A response from an endpoint"""
|
||||||
|
|
||||||
|
status_code: HTTPStatus
|
||||||
|
content: bytes
|
||||||
|
headers: MutableMapping[str, str]
|
||||||
|
parsed: Optional[T]
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["File", "Response", "FileJsonType"]
|
||||||
42
client-libs/python/openpipe/merge_openai_chunks.py
Normal file
42
client-libs/python/openpipe/merge_openai_chunks.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
def merge_streamed_chunks(base: Optional[Any], chunk: Any) -> Any:
|
||||||
|
if base is None:
|
||||||
|
return merge_streamed_chunks({**chunk, "choices": []}, chunk)
|
||||||
|
|
||||||
|
choices = base["choices"].copy()
|
||||||
|
for choice in chunk["choices"]:
|
||||||
|
base_choice = next((c for c in choices if c["index"] == choice["index"]), None)
|
||||||
|
|
||||||
|
if base_choice:
|
||||||
|
base_choice["finish_reason"] = (
|
||||||
|
choice.get("finish_reason") or base_choice["finish_reason"]
|
||||||
|
)
|
||||||
|
base_choice["message"] = base_choice.get("message") or {"role": "assistant"}
|
||||||
|
|
||||||
|
if choice.get("delta") and choice["delta"].get("content"):
|
||||||
|
base_choice["message"]["content"] = (
|
||||||
|
base_choice["message"].get("content") or ""
|
||||||
|
) + (choice["delta"].get("content") or "")
|
||||||
|
if choice.get("delta") and choice["delta"].get("function_call"):
|
||||||
|
fn_call = base_choice["message"].get("function_call") or {}
|
||||||
|
fn_call["name"] = (fn_call.get("name") or "") + (
|
||||||
|
choice["delta"]["function_call"].get("name") or ""
|
||||||
|
)
|
||||||
|
fn_call["arguments"] = (fn_call.get("arguments") or "") + (
|
||||||
|
choice["delta"]["function_call"].get("arguments") or ""
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Here, we'll have to handle the omitted property "delta" manually
|
||||||
|
new_choice = {k: v for k, v in choice.items() if k != "delta"}
|
||||||
|
choices.append(
|
||||||
|
{**new_choice, "message": {"role": "assistant", **choice["delta"]}}
|
||||||
|
)
|
||||||
|
|
||||||
|
merged = {
|
||||||
|
**base,
|
||||||
|
"choices": choices,
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged
|
||||||
164
client-libs/python/openpipe/openai.py
Normal file
164
client-libs/python/openpipe/openai.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import openai as original_openai
|
||||||
|
import time
|
||||||
|
import inspect
|
||||||
|
|
||||||
|
from openpipe.merge_openai_chunks import merge_streamed_chunks
|
||||||
|
|
||||||
|
from .shared import report_async, report
|
||||||
|
|
||||||
|
|
||||||
|
class ChatCompletionWrapper:
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(original_openai.ChatCompletion, name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
return setattr(original_openai.ChatCompletion, name, value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, *args, **kwargs):
|
||||||
|
openpipe_options = kwargs.pop("openpipe", {})
|
||||||
|
|
||||||
|
requested_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
try:
|
||||||
|
chat_completion = original_openai.ChatCompletion.create(*args, **kwargs)
|
||||||
|
|
||||||
|
if inspect.isgenerator(chat_completion):
|
||||||
|
|
||||||
|
def _gen():
|
||||||
|
assembled_completion = None
|
||||||
|
for chunk in chat_completion:
|
||||||
|
assembled_completion = merge_streamed_chunks(
|
||||||
|
assembled_completion, chunk
|
||||||
|
)
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
report(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=assembled_completion,
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _gen()
|
||||||
|
else:
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
report(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=chat_completion,
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return chat_completion
|
||||||
|
except Exception as e:
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
if isinstance(e, original_openai.OpenAIError):
|
||||||
|
report(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=e.json_body,
|
||||||
|
error_message=str(e),
|
||||||
|
status_code=e.http_status,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
error_message=str(e),
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def acreate(cls, *args, **kwargs):
|
||||||
|
openpipe_options = kwargs.pop("openpipe", {})
|
||||||
|
|
||||||
|
requested_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
try:
|
||||||
|
chat_completion = original_openai.ChatCompletion.acreate(*args, **kwargs)
|
||||||
|
|
||||||
|
if inspect.isgenerator(chat_completion):
|
||||||
|
|
||||||
|
def _gen():
|
||||||
|
assembled_completion = None
|
||||||
|
for chunk in chat_completion:
|
||||||
|
assembled_completion = merge_streamed_chunks(
|
||||||
|
assembled_completion, chunk
|
||||||
|
)
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
report_async(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=assembled_completion,
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return _gen()
|
||||||
|
else:
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
report_async(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=chat_completion,
|
||||||
|
status_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
return chat_completion
|
||||||
|
except Exception as e:
|
||||||
|
received_at = int(time.time() * 1000)
|
||||||
|
|
||||||
|
if isinstance(e, original_openai.OpenAIError):
|
||||||
|
report_async(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
resp_payload=e.json_body,
|
||||||
|
error_message=str(e),
|
||||||
|
status_code=e.http_status,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
report_async(
|
||||||
|
openpipe_options=openpipe_options,
|
||||||
|
requested_at=requested_at,
|
||||||
|
received_at=received_at,
|
||||||
|
req_payload=kwargs,
|
||||||
|
error_message=str(e),
|
||||||
|
)
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIWrapper:
|
||||||
|
ChatCompletion = ChatCompletionWrapper()
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(original_openai, name)
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
return setattr(original_openai, name, value)
|
||||||
|
|
||||||
|
def __dir__(self):
|
||||||
|
return dir(original_openai) + ["openpipe_base_url", "openpipe_api_key"]
|
||||||
56
client-libs/python/openpipe/shared.py
Normal file
56
client-libs/python/openpipe/shared.py
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
from openpipe.api_client.api.default import external_api_report
|
||||||
|
from openpipe.api_client.client import AuthenticatedClient
|
||||||
|
from openpipe.api_client.models.external_api_report_json_body_tags import (
|
||||||
|
ExternalApiReportJsonBodyTags,
|
||||||
|
)
|
||||||
|
import toml
|
||||||
|
|
||||||
|
version = toml.load("pyproject.toml")["tool"]["poetry"]["version"]
|
||||||
|
|
||||||
|
configured_client = AuthenticatedClient(
|
||||||
|
base_url="https://app.openpipe.ai/api/v1", token=""
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_tags(openpipe_options):
|
||||||
|
tags = openpipe_options.get("tags") or {}
|
||||||
|
tags["$sdk"] = "python"
|
||||||
|
tags["$sdk_version"] = version
|
||||||
|
|
||||||
|
return ExternalApiReportJsonBodyTags.from_dict(tags)
|
||||||
|
|
||||||
|
|
||||||
|
def report(
|
||||||
|
openpipe_options={},
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
external_api_report.sync_detailed(
|
||||||
|
client=configured_client,
|
||||||
|
json_body=external_api_report.ExternalApiReportJsonBody(
|
||||||
|
**kwargs,
|
||||||
|
tags=_get_tags(openpipe_options),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# We don't want to break client apps if our API is down for some reason
|
||||||
|
print(f"Error reporting to OpenPipe: {e}")
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
|
||||||
|
async def report_async(
|
||||||
|
openpipe_options={},
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
await external_api_report.asyncio_detailed(
|
||||||
|
client=configured_client,
|
||||||
|
json_body=external_api_report.ExternalApiReportJsonBody(
|
||||||
|
**kwargs,
|
||||||
|
tags=_get_tags(openpipe_options),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# We don't want to break client apps if our API is down for some reason
|
||||||
|
print(f"Error reporting to OpenPipe: {e}")
|
||||||
|
print(e)
|
||||||
76
client-libs/python/openpipe/test_client.py
Normal file
76
client-libs/python/openpipe/test_client.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from dotenv import load_dotenv
|
||||||
|
from . import openai, configure_openpipe
|
||||||
|
import os
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
configure_openpipe(
|
||||||
|
base_url="http://localhost:3000/api", api_key=os.getenv("OPENPIPE_API_KEY")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip
|
||||||
|
def test_sync():
|
||||||
|
completion = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "system", "content": "count to 10"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
print(completion.choices[0].message.content)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip
|
||||||
|
def test_streaming():
|
||||||
|
completion = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "system", "content": "count to 10"}],
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
for chunk in completion:
|
||||||
|
print(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip
|
||||||
|
async def test_async():
|
||||||
|
acompletion = await openai.ChatCompletion.acreate(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "user", "content": "count down from 5"}],
|
||||||
|
)
|
||||||
|
|
||||||
|
print(acompletion.choices[0].message.content)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip
|
||||||
|
async def test_async_streaming():
|
||||||
|
acompletion = await openai.ChatCompletion.acreate(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "user", "content": "count down from 5"}],
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async for chunk in acompletion:
|
||||||
|
print(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sync_with_tags():
|
||||||
|
completion = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo",
|
||||||
|
messages=[{"role": "system", "content": "count to 10"}],
|
||||||
|
openpipe={"tags": {"promptId": "testprompt"}},
|
||||||
|
)
|
||||||
|
print("finished")
|
||||||
|
|
||||||
|
print(completion.choices[0].message.content)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.focus
|
||||||
|
def test_bad_call():
|
||||||
|
completion = openai.ChatCompletion.create(
|
||||||
|
model="gpt-3.5-turbo-blaster",
|
||||||
|
messages=[{"role": "system", "content": "count to 10"}],
|
||||||
|
stream=True,
|
||||||
|
)
|
||||||
1370
client-libs/python/poetry.lock
generated
Normal file
1370
client-libs/python/poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
client-libs/python/pyproject.toml
Normal file
35
client-libs/python/pyproject.toml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "openpipe"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Kyle Corbitt <kyle@corbt.com>"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.9"
|
||||||
|
openai = "^0.27.8"
|
||||||
|
httpx = "^0.24.1"
|
||||||
|
attrs = "^23.1.0"
|
||||||
|
python-dateutil = "^2.8.2"
|
||||||
|
toml = "^0.10.2"
|
||||||
|
|
||||||
|
[tool.poetry.dev-dependencies]
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
openapi-python-client = "^0.15.0"
|
||||||
|
black = "^23.7.0"
|
||||||
|
isort = "^5.12.0"
|
||||||
|
autoflake = "^2.2.0"
|
||||||
|
pytest = "^7.4.0"
|
||||||
|
python-dotenv = "^1.0.0"
|
||||||
|
pytest-asyncio = "^0.21.1"
|
||||||
|
pytest-watch = "^4.2.0"
|
||||||
|
pytest-testmon = "^2.0.12"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "auto"
|
||||||
|
markers = "focus"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
@@ -5,16 +5,13 @@
|
|||||||
"description": "The public API for reporting API calls to OpenPipe",
|
"description": "The public API for reporting API calls to OpenPipe",
|
||||||
"version": "0.1.0"
|
"version": "0.1.0"
|
||||||
},
|
},
|
||||||
"servers": [
|
"servers": [{ "url": "https://app.openpipe.ai/api" }],
|
||||||
{
|
|
||||||
"url": "https://app.openpipe.ai/api"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"/v1/check-cache": {
|
"/v1/check-cache": {
|
||||||
"post": {
|
"post": {
|
||||||
"operationId": "externalApi-checkCache",
|
"operationId": "externalApi-checkCache",
|
||||||
"description": "Check if a prompt is cached",
|
"description": "Check if a prompt is cached",
|
||||||
|
"security": [{ "Authorization": [] }],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
@@ -22,24 +19,18 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"requestedAt": {
|
"startTime": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Unix timestamp in milliseconds"
|
"description": "Unix timestamp in milliseconds"
|
||||||
},
|
},
|
||||||
"reqPayload": {
|
"reqPayload": { "description": "JSON-encoded request payload" },
|
||||||
"description": "JSON-encoded request payload"
|
|
||||||
},
|
|
||||||
"tags": {
|
"tags": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": { "type": "string" },
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["startTime"],
|
||||||
"requestedAt"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,18 +45,14 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"respPayload": {
|
"respPayload": { "description": "JSON-encoded response payload" }
|
||||||
"description": "JSON-encoded response payload"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"default": {
|
"default": { "$ref": "#/components/responses/error" }
|
||||||
"$ref": "#/components/responses/error"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -73,6 +60,7 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"operationId": "externalApi-report",
|
"operationId": "externalApi-report",
|
||||||
"description": "Report an API call",
|
"description": "Report an API call",
|
||||||
|
"security": [{ "Authorization": [] }],
|
||||||
"requestBody": {
|
"requestBody": {
|
||||||
"required": true,
|
"required": true,
|
||||||
"content": {
|
"content": {
|
||||||
@@ -80,40 +68,22 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"requestedAt": {
|
"startTime": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"description": "Unix timestamp in milliseconds"
|
"description": "Unix timestamp in milliseconds"
|
||||||
},
|
},
|
||||||
"receivedAt": {
|
"endTime": { "type": "number", "description": "Unix timestamp in milliseconds" },
|
||||||
"type": "number",
|
"reqPayload": { "description": "JSON-encoded request payload" },
|
||||||
"description": "Unix timestamp in milliseconds"
|
"respPayload": { "description": "JSON-encoded response payload" },
|
||||||
},
|
"respStatus": { "type": "number", "description": "HTTP status code of response" },
|
||||||
"reqPayload": {
|
"error": { "type": "string", "description": "User-friendly error message" },
|
||||||
"description": "JSON-encoded request payload"
|
|
||||||
},
|
|
||||||
"respPayload": {
|
|
||||||
"description": "JSON-encoded response payload"
|
|
||||||
},
|
|
||||||
"statusCode": {
|
|
||||||
"type": "number",
|
|
||||||
"description": "HTTP status code of response"
|
|
||||||
},
|
|
||||||
"errorMessage": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "User-friendly error message"
|
|
||||||
},
|
|
||||||
"tags": {
|
"tags": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": {
|
"additionalProperties": { "type": "string" },
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
"description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["startTime", "endTime"],
|
||||||
"requestedAt",
|
|
||||||
"receivedAt"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,26 +93,15 @@
|
|||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Successful response",
|
"description": "Successful response",
|
||||||
"content": {
|
"content": { "application/json": { "schema": {} } }
|
||||||
"application/json": {
|
|
||||||
"schema": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"default": {
|
"default": { "$ref": "#/components/responses/error" }
|
||||||
"$ref": "#/components/responses/error"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"securitySchemes": {
|
"securitySchemes": { "Authorization": { "type": "http", "scheme": "bearer" } },
|
||||||
"Authorization": {
|
|
||||||
"type": "http",
|
|
||||||
"scheme": "bearer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"responses": {
|
"responses": {
|
||||||
"error": {
|
"error": {
|
||||||
"description": "Error response",
|
"description": "Error response",
|
||||||
@@ -151,32 +110,19 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"message": {
|
"message": { "type": "string" },
|
||||||
"type": "string"
|
"code": { "type": "string" },
|
||||||
},
|
|
||||||
"code": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"issues": {
|
"issues": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": { "message": { "type": "string" } },
|
||||||
"message": {
|
"required": ["message"],
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"message"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": ["message", "code"],
|
||||||
"message",
|
|
||||||
"code"
|
|
||||||
],
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ export class OpenAI extends openai.OpenAI {
|
|||||||
|
|
||||||
constructor({
|
constructor({
|
||||||
openPipeApiKey = readEnv("OPENPIPE_API_KEY"),
|
openPipeApiKey = readEnv("OPENPIPE_API_KEY"),
|
||||||
openPipeBaseUrl = readEnv("OPENPIPE_BASE_URL") ??
|
openPipeBaseUrl = readEnv("OPENPIPE_BASE_URL") ?? `https://app.openpipe.ai/v1`,
|
||||||
`https://app.openpipe.ai/v1`,
|
|
||||||
...opts
|
...opts
|
||||||
}: ClientOptions = {}) {
|
}: ClientOptions = {}) {
|
||||||
super({ ...opts });
|
super({ ...opts });
|
||||||
@@ -26,7 +25,7 @@ export class OpenAI extends openai.OpenAI {
|
|||||||
const axiosInstance = axios.create({
|
const axiosInstance = axios.create({
|
||||||
baseURL: openPipeBaseUrl,
|
baseURL: openPipeBaseUrl,
|
||||||
headers: {
|
headers: {
|
||||||
"x-openpipe-api-key": openPipeApiKey,
|
Authorization: `Bearer ${openPipeApiKey}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
this.openPipeApi = new openPipeClient.DefaultApi(
|
this.openPipeApi = new openPipeClient.DefaultApi(
|
||||||
|
|||||||
Reference in New Issue
Block a user