agenta 0.26.0a0__py3-none-any.whl → 0.27.0a0__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 (41) hide show
  1. agenta/__init__.py +6 -7
  2. agenta/client/backend/client.py +22 -14
  3. agenta/client/backend/core/http_client.py +23 -15
  4. agenta/sdk/__init__.py +27 -6
  5. agenta/sdk/agenta_init.py +73 -26
  6. agenta/sdk/config_manager.py +2 -2
  7. agenta/sdk/context/__init__.py +0 -0
  8. agenta/sdk/context/routing.py +25 -0
  9. agenta/sdk/context/tracing.py +3 -0
  10. agenta/sdk/decorators/__init__.py +0 -0
  11. agenta/sdk/decorators/{llm_entrypoint.py → routing.py} +137 -124
  12. agenta/sdk/decorators/tracing.py +228 -76
  13. agenta/sdk/litellm/__init__.py +1 -0
  14. agenta/sdk/litellm/litellm.py +277 -0
  15. agenta/sdk/router.py +0 -7
  16. agenta/sdk/tracing/__init__.py +1 -0
  17. agenta/sdk/tracing/attributes.py +181 -0
  18. agenta/sdk/tracing/context.py +21 -0
  19. agenta/sdk/tracing/conventions.py +43 -0
  20. agenta/sdk/tracing/exporters.py +53 -0
  21. agenta/sdk/tracing/inline.py +1306 -0
  22. agenta/sdk/tracing/processors.py +65 -0
  23. agenta/sdk/tracing/spans.py +124 -0
  24. agenta/sdk/tracing/tracing.py +174 -0
  25. agenta/sdk/types.py +0 -12
  26. agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
  27. agenta/sdk/utils/debug.py +5 -5
  28. agenta/sdk/utils/exceptions.py +19 -0
  29. agenta/sdk/utils/globals.py +3 -5
  30. agenta/sdk/{tracing/logger.py → utils/logging.py} +3 -5
  31. agenta/sdk/utils/singleton.py +13 -0
  32. {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/METADATA +5 -1
  33. {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/RECORD +35 -25
  34. agenta/sdk/context.py +0 -41
  35. agenta/sdk/decorators/base.py +0 -10
  36. agenta/sdk/tracing/callbacks.py +0 -187
  37. agenta/sdk/tracing/llm_tracing.py +0 -617
  38. agenta/sdk/tracing/tasks_manager.py +0 -129
  39. agenta/sdk/tracing/tracing_context.py +0 -27
  40. {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/WHEEL +0 -0
  41. {agenta-0.26.0a0.dist-info → agenta-0.27.0a0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,5 @@
1
1
  """The code for the Agenta SDK"""
2
2
 
3
- from agenta.sdk.utils.debug import debug, DEBUG, SHIFT
4
3
  import os
5
4
  import sys
6
5
  import time
@@ -14,18 +13,17 @@ from pathlib import Path
14
13
  from tempfile import NamedTemporaryFile
15
14
  from typing import Any, Callable, Dict, Optional, Tuple, List
16
15
  from importlib.metadata import version
17
-
18
16
  from fastapi.middleware.cors import CORSMiddleware
19
17
  from fastapi import Body, FastAPI, UploadFile, HTTPException
20
18
 
21
19
  import agenta as ag
22
- from agenta.sdk.context import save_context
20
+
21
+ from agenta.sdk.context.routing import routing_context_manager, routing_context
22
+ from agenta.sdk.context.tracing import tracing_context
23
23
  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
24
+ from agenta.sdk.utils.exceptions import suppress
25
+ from agenta.sdk.utils.logging import log
27
26
  from agenta.sdk.types import (
28
- Context,
29
27
  DictInput,
30
28
  FloatParam,
31
29
  InFile,
@@ -67,36 +65,14 @@ app.add_middleware(
67
65
  app.include_router(router, prefix="")
68
66
 
69
67
 
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)
68
+ log.setLevel("DEBUG")
93
69
 
94
70
 
95
71
  class PathValidator(BaseModel):
96
72
  url: HttpUrl
97
73
 
98
74
 
99
- class route(BaseDecorator):
75
+ class route:
100
76
  # This decorator is used to expose specific stages of a workflow (embedding, retrieval, summarization, etc.)
101
77
  # as independent endpoints. It is designed for backward compatibility with existing code that uses
102
78
  # the @entrypoint decorator, which has certain limitations. By using @route(), we can create new
@@ -118,7 +94,7 @@ class route(BaseDecorator):
118
94
  return f
119
95
 
120
96
 
121
- class entrypoint(BaseDecorator):
97
+ class entrypoint:
122
98
  """
123
99
  Decorator class to wrap a function for HTTP POST, terminal exposure and enable tracing.
124
100
 
@@ -152,10 +128,11 @@ class entrypoint(BaseDecorator):
152
128
  routes = list()
153
129
 
154
130
  def __init__(
155
- self, func: Callable[..., Any], route_path="", config_schema: BaseModel = None
131
+ self,
132
+ func: Callable[..., Any],
133
+ route_path="",
134
+ config_schema: Optional[BaseModel] = None,
156
135
  ):
157
- logging.info(f"Using Agenta Python SDK version {version('agenta')}")
158
-
159
136
  DEFAULT_PATH = "generate"
160
137
  PLAYGROUND_PATH = "/playground"
161
138
  RUN_PATH = "/run"
@@ -176,8 +153,9 @@ class entrypoint(BaseDecorator):
176
153
  config_params = config.dict() if config else ag.config.all()
177
154
  ingestible_files = self.extract_ingestible_files(func_signature)
178
155
 
156
+ self.route_path = route_path
157
+
179
158
  ### --- Playground --- #
180
- @debug()
181
159
  @functools.wraps(func)
182
160
  async def wrapper(*args, **kwargs) -> Any:
183
161
  func_params, api_config_params = self.split_kwargs(kwargs, config_params)
@@ -185,11 +163,10 @@ class entrypoint(BaseDecorator):
185
163
  if not config_schema:
186
164
  ag.config.set(**api_config_params)
187
165
 
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):
166
+ with routing_context_manager(
167
+ config=api_config_params,
168
+ environment="playground",
169
+ ):
193
170
  entrypoint_result = await self.execute_function(
194
171
  func,
195
172
  True, # inline trace: True
@@ -242,7 +219,6 @@ class entrypoint(BaseDecorator):
242
219
  ### ---------------------------- #
243
220
 
244
221
  ### --- Deployed / Published --- #
245
- @debug()
246
222
  @functools.wraps(func)
247
223
  async def wrapper_deployed(*args, **kwargs) -> Any:
248
224
  func_params = {
@@ -256,12 +232,10 @@ class entrypoint(BaseDecorator):
256
232
  else:
257
233
  ag.config.pull(config_name="default")
258
234
 
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"]
235
+ with routing_context_manager(
236
+ config=config_params,
237
+ variant=kwargs["config"],
238
+ environment=kwargs["environment"],
265
239
  ):
266
240
  entrypoint_result = await self.execute_function(
267
241
  func,
@@ -351,86 +325,108 @@ class entrypoint(BaseDecorator):
351
325
  if name in func_params and func_params[name] is not None:
352
326
  func_params[name] = self.ingest_file(func_params[name])
353
327
 
354
- async def execute_function(
355
- self, func: Callable[..., Any], inline_trace, *args, **func_params
356
- ):
357
- """Execute the function and handle any exceptions."""
328
+ def patch_result(self, result: Any):
329
+ """
330
+ Patch the result to only include the message if the result is a FuncResponse-style dictionary with message, cost, and usage keys.
358
331
 
359
- 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')}")
332
+ Example:
333
+ ```python
334
+ result = {
335
+ "message": "Hello, world!",
336
+ "cost": 0.5,
337
+ "usage": {
338
+ "prompt_tokens": 10,
339
+ "completion_tokens": 20,
340
+ "total_tokens": 30
341
+ }
342
+ }
343
+ result = patch_result(result)
344
+ print(result)
345
+ # Output: "Hello, world!"
346
+ ```
347
+ """
348
+ data = (
349
+ result["message"]
350
+ if isinstance(result, dict)
351
+ and all(key in result for key in ["message", "cost", "usage"])
352
+ else result
353
+ )
366
354
 
367
- WAIT_FOR_SPANS = True
368
- TIMEOUT = 1
369
- TIMESTEP = 0.1
370
- NOFSTEPS = TIMEOUT / TIMESTEP
355
+ if data is None:
356
+ data = (
357
+ "Function executed successfully, but did return None. \n Are you sure you did not forget to return a value?",
358
+ )
371
359
 
372
- data = None
373
- trace = None
360
+ if not isinstance(result, dict):
361
+ data = str(data)
374
362
 
375
- token = None
376
- if tracing_context.get() is None:
377
- token = tracing_context.set(TracingContext())
363
+ return data
378
364
 
379
- is_coroutine_function = inspect.iscoroutinefunction(func)
365
+ async def execute_function(
366
+ self,
367
+ func: Callable[..., Any],
368
+ inline_trace,
369
+ *args,
370
+ **func_params,
371
+ ):
372
+ log.info(f"\n--------------------------")
373
+ log.info(
374
+ f"Running application route: {repr(self.route_path if self.route_path != '' else '/')}"
375
+ )
376
+ log.info(f"--------------------------\n")
380
377
 
381
- if is_coroutine_function:
382
- result = await func(*args, **func_params["params"])
383
- else:
384
- result = func(*args, **func_params["params"])
378
+ tracing_context.set(routing_context.get())
385
379
 
386
- if token is not None:
387
- if WAIT_FOR_SPANS:
388
- remaining_steps = NOFSTEPS
380
+ WAIT_FOR_SPANS = True
381
+ TIMEOUT = 1
382
+ TIMESTEP = 0.1
383
+ FINALSTEP = 0.001
384
+ NOFSTEPS = TIMEOUT / TIMESTEP
389
385
 
390
- while not ag.tracing.is_trace_ready() and remaining_steps > 0:
391
- await asyncio.sleep(TIMESTEP)
392
- remaining_steps -= 1
386
+ data = None
387
+ trace = {}
393
388
 
394
- trace = ag.tracing.dump_trace()
389
+ try:
390
+ result = (
391
+ await func(*args, **func_params["params"])
392
+ if inspect.iscoroutinefunction(func)
393
+ else func(*args, **func_params["params"])
394
+ )
395
+ data = self.patch_result(result)
396
+ except Exception as e:
397
+ log.error(f"Agenta SDK - Routing Exception")
395
398
 
396
- if not inline_trace:
397
- trace = {"trace_id": trace["trace_id"]}
399
+ traceback.print_exc()
398
400
 
399
- ag.tracing.flush_spans()
400
- tracing_context.reset(token)
401
+ self.handle_exception(e)
401
402
 
402
- if isinstance(result, Context):
403
- save_context(result)
403
+ with suppress():
404
+ root_context: Dict[str, Any] = tracing_context.get().get("root")
404
405
 
405
- data = result
406
+ trace_id = root_context.get("trace_id") if root_context else None
406
407
 
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"])
408
+ if trace_id is not None:
409
+ if inline_trace:
410
+ if WAIT_FOR_SPANS:
411
+ remaining_steps = NOFSTEPS
418
412
 
419
- # END OF PATH
413
+ while (
414
+ not ag.tracing.is_inline_trace_ready(trace_id)
415
+ and remaining_steps > 0
416
+ ):
417
+ await asyncio.sleep(TIMESTEP)
420
418
 
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
- )
419
+ remaining_steps -= 1
425
420
 
426
- response = BaseResponse(data=data, trace=trace)
421
+ await asyncio.sleep(FINALSTEP)
427
422
 
428
- # logging.debug(response)
423
+ trace = ag.tracing.get_inline_trace(trace_id)
424
+ else:
425
+ trace = {"trace_id": trace_id}
429
426
 
430
- return response
427
+ response = BaseResponse(data=data, trace=trace)
431
428
 
432
- except Exception as e:
433
- self.handle_exception(e)
429
+ return response
434
430
 
435
431
  def handle_exception(self, e: Exception):
436
432
  status_code = e.status_code if hasattr(e, "status_code") else 500
@@ -644,28 +640,45 @@ class entrypoint(BaseDecorator):
644
640
  }
645
641
  )
646
642
 
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"})
649
-
650
643
  loop = asyncio.get_event_loop()
651
644
 
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},
645
+ with routing_context_manager(
646
+ config=args_config_params,
647
+ environment="terminal",
648
+ ):
649
+ result = loop.run_until_complete(
650
+ self.execute_function(
651
+ func,
652
+ True, # inline trace: True
653
+ **{"params": args_func_params, "config_params": args_config_params},
654
+ )
657
655
  )
658
- )
659
656
 
660
- print("\n========== Result ==========\n")
657
+ SHOW_DETAILS = True
658
+ SHOW_DATA = False
659
+ SHOW_TRACE = False
660
+
661
+ log.info("\n========= Result =========\n")
662
+
663
+ log.info(f"trace_id: {result.trace['trace_id']}")
664
+ if SHOW_DETAILS:
665
+ log.info(f"latency: {result.trace.get('latency')}")
666
+ log.info(f"cost: {result.trace.get('cost')}")
667
+ log.info(f"usage: {list(result.trace.get('usage', {}).values())}")
668
+
669
+ if SHOW_DATA:
670
+ log.info(" ")
671
+ log.info(f"data:")
672
+ log.info(json.dumps(result.data, indent=2))
661
673
 
662
- print("-> data")
663
- print(json.dumps(result.data, indent=2))
664
- print("-> trace")
665
- print(json.dumps(result.trace, indent=2))
674
+ if SHOW_TRACE:
675
+ log.info(" ")
676
+ log.info(f"trace:")
677
+ log.info(f"----------------")
678
+ log.info(json.dumps(result.trace.get("spans", []), indent=2))
679
+ log.info(f"----------------")
666
680
 
667
- with open("trace.json", "w") as trace_file:
668
- json.dump(result.trace, trace_file, indent=4)
681
+ log.info("\n==========================\n")
669
682
 
670
683
  def override_config_in_schema(
671
684
  self,