agenta 0.25.3__py3-none-any.whl → 0.25.3a1__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 +6 -7
  2. agenta/client/backend/client.py +22 -14
  3. agenta/client/backend/core/http_client.py +23 -15
  4. agenta/client/backend/core/pydantic_utilities.py +2 -2
  5. agenta/sdk/__init__.py +27 -6
  6. agenta/sdk/agenta_init.py +73 -26
  7. agenta/sdk/config_manager.py +2 -2
  8. agenta/sdk/context/__init__.py +0 -0
  9. agenta/sdk/context/routing.py +25 -0
  10. agenta/sdk/context/tracing.py +3 -0
  11. agenta/sdk/decorators/__init__.py +0 -0
  12. agenta/sdk/decorators/{llm_entrypoint.py → routing.py} +136 -125
  13. agenta/sdk/decorators/tracing.py +243 -81
  14. agenta/sdk/litellm/__init__.py +1 -0
  15. agenta/sdk/litellm/litellm.py +275 -0
  16. agenta/sdk/router.py +0 -7
  17. agenta/sdk/tracing/__init__.py +1 -0
  18. agenta/sdk/tracing/attributes.py +181 -0
  19. agenta/sdk/tracing/context.py +21 -0
  20. agenta/sdk/tracing/conventions.py +43 -0
  21. agenta/sdk/tracing/exporters.py +53 -0
  22. agenta/sdk/tracing/inline.py +1230 -0
  23. agenta/sdk/tracing/processors.py +65 -0
  24. agenta/sdk/tracing/spans.py +124 -0
  25. agenta/sdk/tracing/tracing.py +171 -0
  26. agenta/sdk/types.py +0 -12
  27. agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
  28. agenta/sdk/utils/debug.py +5 -5
  29. agenta/sdk/utils/exceptions.py +18 -0
  30. agenta/sdk/utils/globals.py +3 -5
  31. agenta/sdk/{tracing/logger.py → utils/logging.py} +3 -5
  32. agenta/sdk/utils/singleton.py +13 -0
  33. {agenta-0.25.3.dist-info → agenta-0.25.3a1.dist-info}/METADATA +4 -1
  34. {agenta-0.25.3.dist-info → agenta-0.25.3a1.dist-info}/RECORD +36 -26
  35. agenta/sdk/context.py +0 -41
  36. agenta/sdk/decorators/base.py +0 -10
  37. agenta/sdk/tracing/callbacks.py +0 -187
  38. agenta/sdk/tracing/llm_tracing.py +0 -617
  39. agenta/sdk/tracing/tasks_manager.py +0 -129
  40. agenta/sdk/tracing/tracing_context.py +0 -27
  41. {agenta-0.25.3.dist-info → agenta-0.25.3a1.dist-info}/WHEEL +0 -0
  42. {agenta-0.25.3.dist-info → agenta-0.25.3a1.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,105 @@ 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."""
358
-
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')}")
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.
366
331
 
367
- WAIT_FOR_SPANS = True
368
- TIMEOUT = 1
369
- TIMESTEP = 0.1
370
- NOFSTEPS = TIMEOUT / TIMESTEP
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
+ )
371
354
 
372
- data = None
373
- trace = None
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
+ )
374
359
 
375
- token = None
376
- if tracing_context.get() is None:
377
- token = tracing_context.set(TracingContext())
360
+ if not isinstance(result, dict):
361
+ data = str(data)
378
362
 
379
- is_coroutine_function = inspect.iscoroutinefunction(func)
363
+ return data
380
364
 
381
- if is_coroutine_function:
382
- result = await func(*args, **func_params["params"])
383
- else:
384
- result = func(*args, **func_params["params"])
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")
385
377
 
386
- if token is not None:
387
- if WAIT_FOR_SPANS:
388
- remaining_steps = NOFSTEPS
378
+ tracing_context.set(routing_context.get())
389
379
 
390
- while not ag.tracing.is_trace_ready() and remaining_steps > 0:
391
- await asyncio.sleep(TIMESTEP)
392
- remaining_steps -= 1
380
+ WAIT_FOR_SPANS = True
381
+ TIMEOUT = 1
382
+ TIMESTEP = 0.1
383
+ FINALSTEP = 0.001
384
+ NOFSTEPS = TIMEOUT / TIMESTEP
393
385
 
394
- trace = ag.tracing.dump_trace()
386
+ data = None
387
+ trace = {}
395
388
 
396
- if not inline_trace:
397
- trace = {"trace_id": trace["trace_id"]}
389
+ try:
390
+ result = (
391
+ await func(*args, **func_params["params"])
392
+ if inspect.iscoroutinefunction(func)
393
+ else func(*args, **func_params["params"])
394
+ )
398
395
 
399
- ag.tracing.flush_spans()
400
- tracing_context.reset(token)
396
+ data = self.patch_result(result)
397
+ except Exception as e:
398
+ self.handle_exception(e)
401
399
 
402
- if isinstance(result, Context):
403
- save_context(result)
400
+ with suppress():
401
+ root_context: Dict[str, Any] = tracing_context.get().get("root")
404
402
 
405
- data = result
403
+ trace_id = root_context.get("trace_id") if root_context else None
406
404
 
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"])
405
+ if trace_id is not None:
406
+ if inline_trace:
407
+ if WAIT_FOR_SPANS:
408
+ remaining_steps = NOFSTEPS
418
409
 
419
- # END OF PATH
410
+ while (
411
+ not ag.tracing.is_inline_trace_ready(trace_id)
412
+ and remaining_steps > 0
413
+ ):
414
+ await asyncio.sleep(TIMESTEP)
420
415
 
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
- )
416
+ remaining_steps -= 1
425
417
 
426
- response = BaseResponse(data=data, trace=trace)
418
+ await asyncio.sleep(FINALSTEP)
427
419
 
428
- # logging.debug(response)
420
+ trace = ag.tracing.get_inline_trace(trace_id)
421
+ else:
422
+ trace = {"trace_id": trace_id}
429
423
 
430
- return response
424
+ response = BaseResponse(data=data, trace=trace)
431
425
 
432
- except Exception as e:
433
- self.handle_exception(e)
426
+ return response
434
427
 
435
428
  def handle_exception(self, e: Exception):
436
429
  status_code = e.status_code if hasattr(e, "status_code") else 500
@@ -644,28 +637,46 @@ class entrypoint(BaseDecorator):
644
637
  }
645
638
  )
646
639
 
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
640
  loop = asyncio.get_event_loop()
651
641
 
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},
642
+ with routing_context_manager(
643
+ config=args_config_params,
644
+ environment="terminal",
645
+ ):
646
+ result = loop.run_until_complete(
647
+ self.execute_function(
648
+ func,
649
+ True, # inline trace: True
650
+ **{"params": args_func_params, "config_params": args_config_params},
651
+ )
657
652
  )
658
- )
659
653
 
660
- print("\n========== Result ==========\n")
654
+ SHOW_DETAILS = True
655
+ SHOW_DATA = False
656
+ SHOW_SPANS = True
657
+ SHOW_SPAN_ATTRIBUTES = False
658
+
659
+ log.info("\n========= Result =========\n")
660
+
661
+ log.info(f"trace_id: {result.trace['trace_id']}")
662
+ if SHOW_DETAILS:
663
+ log.info(f"latency: {result.trace.get('latency')}")
664
+ log.info(f"cost: {result.trace.get('cost')}")
665
+ log.info(f"tokens: {list(result.trace.get('tokens', {}).values())}")
666
+
667
+ if SHOW_DATA:
668
+ log.info(" ")
669
+ log.info(f"data:")
670
+ log.info(json.dumps(result.data, indent=2))
661
671
 
662
- print("-> data")
663
- print(json.dumps(result.data, indent=2))
664
- print("-> trace")
665
- print(json.dumps(result.trace, indent=2))
672
+ if SHOW_SPANS:
673
+ log.info(" ")
674
+ log.info(f"trace:")
675
+ log.info(f"----------------")
676
+ log.info(json.dumps(result.trace.get("spans", []), indent=2))
677
+ log.info(f"----------------")
666
678
 
667
- with open("trace.json", "w") as trace_file:
668
- json.dump(result.trace, trace_file, indent=4)
679
+ log.info("\n==========================\n")
669
680
 
670
681
  def override_config_in_schema(
671
682
  self,