agenta 0.26.0a0__py3-none-any.whl → 0.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of agenta might be problematic. Click here for more details.

Files changed (85) hide show
  1. agenta/__init__.py +29 -10
  2. agenta/cli/helper.py +5 -1
  3. agenta/client/backend/__init__.py +14 -0
  4. agenta/client/backend/apps/client.py +28 -20
  5. agenta/client/backend/client.py +47 -16
  6. agenta/client/backend/containers/client.py +5 -1
  7. agenta/client/backend/core/__init__.py +2 -1
  8. agenta/client/backend/core/client_wrapper.py +6 -6
  9. agenta/client/backend/core/file.py +33 -11
  10. agenta/client/backend/core/http_client.py +45 -31
  11. agenta/client/backend/core/pydantic_utilities.py +144 -29
  12. agenta/client/backend/core/request_options.py +3 -0
  13. agenta/client/backend/core/serialization.py +139 -42
  14. agenta/client/backend/evaluations/client.py +7 -2
  15. agenta/client/backend/evaluators/client.py +349 -1
  16. agenta/client/backend/observability/client.py +11 -2
  17. agenta/client/backend/testsets/client.py +10 -10
  18. agenta/client/backend/types/__init__.py +14 -0
  19. agenta/client/backend/types/app.py +1 -0
  20. agenta/client/backend/types/app_variant_response.py +3 -1
  21. agenta/client/backend/types/config_dto.py +32 -0
  22. agenta/client/backend/types/config_response_model.py +32 -0
  23. agenta/client/backend/types/create_span.py +3 -2
  24. agenta/client/backend/types/environment_output.py +1 -0
  25. agenta/client/backend/types/environment_output_extended.py +1 -0
  26. agenta/client/backend/types/evaluation.py +1 -2
  27. agenta/client/backend/types/evaluator.py +2 -0
  28. agenta/client/backend/types/evaluator_config.py +1 -0
  29. agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
  30. agenta/client/backend/types/evaluator_output_interface.py +21 -0
  31. agenta/client/backend/types/human_evaluation.py +1 -2
  32. agenta/client/backend/types/lifecycle_dto.py +24 -0
  33. agenta/client/backend/types/llm_tokens.py +2 -2
  34. agenta/client/backend/types/reference_dto.py +23 -0
  35. agenta/client/backend/types/reference_request_model.py +23 -0
  36. agenta/client/backend/types/span.py +1 -0
  37. agenta/client/backend/types/span_detail.py +7 -1
  38. agenta/client/backend/types/test_set_output_response.py +5 -2
  39. agenta/client/backend/types/trace_detail.py +7 -1
  40. agenta/client/backend/types/with_pagination.py +4 -2
  41. agenta/client/backend/variants/client.py +1565 -272
  42. agenta/docker/docker-assets/Dockerfile.cloud.template +1 -1
  43. agenta/sdk/__init__.py +44 -7
  44. agenta/sdk/agenta_init.py +85 -33
  45. agenta/sdk/context/__init__.py +0 -0
  46. agenta/sdk/context/routing.py +26 -0
  47. agenta/sdk/context/tracing.py +3 -0
  48. agenta/sdk/decorators/__init__.py +0 -0
  49. agenta/sdk/decorators/{llm_entrypoint.py → routing.py} +216 -191
  50. agenta/sdk/decorators/tracing.py +218 -99
  51. agenta/sdk/litellm/__init__.py +1 -0
  52. agenta/sdk/litellm/litellm.py +288 -0
  53. agenta/sdk/managers/__init__.py +6 -0
  54. agenta/sdk/managers/config.py +318 -0
  55. agenta/sdk/managers/deployment.py +45 -0
  56. agenta/sdk/managers/shared.py +639 -0
  57. agenta/sdk/managers/variant.py +182 -0
  58. agenta/sdk/router.py +0 -7
  59. agenta/sdk/tracing/__init__.py +1 -0
  60. agenta/sdk/tracing/attributes.py +141 -0
  61. agenta/sdk/tracing/context.py +24 -0
  62. agenta/sdk/tracing/conventions.py +49 -0
  63. agenta/sdk/tracing/exporters.py +65 -0
  64. agenta/sdk/tracing/inline.py +1252 -0
  65. agenta/sdk/tracing/processors.py +117 -0
  66. agenta/sdk/tracing/spans.py +136 -0
  67. agenta/sdk/tracing/tracing.py +233 -0
  68. agenta/sdk/types.py +49 -2
  69. agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
  70. agenta/sdk/utils/debug.py +5 -5
  71. agenta/sdk/utils/exceptions.py +52 -0
  72. agenta/sdk/utils/globals.py +3 -5
  73. agenta/sdk/{tracing/logger.py → utils/logging.py} +3 -5
  74. agenta/sdk/utils/singleton.py +13 -0
  75. {agenta-0.26.0a0.dist-info → agenta-0.27.0.dist-info}/METADATA +5 -1
  76. {agenta-0.26.0a0.dist-info → agenta-0.27.0.dist-info}/RECORD +78 -57
  77. agenta/sdk/config_manager.py +0 -205
  78. agenta/sdk/context.py +0 -41
  79. agenta/sdk/decorators/base.py +0 -10
  80. agenta/sdk/tracing/callbacks.py +0 -187
  81. agenta/sdk/tracing/llm_tracing.py +0 -617
  82. agenta/sdk/tracing/tasks_manager.py +0 -129
  83. agenta/sdk/tracing/tracing_context.py +0 -27
  84. {agenta-0.26.0a0.dist-info → agenta-0.27.0.dist-info}/WHEEL +0 -0
  85. {agenta-0.26.0a0.dist-info → agenta-0.27.0.dist-info}/entry_points.txt +0 -0
@@ -1,31 +1,25 @@
1
- """The code for the Agenta SDK"""
2
-
3
- from agenta.sdk.utils.debug import debug, DEBUG, SHIFT
4
- import os
5
- import sys
6
- import time
7
- import json
8
- import inspect
9
- import argparse
10
- import asyncio
11
- import traceback
12
- import functools
1
+ from typing import Type, Any, Callable, Dict, Optional, Tuple, List
2
+ from annotated_types import Ge, Le, Gt, Lt
3
+ from pydantic import BaseModel, HttpUrl, ValidationError
4
+ from json import dumps
5
+ from inspect import signature, iscoroutinefunction, Signature, Parameter, _empty
6
+ from argparse import ArgumentParser
7
+ from functools import wraps
8
+ from asyncio import sleep, get_event_loop
9
+ from traceback import format_exc, format_exception
13
10
  from pathlib import Path
14
11
  from tempfile import NamedTemporaryFile
15
- from typing import Any, Callable, Dict, Optional, Tuple, List
16
- from importlib.metadata import version
12
+ from os import environ
17
13
 
18
14
  from fastapi.middleware.cors import CORSMiddleware
19
15
  from fastapi import Body, FastAPI, UploadFile, HTTPException
20
16
 
21
- import agenta as ag
22
- from agenta.sdk.context import save_context
17
+ from agenta.sdk.context.routing import routing_context_manager, routing_context
18
+ from agenta.sdk.context.tracing import tracing_context
23
19
  from agenta.sdk.router import router as router
24
- from agenta.sdk.tracing.logger import llm_logger as logging
25
- from agenta.sdk.tracing.tracing_context import tracing_context, TracingContext
26
- from agenta.sdk.decorators.base import BaseDecorator
20
+ from agenta.sdk.utils.exceptions import suppress
21
+ from agenta.sdk.utils.logging import log
27
22
  from agenta.sdk.types import (
28
- Context,
29
23
  DictInput,
30
24
  FloatParam,
31
25
  InFile,
@@ -39,16 +33,8 @@ from agenta.sdk.types import (
39
33
  BaseResponse,
40
34
  BinaryParam,
41
35
  )
42
- import pydantic
43
-
44
- from pydantic import BaseModel
45
- from typing import Type
46
- from annotated_types import Ge, Le, Gt, Lt
47
-
48
- from pydantic import BaseModel, HttpUrl
49
- import contextvars
50
- from contextlib import contextmanager
51
36
 
37
+ import agenta as ag
52
38
 
53
39
  app = FastAPI()
54
40
 
@@ -67,36 +53,14 @@ app.add_middleware(
67
53
  app.include_router(router, prefix="")
68
54
 
69
55
 
70
- logging.setLevel("DEBUG")
71
-
72
- route_context = contextvars.ContextVar("route_context", default={})
73
-
74
-
75
- @contextmanager
76
- def route_context_manager(
77
- config: Optional[Dict[str, Any]] = None,
78
- environment: Optional[str] = None,
79
- version: Optional[str] = None,
80
- variant: Optional[str] = None,
81
- ):
82
- context = {
83
- "config": config,
84
- "environment": environment,
85
- "version": version,
86
- "variant": variant,
87
- }
88
- token = route_context.set(context)
89
- try:
90
- yield
91
- finally:
92
- route_context.reset(token)
56
+ log.setLevel("DEBUG")
93
57
 
94
58
 
95
59
  class PathValidator(BaseModel):
96
60
  url: HttpUrl
97
61
 
98
62
 
99
- class route(BaseDecorator):
63
+ class route:
100
64
  # This decorator is used to expose specific stages of a workflow (embedding, retrieval, summarization, etc.)
101
65
  # as independent endpoints. It is designed for backward compatibility with existing code that uses
102
66
  # the @entrypoint decorator, which has certain limitations. By using @route(), we can create new
@@ -118,7 +82,7 @@ class route(BaseDecorator):
118
82
  return f
119
83
 
120
84
 
121
- class entrypoint(BaseDecorator):
85
+ class entrypoint:
122
86
  """
123
87
  Decorator class to wrap a function for HTTP POST, terminal exposure and enable tracing.
124
88
 
@@ -152,19 +116,20 @@ class entrypoint(BaseDecorator):
152
116
  routes = list()
153
117
 
154
118
  def __init__(
155
- self, func: Callable[..., Any], route_path="", config_schema: BaseModel = None
119
+ self,
120
+ func: Callable[..., Any],
121
+ route_path="",
122
+ config_schema: Optional[BaseModel] = None,
156
123
  ):
157
- logging.info(f"Using Agenta Python SDK version {version('agenta')}")
158
-
159
124
  DEFAULT_PATH = "generate"
160
125
  PLAYGROUND_PATH = "/playground"
161
126
  RUN_PATH = "/run"
162
- func_signature = inspect.signature(func)
127
+ func_signature = signature(func)
163
128
  try:
164
129
  config = (
165
130
  config_schema() if config_schema else None
166
131
  ) # we initialize the config object to be able to use it
167
- except pydantic.ValidationError as e:
132
+ except ValidationError as e:
168
133
  raise ValueError(
169
134
  f"Error initializing config_schema. Please ensure all required fields have default values: {str(e)}"
170
135
  ) from e
@@ -176,20 +141,19 @@ class entrypoint(BaseDecorator):
176
141
  config_params = config.dict() if config else ag.config.all()
177
142
  ingestible_files = self.extract_ingestible_files(func_signature)
178
143
 
144
+ self.route_path = route_path
145
+
179
146
  ### --- Playground --- #
180
- @debug()
181
- @functools.wraps(func)
147
+ @wraps(func)
182
148
  async def wrapper(*args, **kwargs) -> Any:
183
149
  func_params, api_config_params = self.split_kwargs(kwargs, config_params)
184
150
  self.ingest_files(func_params, ingestible_files)
185
151
  if not config_schema:
186
152
  ag.config.set(**api_config_params)
187
153
 
188
- # Set the configuration and environment of the LLM app parent span at run-time
189
- ag.tracing.update_baggage(
190
- {"config": config_params, "environment": "playground"}
191
- )
192
- with route_context_manager(config=api_config_params):
154
+ with routing_context_manager(
155
+ config=api_config_params,
156
+ ):
193
157
  entrypoint_result = await self.execute_function(
194
158
  func,
195
159
  True, # inline trace: True
@@ -241,12 +205,13 @@ class entrypoint(BaseDecorator):
241
205
  )
242
206
  ### ---------------------------- #
243
207
 
244
- ### --- Deployed / Published --- #
245
- @debug()
246
- @functools.wraps(func)
208
+ ### --- Deployed --- #
209
+ @wraps(func)
247
210
  async def wrapper_deployed(*args, **kwargs) -> Any:
248
211
  func_params = {
249
- k: v for k, v in kwargs.items() if k not in ["config", "environment"]
212
+ k: v
213
+ for k, v in kwargs.items()
214
+ if k not in ["config", "environment", "app"]
250
215
  }
251
216
  if not config_schema:
252
217
  if "environment" in kwargs and kwargs["environment"] is not None:
@@ -256,12 +221,19 @@ class entrypoint(BaseDecorator):
256
221
  else:
257
222
  ag.config.pull(config_name="default")
258
223
 
259
- # Set the configuration and environment of the LLM app parent span at run-time
260
- ag.tracing.update_baggage(
261
- {"config": config_params, "environment": kwargs["environment"]}
262
- )
263
- with route_context_manager(
264
- variant=kwargs["config"], environment=kwargs["environment"]
224
+ app_id = environ.get("AGENTA_APP_ID")
225
+
226
+ with routing_context_manager(
227
+ application={
228
+ "id": app_id,
229
+ "slug": kwargs["app"],
230
+ },
231
+ variant={
232
+ "slug": kwargs.get("config"),
233
+ },
234
+ environment={
235
+ "slug": kwargs.get("environment"),
236
+ },
265
237
  ):
266
238
  entrypoint_result = await self.execute_function(
267
239
  func,
@@ -284,7 +256,7 @@ class entrypoint(BaseDecorator):
284
256
 
285
257
  route_deployed = f"{RUN_PATH}{route_path}"
286
258
  app.post(route_deployed, response_model=BaseResponse)(wrapper_deployed)
287
- ### ---------------------------- #
259
+ ### ---------------- #
288
260
 
289
261
  ### --- Update OpenAPI --- #
290
262
  app.openapi_schema = None # Forces FastAPI to re-generate the schema
@@ -315,8 +287,8 @@ class entrypoint(BaseDecorator):
315
287
 
316
288
  def extract_ingestible_files(
317
289
  self,
318
- func_signature: inspect.Signature,
319
- ) -> Dict[str, inspect.Parameter]:
290
+ func_signature: Signature,
291
+ ) -> Dict[str, Parameter]:
320
292
  """Extract parameters annotated as InFile from function signature."""
321
293
 
322
294
  return {
@@ -343,7 +315,7 @@ class entrypoint(BaseDecorator):
343
315
  def ingest_files(
344
316
  self,
345
317
  func_params: Dict[str, Any],
346
- ingestible_files: Dict[str, inspect.Parameter],
318
+ ingestible_files: Dict[str, Parameter],
347
319
  ) -> None:
348
320
  """Ingest files specified in function parameters."""
349
321
 
@@ -352,96 +324,134 @@ class entrypoint(BaseDecorator):
352
324
  func_params[name] = self.ingest_file(func_params[name])
353
325
 
354
326
  async def execute_function(
355
- self, func: Callable[..., Any], inline_trace, *args, **func_params
327
+ self,
328
+ func: Callable[..., Any],
329
+ inline_trace,
330
+ *args,
331
+ **func_params,
356
332
  ):
357
- """Execute the function and handle any exceptions."""
333
+ log.info(f"---------------------------")
334
+ log.info(f"Agenta SDK - running route: {repr(self.route_path or '/')}")
335
+ log.info(f"---------------------------")
336
+
337
+ tracing_context.set(routing_context.get())
358
338
 
359
339
  try:
360
- """Note: The following block is for backward compatibility.
361
- It allows functions to work seamlessly whether they are synchronous or asynchronous.
362
- For synchronous functions, it calls them directly, while for asynchronous functions,
363
- it awaits their execution.
364
- """
365
- logging.info(f"Using Agenta Python SDK version {version('agenta')}")
340
+ result = (
341
+ await func(*args, **func_params["params"])
342
+ if iscoroutinefunction(func)
343
+ else func(*args, **func_params["params"])
344
+ )
366
345
 
367
- WAIT_FOR_SPANS = True
368
- TIMEOUT = 1
369
- TIMESTEP = 0.1
370
- NOFSTEPS = TIMEOUT / TIMESTEP
346
+ return await self.handle_success(result, inline_trace)
371
347
 
372
- data = None
373
- trace = None
348
+ except Exception as error:
349
+ self.handle_failure(error)
374
350
 
375
- token = None
376
- if tracing_context.get() is None:
377
- token = tracing_context.set(TracingContext())
351
+ async def handle_success(self, result: Any, inline_trace: bool):
352
+ data = None
353
+ trace = dict()
378
354
 
379
- is_coroutine_function = inspect.iscoroutinefunction(func)
355
+ with suppress():
356
+ data = self.patch_result(result)
380
357
 
381
- if is_coroutine_function:
382
- result = await func(*args, **func_params["params"])
383
- else:
384
- result = func(*args, **func_params["params"])
358
+ if inline_trace:
359
+ trace = await self.fetch_inline_trace(inline_trace)
385
360
 
386
- if token is not None:
387
- if WAIT_FOR_SPANS:
388
- remaining_steps = NOFSTEPS
361
+ log.info(f"----------------------------------")
362
+ log.info(f"Agenta SDK - exiting with success: 200")
363
+ log.info(f"----------------------------------")
389
364
 
390
- while not ag.tracing.is_trace_ready() and remaining_steps > 0:
391
- await asyncio.sleep(TIMESTEP)
392
- remaining_steps -= 1
365
+ return BaseResponse(data=data, trace=trace)
393
366
 
394
- trace = ag.tracing.dump_trace()
367
+ def handle_failure(self, error: Exception):
368
+ log.error("--------------------------------------------------")
369
+ log.error("Agenta SDK - handling application exception below:")
370
+ log.error("--------------------------------------------------")
371
+ log.error(format_exc().strip("\n"))
372
+ log.error("--------------------------------------------------")
395
373
 
396
- if not inline_trace:
397
- trace = {"trace_id": trace["trace_id"]}
374
+ status_code = error.status_code if hasattr(error, "status_code") else 500
375
+ message = str(error)
376
+ stacktrace = format_exception(error, value=error, tb=error.__traceback__) # type: ignore
377
+ detail = {"message": message, "stacktrace": stacktrace}
398
378
 
399
- ag.tracing.flush_spans()
400
- tracing_context.reset(token)
379
+ log.error(f"----------------------------------")
380
+ log.error(f"Agenta SDK - exiting with failure: {status_code}")
381
+ log.error(f"----------------------------------")
401
382
 
402
- if isinstance(result, Context):
403
- save_context(result)
383
+ raise HTTPException(status_code=status_code, detail=detail)
404
384
 
405
- data = result
385
+ def patch_result(self, result: Any):
386
+ """
387
+ Patch the result to only include the message if the result is a FuncResponse-style dictionary with message, cost, and usage keys.
406
388
 
407
- # PATCH : if result is not a dict, make it a dict
408
- if not isinstance(result, dict):
409
- data = str(result)
410
- else:
411
- # PATCH : if result is a legacy dict, clean it up
412
- if (
413
- "message" in result.keys()
414
- and "cost" in result.keys()
415
- and "usage" in result.keys()
416
- ):
417
- data = str(result["message"])
389
+ Example:
390
+ ```python
391
+ result = {
392
+ "message": "Hello, world!",
393
+ "cost": 0.5,
394
+ "usage": {
395
+ "prompt_tokens": 10,
396
+ "completion_tokens": 20,
397
+ "total_tokens": 30
398
+ }
399
+ }
400
+ result = patch_result(result)
401
+ print(result)
402
+ # Output: "Hello, world!"
403
+ ```
404
+ """
405
+ data = (
406
+ result["message"]
407
+ if isinstance(result, dict)
408
+ and all(key in result for key in ["message", "cost", "usage"])
409
+ else result
410
+ )
418
411
 
419
- # END OF PATH
412
+ if data is None:
413
+ data = (
414
+ "Function executed successfully, but did return None. \n Are you sure you did not forget to return a value?",
415
+ )
420
416
 
421
- if data is None:
422
- data = (
423
- "Function executed successfully, but did return None. \n Are you sure you did not forget to return a value?",
424
- )
417
+ if not isinstance(result, dict):
418
+ data = str(data)
425
419
 
426
- response = BaseResponse(data=data, trace=trace)
420
+ return data
427
421
 
428
- # logging.debug(response)
422
+ async def fetch_inline_trace(self, inline_trace):
423
+ WAIT_FOR_SPANS = True
424
+ TIMEOUT = 1
425
+ TIMESTEP = 0.1
426
+ FINALSTEP = 0.001
427
+ NOFSTEPS = TIMEOUT / TIMESTEP
429
428
 
430
- return response
429
+ trace = None
431
430
 
432
- except Exception as e:
433
- self.handle_exception(e)
431
+ root_context: Dict[str, Any] = tracing_context.get().get("root")
434
432
 
435
- def handle_exception(self, e: Exception):
436
- status_code = e.status_code if hasattr(e, "status_code") else 500
437
- message = str(e)
438
- stacktrace = traceback.format_exception(e, value=e, tb=e.__traceback__) # type: ignore
439
- detail = {"message": message, "stacktrace": stacktrace}
433
+ trace_id = root_context.get("trace_id") if root_context else None
440
434
 
441
- raise HTTPException(
442
- status_code=status_code,
443
- detail=detail,
444
- )
435
+ if trace_id is not None:
436
+ if inline_trace:
437
+ if WAIT_FOR_SPANS:
438
+ remaining_steps = NOFSTEPS
439
+
440
+ while (
441
+ not ag.tracing.is_inline_trace_ready(trace_id)
442
+ and remaining_steps > 0
443
+ ):
444
+ await sleep(TIMESTEP)
445
+
446
+ remaining_steps -= 1
447
+
448
+ await sleep(FINALSTEP)
449
+
450
+ trace = ag.tracing.get_inline_trace(trace_id)
451
+ else:
452
+ trace = {"trace_id": trace_id}
453
+
454
+ return trace
445
455
 
446
456
  def update_wrapper_signature(
447
457
  self, wrapper: Callable[..., Any], updated_params: List
@@ -451,25 +461,25 @@ class entrypoint(BaseDecorator):
451
461
 
452
462
  Args:
453
463
  wrapper (callable): A callable object, such as a function or a method, that requires a signature update.
454
- updated_params (List[inspect.Parameter]): A list of `inspect.Parameter` objects representing the updated parameters
464
+ updated_params (List[Parameter]): A list of `Parameter` objects representing the updated parameters
455
465
  for the wrapper function.
456
466
  """
457
467
 
458
- wrapper_signature = inspect.signature(wrapper)
468
+ wrapper_signature = signature(wrapper)
459
469
  wrapper_signature = wrapper_signature.replace(parameters=updated_params)
460
470
  wrapper.__signature__ = wrapper_signature # type: ignore
461
471
 
462
472
  def update_function_signature(
463
473
  self,
464
474
  wrapper: Callable[..., Any],
465
- func_signature: inspect.Signature,
475
+ func_signature: Signature,
466
476
  config_class: Type[BaseModel], # TODO: change to our type
467
477
  config_dict: Dict[str, Any],
468
- ingestible_files: Dict[str, inspect.Parameter],
478
+ ingestible_files: Dict[str, Parameter],
469
479
  ) -> None:
470
480
  """Update the function signature to include new parameters."""
471
481
 
472
- updated_params: List[inspect.Parameter] = []
482
+ updated_params: List[Parameter] = []
473
483
  if config_class:
474
484
  self.add_config_params_to_parser(updated_params, config_class)
475
485
  else:
@@ -480,21 +490,21 @@ class entrypoint(BaseDecorator):
480
490
  def update_deployed_function_signature(
481
491
  self,
482
492
  wrapper: Callable[..., Any],
483
- func_signature: inspect.Signature,
484
- ingestible_files: Dict[str, inspect.Parameter],
493
+ func_signature: Signature,
494
+ ingestible_files: Dict[str, Parameter],
485
495
  ) -> None:
486
496
  """Update the function signature to include new parameters."""
487
497
 
488
- updated_params: List[inspect.Parameter] = []
498
+ updated_params: List[Parameter] = []
489
499
  self.add_func_params_to_parser(updated_params, func_signature, ingestible_files)
490
500
  for param in [
491
501
  "config",
492
502
  "environment",
493
503
  ]: # we add the config and environment parameters
494
504
  updated_params.append(
495
- inspect.Parameter(
505
+ Parameter(
496
506
  name=param,
497
- kind=inspect.Parameter.KEYWORD_ONLY,
507
+ kind=Parameter.KEYWORD_ONLY,
498
508
  default=Body(None),
499
509
  annotation=str,
500
510
  )
@@ -508,9 +518,9 @@ class entrypoint(BaseDecorator):
508
518
  for name, field in config_class.__fields__.items():
509
519
  assert field.default is not None, f"Field {name} has no default value"
510
520
  updated_params.append(
511
- inspect.Parameter(
521
+ Parameter(
512
522
  name=name,
513
- kind=inspect.Parameter.KEYWORD_ONLY,
523
+ kind=Parameter.KEYWORD_ONLY,
514
524
  annotation=field.annotation.__name__,
515
525
  default=Body(field.default),
516
526
  )
@@ -525,9 +535,9 @@ class entrypoint(BaseDecorator):
525
535
  len(param.__class__.__bases__) == 1
526
536
  ), f"Inherited standard type of {param.__class__} needs to be one."
527
537
  updated_params.append(
528
- inspect.Parameter(
538
+ Parameter(
529
539
  name=name,
530
- kind=inspect.Parameter.KEYWORD_ONLY,
540
+ kind=Parameter.KEYWORD_ONLY,
531
541
  default=Body(param),
532
542
  annotation=param.__class__.__bases__[
533
543
  0
@@ -540,23 +550,23 @@ class entrypoint(BaseDecorator):
540
550
  def add_func_params_to_parser(
541
551
  self,
542
552
  updated_params: list,
543
- func_signature: inspect.Signature,
544
- ingestible_files: Dict[str, inspect.Parameter],
553
+ func_signature: Signature,
554
+ ingestible_files: Dict[str, Parameter],
545
555
  ) -> None:
546
556
  """Add function parameters to function signature."""
547
557
  for name, param in func_signature.parameters.items():
548
558
  if name in ingestible_files:
549
559
  updated_params.append(
550
- inspect.Parameter(name, param.kind, annotation=UploadFile)
560
+ Parameter(name, param.kind, annotation=UploadFile)
551
561
  )
552
562
  else:
553
563
  assert (
554
564
  len(param.default.__class__.__bases__) == 1
555
565
  ), f"Inherited standard type of {param.default.__class__} needs to be one."
556
566
  updated_params.append(
557
- inspect.Parameter(
567
+ Parameter(
558
568
  name,
559
- inspect.Parameter.KEYWORD_ONLY,
569
+ Parameter.KEYWORD_ONLY,
560
570
  default=Body(..., embed=True),
561
571
  annotation=param.default.__class__.__bases__[
562
572
  0
@@ -585,7 +595,7 @@ class entrypoint(BaseDecorator):
585
595
  def handle_terminal_run(
586
596
  self,
587
597
  func: Callable,
588
- func_params: Dict[str, inspect.Parameter],
598
+ func_params: Dict[str, Parameter],
589
599
  config_params: Dict[str, Any],
590
600
  ingestible_files: Dict,
591
601
  ):
@@ -599,7 +609,7 @@ class entrypoint(BaseDecorator):
599
609
  """
600
610
 
601
611
  # For required parameters, we add them as arguments
602
- parser = argparse.ArgumentParser()
612
+ parser = ArgumentParser()
603
613
  for name, param in func_params.items():
604
614
  if name in ingestible_files:
605
615
  parser.add_argument(name, type=str)
@@ -644,28 +654,46 @@ class entrypoint(BaseDecorator):
644
654
  }
645
655
  )
646
656
 
647
- # Set the configuration and environment of the LLM app parent span at run-time
648
- ag.tracing.update_baggage({"config": args_config_params, "environment": "bash"})
657
+ loop = get_event_loop()
649
658
 
650
- loop = asyncio.get_event_loop()
651
-
652
- result = loop.run_until_complete(
653
- self.execute_function(
654
- func,
655
- True, # inline trace: True
656
- **{"params": args_func_params, "config_params": args_config_params},
659
+ with routing_context_manager(
660
+ config=args_config_params,
661
+ environment="terminal",
662
+ ):
663
+ result = loop.run_until_complete(
664
+ self.execute_function(
665
+ func,
666
+ True, # inline trace: True
667
+ **{"params": args_func_params, "config_params": args_config_params},
668
+ )
657
669
  )
658
- )
659
670
 
660
- print("\n========== Result ==========\n")
671
+ SHOW_DETAILS = True
672
+ SHOW_DATA = False
673
+ SHOW_TRACE = False
674
+
675
+ if result.trace:
676
+ log.info("\n========= Result =========\n")
677
+
678
+ log.info(f"trace_id: {result.trace['trace_id']}")
679
+ if SHOW_DETAILS:
680
+ log.info(f"latency: {result.trace.get('latency')}")
681
+ log.info(f"cost: {result.trace.get('cost')}")
682
+ log.info(f"usage: {list(result.trace.get('usage', {}).values())}")
661
683
 
662
- print("-> data")
663
- print(json.dumps(result.data, indent=2))
664
- print("-> trace")
665
- print(json.dumps(result.trace, indent=2))
684
+ if SHOW_DATA:
685
+ log.info(" ")
686
+ log.info(f"data:")
687
+ log.info(dumps(result.data, indent=2))
666
688
 
667
- with open("trace.json", "w") as trace_file:
668
- json.dump(result.trace, trace_file, indent=4)
689
+ if SHOW_TRACE:
690
+ log.info(" ")
691
+ log.info(f"trace:")
692
+ log.info(f"----------------")
693
+ log.info(dumps(result.trace.get("spans", []), indent=2))
694
+ log.info(f"----------------")
695
+
696
+ log.info("\n==========================\n")
669
697
 
670
698
  def override_config_in_schema(
671
699
  self,
@@ -872,10 +900,7 @@ class entrypoint(BaseDecorator):
872
900
  subschema["maximum"] = param_val.maxval # type: ignore
873
901
  subschema["default"] = param_val
874
902
 
875
- elif (
876
- isinstance(param_val, inspect.Parameter)
877
- and param_val.annotation is DictInput
878
- ):
903
+ elif isinstance(param_val, Parameter) and param_val.annotation is DictInput:
879
904
  subschema = find_in_schema(
880
905
  param_val.annotation.__schema_type_properties__(),
881
906
  schema_to_override,
@@ -894,7 +919,7 @@ class entrypoint(BaseDecorator):
894
919
  subschema["default"] = param_val
895
920
 
896
921
  elif (
897
- isinstance(param_val, inspect.Parameter)
922
+ isinstance(param_val, Parameter)
898
923
  and param_val.annotation is MessagesInput
899
924
  ):
900
925
  subschema = find_in_schema(
@@ -906,7 +931,7 @@ class entrypoint(BaseDecorator):
906
931
  subschema["default"] = param_val.default
907
932
 
908
933
  elif (
909
- isinstance(param_val, inspect.Parameter)
934
+ isinstance(param_val, Parameter)
910
935
  and param_val.annotation is FileInputURL
911
936
  ):
912
937
  subschema = find_in_schema(
@@ -930,6 +955,6 @@ class entrypoint(BaseDecorator):
930
955
  "title": str(param_name).capitalize(),
931
956
  "type": get_type_from_param(param_val),
932
957
  }
933
- if param_val.default != inspect._empty:
958
+ if param_val.default != _empty:
934
959
  subschema["default"] = param_val.default # type: ignore
935
960
  schema_to_override[param_name] = subschema