agenta 0.25.2__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.
- agenta/__init__.py +6 -7
- agenta/client/backend/client.py +22 -14
- agenta/client/backend/core/http_client.py +23 -15
- agenta/client/backend/core/pydantic_utilities.py +2 -2
- agenta/sdk/__init__.py +27 -6
- agenta/sdk/agenta_init.py +73 -26
- agenta/sdk/config_manager.py +2 -2
- agenta/sdk/context/__init__.py +0 -0
- agenta/sdk/context/routing.py +25 -0
- agenta/sdk/context/tracing.py +3 -0
- agenta/sdk/decorators/__init__.py +0 -0
- agenta/sdk/decorators/{llm_entrypoint.py → routing.py} +136 -125
- agenta/sdk/decorators/tracing.py +243 -81
- agenta/sdk/litellm/__init__.py +1 -0
- agenta/sdk/litellm/litellm.py +275 -0
- agenta/sdk/router.py +0 -7
- agenta/sdk/tracing/__init__.py +1 -0
- agenta/sdk/tracing/attributes.py +181 -0
- agenta/sdk/tracing/context.py +21 -0
- agenta/sdk/tracing/conventions.py +43 -0
- agenta/sdk/tracing/exporters.py +53 -0
- agenta/sdk/tracing/inline.py +1230 -0
- agenta/sdk/tracing/processors.py +65 -0
- agenta/sdk/tracing/spans.py +124 -0
- agenta/sdk/tracing/tracing.py +171 -0
- agenta/sdk/types.py +0 -12
- agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
- agenta/sdk/utils/debug.py +5 -5
- agenta/sdk/utils/exceptions.py +18 -0
- agenta/sdk/utils/globals.py +3 -5
- agenta/sdk/{tracing/logger.py → utils/logging.py} +3 -5
- agenta/sdk/utils/singleton.py +13 -0
- {agenta-0.25.2.dist-info → agenta-0.25.3a1.dist-info}/METADATA +4 -1
- {agenta-0.25.2.dist-info → agenta-0.25.3a1.dist-info}/RECORD +36 -26
- agenta/sdk/context.py +0 -41
- agenta/sdk/decorators/base.py +0 -10
- agenta/sdk/tracing/callbacks.py +0 -187
- agenta/sdk/tracing/llm_tracing.py +0 -617
- agenta/sdk/tracing/tasks_manager.py +0 -129
- agenta/sdk/tracing/tracing_context.py +0 -27
- {agenta-0.25.2.dist-info → agenta-0.25.3a1.dist-info}/WHEEL +0 -0
- {agenta-0.25.2.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
|
-
|
|
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.
|
|
25
|
-
from agenta.sdk.
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
373
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
token = tracing_context.set(TracingContext())
|
|
360
|
+
if not isinstance(result, dict):
|
|
361
|
+
data = str(data)
|
|
378
362
|
|
|
379
|
-
|
|
363
|
+
return data
|
|
380
364
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
387
|
-
if WAIT_FOR_SPANS:
|
|
388
|
-
remaining_steps = NOFSTEPS
|
|
378
|
+
tracing_context.set(routing_context.get())
|
|
389
379
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
380
|
+
WAIT_FOR_SPANS = True
|
|
381
|
+
TIMEOUT = 1
|
|
382
|
+
TIMESTEP = 0.1
|
|
383
|
+
FINALSTEP = 0.001
|
|
384
|
+
NOFSTEPS = TIMEOUT / TIMESTEP
|
|
393
385
|
|
|
394
|
-
|
|
386
|
+
data = None
|
|
387
|
+
trace = {}
|
|
395
388
|
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
400
|
-
|
|
396
|
+
data = self.patch_result(result)
|
|
397
|
+
except Exception as e:
|
|
398
|
+
self.handle_exception(e)
|
|
401
399
|
|
|
402
|
-
|
|
403
|
-
|
|
400
|
+
with suppress():
|
|
401
|
+
root_context: Dict[str, Any] = tracing_context.get().get("root")
|
|
404
402
|
|
|
405
|
-
|
|
403
|
+
trace_id = root_context.get("trace_id") if root_context else None
|
|
406
404
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
+
await asyncio.sleep(FINALSTEP)
|
|
427
419
|
|
|
428
|
-
|
|
420
|
+
trace = ag.tracing.get_inline_trace(trace_id)
|
|
421
|
+
else:
|
|
422
|
+
trace = {"trace_id": trace_id}
|
|
429
423
|
|
|
430
|
-
|
|
424
|
+
response = BaseResponse(data=data, trace=trace)
|
|
431
425
|
|
|
432
|
-
|
|
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
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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
|
-
|
|
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
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
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,
|