Add auto-parameter support & refactor
This commit is contained in:
parent
7192ba064a
commit
cfbbd578aa
25 changed files with 232 additions and 152 deletions
|
|
@ -38,7 +38,7 @@ install_requires =
|
||||||
pyaml >= 21.0.0
|
pyaml >= 21.0.0
|
||||||
|
|
||||||
[options.package_data]
|
[options.package_data]
|
||||||
* = *.json, *.yaml, *.yml
|
* = *.json, *.yaml, *.yml, *.css
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
where = src
|
where = src
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from .context import configure
|
from .context import configure
|
||||||
from .deploy import create_service, process_batch, process_single
|
from .deploy import GreatAI
|
||||||
from .metrics import log_argument, log_metric
|
from .exceptions import ArgumentValidationError, MissingArgumentError
|
||||||
from .models import save_model, use_model
|
from .models import save_model, use_model
|
||||||
|
from .parameters import log_metric, parameter
|
||||||
|
|
|
||||||
1
great_ai/src/great_ai/great_ai/dashboard/__init__.py
Normal file
1
great_ai/src/great_ai/great_ai/dashboard/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
from .create_dash_app import create_dash_app
|
||||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
|
@ -162,7 +162,7 @@ def create_dash_app(function_name: str, function_docs: str) -> Flask:
|
||||||
dimensions=[
|
dimensions=[
|
||||||
get_dimension_descriptor(df, c)
|
get_dimension_descriptor(df, c)
|
||||||
for c in df.columns
|
for c in df.columns
|
||||||
if not c.startswith("arg:") and c not in {"id", "created", "output"}
|
if c not in {"id", "created", "output"}
|
||||||
],
|
],
|
||||||
line_color=accent_color,
|
line_color=accent_color,
|
||||||
)
|
)
|
||||||
|
|
@ -181,11 +181,12 @@ def get_dimension_descriptor(df: pd.DataFrame, column: str) -> Dict[str, Any]:
|
||||||
try:
|
try:
|
||||||
dimension["values"] = [float(v) for v in values]
|
dimension["values"] = [float(v) for v in values]
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
|
MAX_LENGTH = 40
|
||||||
unique_values = unique(values)
|
unique_values = unique(values)
|
||||||
value_mapping = {str(v): i for i, v in enumerate(unique_values)}
|
value_mapping = {str(v)[-MAX_LENGTH:]: i for i, v in enumerate(unique_values)}
|
||||||
|
|
||||||
dimension["values"] = [value_mapping[str(v)] for v in values]
|
dimension["values"] = [value_mapping[str(v)[-MAX_LENGTH:]] for v in values]
|
||||||
dimension["tickvals"] = list(value_mapping.values())
|
dimension["tickvals"] = list(value_mapping.values())
|
||||||
dimension["ticktext"] = list(value_mapping.keys())
|
dimension["ticktext"] = [k[-MAX_LENGTH:] for k in value_mapping.keys()]
|
||||||
|
|
||||||
return dimension
|
return dimension
|
||||||
|
|
@ -1,3 +1 @@
|
||||||
from .create_service import create_service
|
from .great_ai import GreatAI
|
||||||
from .process_batch import process_batch
|
|
||||||
from .process_single import process_single
|
|
||||||
|
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, Callable, Dict, List
|
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, status
|
|
||||||
from fastapi.middleware.wsgi import WSGIMiddleware
|
|
||||||
from fastapi.openapi.docs import get_swagger_ui_html
|
|
||||||
from fastapi.responses import RedirectResponse
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from ..context import get_context
|
|
||||||
from ..helper import snake_case_to_text
|
|
||||||
from ..metrics import create_dash_app
|
|
||||||
from ..tracing import TracingContext
|
|
||||||
from ..views import EvaluationFeedbackRequest, HealthCheckResponse, Query, Trace
|
|
||||||
|
|
||||||
PATH = Path(__file__).parent.resolve()
|
|
||||||
|
|
||||||
|
|
||||||
def create_service(
|
|
||||||
func: Callable[..., Any], disable_docs: bool = False, disable_metrics: bool = False
|
|
||||||
) -> FastAPI:
|
|
||||||
function_name = func.__name__
|
|
||||||
function_docs = func.__doc__
|
|
||||||
|
|
||||||
documentation = (
|
|
||||||
f"REST API wrapper for interacting with the '{function_name}' function.\n"
|
|
||||||
)
|
|
||||||
if function_docs:
|
|
||||||
documentation += function_docs
|
|
||||||
|
|
||||||
app = FastAPI(
|
|
||||||
title=snake_case_to_text(function_name),
|
|
||||||
description=documentation,
|
|
||||||
docs_url=None,
|
|
||||||
redoc_url=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.post("/evaluations", status_code=status.HTTP_200_OK, response_model=Trace)
|
|
||||||
def score(input: Any) -> Trace:
|
|
||||||
with TracingContext() as t:
|
|
||||||
result = func(input)
|
|
||||||
output = t.log_output(result)
|
|
||||||
return output
|
|
||||||
|
|
||||||
@app.get("/evaluations/:evaluation_id", status_code=status.HTTP_200_OK)
|
|
||||||
def get_evaluation(evaluation_id: str) -> Trace:
|
|
||||||
result = get_context().persistence.get_trace(evaluation_id)
|
|
||||||
if result is None:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@app.post(
|
|
||||||
"/evaluations/:evaluation_id/feedback", status_code=status.HTTP_202_ACCEPTED
|
|
||||||
)
|
|
||||||
def give_feedback(evaluation_id: str, input: EvaluationFeedbackRequest) -> None:
|
|
||||||
get_context().persistence.add_evaluation(evaluation_id, input.evaluation)
|
|
||||||
|
|
||||||
if not disable_docs:
|
|
||||||
|
|
||||||
@app.get("/docs", include_in_schema=False)
|
|
||||||
def custom_swagger_ui_html() -> HTMLResponse:
|
|
||||||
return get_swagger_ui_html(openapi_url="openapi.json", title=app.title)
|
|
||||||
|
|
||||||
@app.get("/docs/index.html", include_in_schema=False)
|
|
||||||
def redirect_to_entrypoint() -> RedirectResponse:
|
|
||||||
return RedirectResponse("/docs")
|
|
||||||
|
|
||||||
if not disable_metrics:
|
|
||||||
dash_app = create_dash_app(function_name, documentation)
|
|
||||||
app.mount(get_context().metrics_path, WSGIMiddleware(dash_app))
|
|
||||||
|
|
||||||
@app.get("/", include_in_schema=False)
|
|
||||||
def redirect_to_entrypoint() -> RedirectResponse:
|
|
||||||
return RedirectResponse("/metrics")
|
|
||||||
|
|
||||||
app.mount(
|
|
||||||
"/assets", StaticFiles(directory=PATH / "../metrics/assets"), name="static"
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.post("/query", status_code=status.HTTP_200_OK)
|
|
||||||
def query_metrics(query: Query) -> List[Dict[str, Any]]:
|
|
||||||
return get_context().persistence.query(
|
|
||||||
conjunctive_filters=query.filter,
|
|
||||||
sort_by=query.sort,
|
|
||||||
skip=query.skip,
|
|
||||||
take=query.take,
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/health", status_code=status.HTTP_200_OK)
|
|
||||||
def check_health() -> HealthCheckResponse:
|
|
||||||
return HealthCheckResponse(is_healthy=True)
|
|
||||||
|
|
||||||
return app
|
|
||||||
145
great_ai/src/great_ai/great_ai/deploy/great_ai.py
Normal file
145
great_ai/src/great_ai/great_ai/deploy/great_ai.py
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
import inspect
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Callable, Dict, Iterable, List, Optional, Sequence, Type, cast
|
||||||
|
|
||||||
|
from fastapi import FastAPI, HTTPException, status
|
||||||
|
from fastapi.middleware.wsgi import WSGIMiddleware
|
||||||
|
from fastapi.openapi.docs import get_swagger_ui_html
|
||||||
|
from fastapi.responses import RedirectResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
from pydantic import BaseModel, create_model
|
||||||
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
|
from great_ai.utilities.parallel_map import parallel_map
|
||||||
|
|
||||||
|
from ..context import get_context
|
||||||
|
from ..dashboard import create_dash_app
|
||||||
|
from ..helper import get_function_metadata_store, snake_case_to_text
|
||||||
|
from ..parameters import automatically_decorate_parameters
|
||||||
|
from ..tracing import TracingContext
|
||||||
|
from ..views import EvaluationFeedbackRequest, HealthCheckResponse, Query, Trace
|
||||||
|
|
||||||
|
PATH = Path(__file__).parent.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
class GreatAI(FastAPI):
|
||||||
|
def __init__(self, func: Callable[..., Any], *args: Any, **kwargs: Any):
|
||||||
|
self._func = automatically_decorate_parameters(func)
|
||||||
|
|
||||||
|
signature = inspect.signature(func)
|
||||||
|
parameters = {
|
||||||
|
p.name: (
|
||||||
|
p.annotation if p.annotation != inspect._empty else Any,
|
||||||
|
p.default if p.default != inspect._empty else ...,
|
||||||
|
)
|
||||||
|
for p in signature.parameters.values()
|
||||||
|
if p.name in get_function_metadata_store(func).input_parameter_names
|
||||||
|
}
|
||||||
|
|
||||||
|
schema: Type[BaseModel] = create_model("InputModel", **parameters) # type: ignore
|
||||||
|
|
||||||
|
def process_single(input_value: schema) -> Trace: # type: ignore
|
||||||
|
with TracingContext() as t:
|
||||||
|
result = self._func(**cast(BaseModel, input_value).dict())
|
||||||
|
output = t.log_output(result)
|
||||||
|
return output
|
||||||
|
|
||||||
|
self.process_single = process_single
|
||||||
|
|
||||||
|
super().__init__(
|
||||||
|
*args,
|
||||||
|
title=snake_case_to_text(func.__name__),
|
||||||
|
description=self.documentation,
|
||||||
|
docs_url=None,
|
||||||
|
version=get_function_metadata_store(func).version,
|
||||||
|
redoc_url=None,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def deploy(
|
||||||
|
disable_docs: bool = False, disable_metrics: bool = False
|
||||||
|
) -> Callable[[Callable[..., Any]], "GreatAI"]:
|
||||||
|
def decorator(func: Callable[..., Any]) -> GreatAI:
|
||||||
|
return GreatAI(func)._bootstrap_rest_api(
|
||||||
|
disable_docs=disable_docs, disable_metrics=disable_metrics
|
||||||
|
)
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def process_batch(
|
||||||
|
self,
|
||||||
|
batch: Iterable[Any],
|
||||||
|
concurrency: Optional[int] = None,
|
||||||
|
) -> Sequence[Trace]:
|
||||||
|
if not get_context().persistence.is_threadsafe:
|
||||||
|
concurrency = 1
|
||||||
|
get_context().logger.warning("Concurrency is ignored")
|
||||||
|
|
||||||
|
return parallel_map(self.process_single, batch, concurrency=concurrency)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def documentation(self) -> str:
|
||||||
|
return (
|
||||||
|
f"GreatAI wrapper for interacting with the '{self._func.__name__}' function.\n"
|
||||||
|
+ (self._func.__doc__ or "")
|
||||||
|
)
|
||||||
|
|
||||||
|
def _bootstrap_rest_api(
|
||||||
|
self, disable_docs: bool, disable_metrics: bool
|
||||||
|
) -> "GreatAI":
|
||||||
|
self.post("/evaluations", status_code=status.HTTP_200_OK, response_model=Trace)(
|
||||||
|
self.process_single
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.get("/evaluations/:evaluation_id", status_code=status.HTTP_200_OK)
|
||||||
|
def get_evaluation(evaluation_id: str) -> Trace:
|
||||||
|
result = get_context().persistence.get_trace(evaluation_id)
|
||||||
|
if result is None:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||||
|
return result
|
||||||
|
|
||||||
|
@self.post(
|
||||||
|
"/evaluations/:evaluation_id/feedback", status_code=status.HTTP_202_ACCEPTED
|
||||||
|
)
|
||||||
|
def give_feedback(evaluation_id: str, input: EvaluationFeedbackRequest) -> None:
|
||||||
|
get_context().persistence.add_evaluation(evaluation_id, input.evaluation)
|
||||||
|
|
||||||
|
if not disable_docs:
|
||||||
|
|
||||||
|
@self.get("/docs", include_in_schema=False)
|
||||||
|
def custom_swagger_ui_html() -> HTMLResponse:
|
||||||
|
return get_swagger_ui_html(openapi_url="openapi.json", title=self.title)
|
||||||
|
|
||||||
|
@self.get("/docs/index.html", include_in_schema=False)
|
||||||
|
def redirect_to_entrypoint() -> RedirectResponse:
|
||||||
|
return RedirectResponse("/docs")
|
||||||
|
|
||||||
|
if not disable_metrics:
|
||||||
|
dash_app = create_dash_app(self._func.__name__, self.documentation)
|
||||||
|
self.mount(get_context().metrics_path, WSGIMiddleware(dash_app))
|
||||||
|
|
||||||
|
@self.get("/", include_in_schema=False)
|
||||||
|
def redirect_to_entrypoint() -> RedirectResponse:
|
||||||
|
return RedirectResponse("/metrics")
|
||||||
|
|
||||||
|
self.mount(
|
||||||
|
"/assets",
|
||||||
|
StaticFiles(directory=PATH / "../dashboard/assets"),
|
||||||
|
name="static",
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.post("/query", status_code=status.HTTP_200_OK)
|
||||||
|
def query_metrics(query: Query) -> List[Dict[str, Any]]:
|
||||||
|
return get_context().persistence.query(
|
||||||
|
conjunctive_filters=query.filter,
|
||||||
|
sort_by=query.sort,
|
||||||
|
skip=query.skip,
|
||||||
|
take=query.take,
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.get("/health", status_code=status.HTTP_200_OK)
|
||||||
|
def check_health() -> HealthCheckResponse:
|
||||||
|
return HealthCheckResponse(is_healthy=True)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
from typing import Any, Callable, Iterable, Optional, Sequence
|
|
||||||
|
|
||||||
from great_ai.utilities.parallel_map import parallel_map
|
|
||||||
|
|
||||||
from ..context import get_context
|
|
||||||
from ..tracing import TracingContext
|
|
||||||
from ..views import Trace
|
|
||||||
|
|
||||||
|
|
||||||
def process_batch(
|
|
||||||
function: Callable[..., Any],
|
|
||||||
batch: Iterable[Any],
|
|
||||||
concurrency: Optional[int] = None,
|
|
||||||
) -> Sequence[Trace]:
|
|
||||||
def inner(input: Any) -> Trace:
|
|
||||||
with TracingContext() as t:
|
|
||||||
result = function(input)
|
|
||||||
output = t.log_output(result)
|
|
||||||
return output
|
|
||||||
|
|
||||||
if not get_context().persistence.is_threadsafe:
|
|
||||||
concurrency = 1
|
|
||||||
get_context().logger.warning("Concurrency is ignored")
|
|
||||||
|
|
||||||
return parallel_map(inner, batch, concurrency=concurrency)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
from typing import Any, Callable
|
|
||||||
|
|
||||||
from ..tracing import TracingContext
|
|
||||||
from ..views import Trace
|
|
||||||
|
|
||||||
|
|
||||||
def process_single(function: Callable[..., Any], input_value: Any) -> Trace:
|
|
||||||
with TracingContext() as t:
|
|
||||||
result = function(input_value)
|
|
||||||
output = t.log_output(result)
|
|
||||||
return output
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from .get_args import get_args
|
from .get_args import get_args
|
||||||
|
from .get_function_metadata_store import get_function_metadata_store
|
||||||
from .snake_case_to_text import snake_case_to_text
|
from .snake_case_to_text import snake_case_to_text
|
||||||
from .strip_lines import strip_lines
|
from .strip_lines import strip_lines
|
||||||
from .text_to_hex_color import text_to_hex_color
|
from .text_to_hex_color import text_to_hex_color
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, Mapping, Sequence
|
||||||
def get_args(
|
def get_args(
|
||||||
func: Callable[..., Any], args: Sequence[Any], kwargs: Mapping[str, Any]
|
func: Callable[..., Any], args: Sequence[Any], kwargs: Mapping[str, Any]
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
|
"""Return mapping from parameter names to actual argument values"""
|
||||||
signature = inspect.signature(func)
|
signature = inspect.signature(func)
|
||||||
filter_keys = [
|
filter_keys = [
|
||||||
param.name
|
param.name
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from typing import Any, Callable, cast
|
||||||
|
|
||||||
|
from ..views.function_metadata import FunctionMetadata
|
||||||
|
|
||||||
|
|
||||||
|
def get_function_metadata_store(func: Callable[..., Any]) -> FunctionMetadata:
|
||||||
|
any_func = cast(Any, func)
|
||||||
|
|
||||||
|
if not hasattr(any_func, "_great_ai_metadata"):
|
||||||
|
any_func._great_ai_metadata = FunctionMetadata()
|
||||||
|
|
||||||
|
return any_func._great_ai_metadata
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
from .create_dash_app import create_dash_app
|
|
||||||
from .log_argument import log_argument
|
|
||||||
from .log_metric import log_metric
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from typing import Any, Callable, Dict, List, Literal, Union
|
from typing import Any, Callable, Dict, List, Literal, Union
|
||||||
|
|
||||||
|
from ..helper import get_function_metadata_store
|
||||||
from ..tracing import TracingContext
|
from ..tracing import TracingContext
|
||||||
from ..views import Model
|
from ..views import Model
|
||||||
from .load_model import load_model
|
from .load_model import load_model
|
||||||
|
|
@ -11,7 +12,7 @@ def use_model(
|
||||||
*,
|
*,
|
||||||
version: Union[int, Literal["latest"]],
|
version: Union[int, Literal["latest"]],
|
||||||
return_path: bool = False,
|
return_path: bool = False,
|
||||||
model_kwarg_name: str = "model"
|
model_kwarg_name: str = "model",
|
||||||
) -> Callable[..., Any]:
|
) -> Callable[..., Any]:
|
||||||
assert isinstance(version, int) or version == "latest"
|
assert isinstance(version, int) or version == "latest"
|
||||||
|
|
||||||
|
|
@ -22,6 +23,12 @@ def use_model(
|
||||||
)
|
)
|
||||||
|
|
||||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
|
store = get_function_metadata_store(func)
|
||||||
|
store.model_parameter_names.append(model_kwarg_name)
|
||||||
|
if store.version:
|
||||||
|
store.version += "|"
|
||||||
|
store.version += f"{key}:{actual_version}"
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
|
def wrapper(*args: List[Any], **kwargs: Dict[str, Any]) -> Any:
|
||||||
context = TracingContext.get_current_context()
|
context = TracingContext.get_current_context()
|
||||||
|
|
|
||||||
3
great_ai/src/great_ai/great_ai/parameters/__init__.py
Normal file
3
great_ai/src/great_ai/great_ai/parameters/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .automatically_decorate_parameters import automatically_decorate_parameters
|
||||||
|
from .log_metric import log_metric
|
||||||
|
from .parameter import parameter
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import inspect
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
from great_ai.great_ai.helper.get_function_metadata_store import (
|
||||||
|
get_function_metadata_store,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .parameter import parameter
|
||||||
|
|
||||||
|
|
||||||
|
def automatically_decorate_parameters(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
|
signature = inspect.signature(func)
|
||||||
|
parameter_names = [
|
||||||
|
param.name
|
||||||
|
for param in signature.parameters.values()
|
||||||
|
if param.kind == param.POSITIONAL_OR_KEYWORD
|
||||||
|
]
|
||||||
|
|
||||||
|
metadata = get_function_metadata_store(func)
|
||||||
|
|
||||||
|
for name in parameter_names:
|
||||||
|
if (
|
||||||
|
name not in metadata.model_parameter_names
|
||||||
|
and name not in metadata.input_parameter_names
|
||||||
|
):
|
||||||
|
func = parameter(name)(func)
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
@ -2,38 +2,43 @@ from functools import wraps
|
||||||
from typing import Any, Callable, Dict
|
from typing import Any, Callable, Dict
|
||||||
|
|
||||||
from ..exceptions import ArgumentValidationError
|
from ..exceptions import ArgumentValidationError
|
||||||
from ..helper import get_args
|
from ..helper import get_args, get_function_metadata_store
|
||||||
from ..tracing import TracingContext
|
from ..tracing import TracingContext
|
||||||
|
|
||||||
|
|
||||||
def log_argument(
|
def parameter(
|
||||||
argument_name: str,
|
parameter_name: str,
|
||||||
*,
|
*,
|
||||||
validator: Callable[[Any], bool] = lambda _: True,
|
validator: Callable[[Any], bool] = lambda _: True,
|
||||||
) -> Callable[..., Any]:
|
) -> Callable[..., Any]:
|
||||||
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
||||||
actual_name = f"arg:{func.__name__}:{argument_name}"
|
get_function_metadata_store(func).input_parameter_names.append(parameter_name)
|
||||||
|
|
||||||
|
actual_name = f"arg:{func.__name__}:{parameter_name}"
|
||||||
|
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Any:
|
def wrapper(*args: Any, **kwargs: Dict[str, Any]) -> Any:
|
||||||
arguments = get_args(func, args, kwargs)
|
arguments = get_args(func, args, kwargs)
|
||||||
argument = arguments[argument_name]
|
argument = arguments[parameter_name]
|
||||||
|
|
||||||
expected_type = func.__annotations__.get(argument_name)
|
expected_type = func.__annotations__.get(parameter_name)
|
||||||
|
|
||||||
if expected_type is not None and not isinstance(argument, expected_type):
|
if expected_type is not None and not isinstance(argument, expected_type):
|
||||||
raise ArgumentValidationError(
|
raise ArgumentValidationError(
|
||||||
f"Argument {argument_name} in {func.__name__} has the wrong type, expected: {expected_type.__name__}, got: {type(argument).__name__}"
|
f"Argument {parameter_name} in {func.__name__} has the wrong type, expected: {expected_type.__name__}, got: {type(argument).__name__}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not validator(argument):
|
if not validator(argument):
|
||||||
raise ArgumentValidationError(
|
raise ArgumentValidationError(
|
||||||
f"Argument {argument_name} in {func.__name__} did not pass validation"
|
f"Argument {parameter_name} in {func.__name__} did not pass validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
context = TracingContext.get_current_context()
|
context = TracingContext.get_current_context()
|
||||||
if context:
|
if context:
|
||||||
context.log_value(name=actual_name, value=argument)
|
context.log_value(name=actual_name, value=argument)
|
||||||
|
if isinstance(argument, str):
|
||||||
|
context.log_value(name=f"{actual_name}:length", value=len(argument))
|
||||||
|
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
from .evaluation_feedback_request import EvaluationFeedbackRequest
|
from .evaluation_feedback_request import EvaluationFeedbackRequest
|
||||||
from .filter import Filter
|
from .filter import Filter
|
||||||
|
from .function_metadata import FunctionMetadata
|
||||||
from .health_check_response import HealthCheckResponse
|
from .health_check_response import HealthCheckResponse
|
||||||
from .model import Model
|
from .model import Model
|
||||||
from .operators import operators
|
from .operators import operators
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionMetadata(BaseModel):
|
||||||
|
input_parameter_names: List[str] = []
|
||||||
|
model_parameter_names: List[str] = []
|
||||||
|
version: str = ""
|
||||||
Loading…
Add table
Add a link
Reference in a new issue