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