|
1 | 1 | from decimal import Decimal |
| 2 | +import functools |
| 3 | +import inspect |
2 | 4 | import uuid |
3 | 5 | import warnings |
4 | 6 | from datetime import datetime, timedelta, timezone |
5 | 7 | from enum import Enum |
6 | 8 |
|
7 | 9 | import sentry_sdk |
8 | | -from sentry_sdk.consts import INSTRUMENTER, SPANSTATUS, SPANDATA |
| 10 | +from sentry_sdk.consts import INSTRUMENTER, OP, SPANSTATUS, SPANDATA |
9 | 11 | from sentry_sdk.profiler.continuous_profiler import get_profiler_id |
10 | 12 | from sentry_sdk.utils import ( |
11 | 13 | get_current_thread_meta, |
@@ -1371,6 +1373,135 @@ async def my_async_function(): |
1371 | 1373 | return start_child_span_decorator |
1372 | 1374 |
|
1373 | 1375 |
|
| 1376 | +def set_span_attributes(span, attributes): |
| 1377 | + # type: (Span, dict[str, Any]) -> None |
| 1378 | + for key, value in attributes.items(): |
| 1379 | + span.set_data(key, value) |
| 1380 | + |
| 1381 | + |
| 1382 | +def set_input_attributes(span, template, args, kwargs): |
| 1383 | + # depending on `template` set some attributes on the span derived from args and kwargs |
| 1384 | + pass |
| 1385 | + |
| 1386 | + |
| 1387 | +def set_output_attributes(span, template, result): |
| 1388 | + # depending on `template` set some attributes on the span derived from result |
| 1389 | + pass |
| 1390 | + |
| 1391 | + |
| 1392 | +def new_trace(func=None, *, as_type="span", name=None): |
| 1393 | + """ |
| 1394 | + Decorator to start a child span. |
| 1395 | +
|
| 1396 | + :param func: The function to trace. |
| 1397 | + :param as_type: The type of span to create. |
| 1398 | + :param name: The name of the span. |
| 1399 | + """ |
| 1400 | + |
| 1401 | + def span_decorator(f, *a, **kw): |
| 1402 | + @functools.wraps(f) |
| 1403 | + async def async_wrapper(*args, **kwargs): |
| 1404 | + # type: (*Any, **Any) -> Any |
| 1405 | + op = kw.get("op", OP.FUNCTION) |
| 1406 | + span_name = kw.get("name", f.__name__) |
| 1407 | + attributes = kw.get("attributes", {}) |
| 1408 | + |
| 1409 | + with sentry_sdk.start_span( |
| 1410 | + op=op, |
| 1411 | + name=span_name, |
| 1412 | + ) as span: |
| 1413 | + set_span_attributes(span, attributes) |
| 1414 | + set_input_attributes(span, as_type, args, kwargs) |
| 1415 | + |
| 1416 | + result = await f(*args, **kwargs) |
| 1417 | + |
| 1418 | + set_output_attributes(span, as_type, result) |
| 1419 | + |
| 1420 | + return result |
| 1421 | + |
| 1422 | + @functools.wraps(f) |
| 1423 | + def sync_wrapper(*args, **kwargs): |
| 1424 | + # type: (*Any, **Any) -> Any |
| 1425 | + op = kw.get("op", OP.FUNCTION) |
| 1426 | + span_name = kw.get("name", f.__name__) |
| 1427 | + attributes = kw.get("attributes", {}) |
| 1428 | + |
| 1429 | + with sentry_sdk.start_span( |
| 1430 | + op=op, |
| 1431 | + name=span_name, |
| 1432 | + ) as span: |
| 1433 | + set_span_attributes(span, attributes) |
| 1434 | + set_input_attributes(span, as_type, args, kwargs) |
| 1435 | + |
| 1436 | + result = f(*args, **kwargs) |
| 1437 | + |
| 1438 | + set_output_attributes(span, as_type, result) |
| 1439 | + |
| 1440 | + return result |
| 1441 | + |
| 1442 | + if inspect.iscoroutinefunction(f): |
| 1443 | + wrapper = async_wrapper |
| 1444 | + else: |
| 1445 | + wrapper = sync_wrapper |
| 1446 | + |
| 1447 | + return wrapper |
| 1448 | + |
| 1449 | + def ai_chat_decorator(f): |
| 1450 | + # type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]] |
| 1451 | + attributes = { |
| 1452 | + "gen_ai.operation.name": "chat", |
| 1453 | + } |
| 1454 | + |
| 1455 | + return span_decorator( |
| 1456 | + f, |
| 1457 | + op=OP.GEN_AI_CHAT, |
| 1458 | + name="chat [MODEL]", |
| 1459 | + attributes=attributes, |
| 1460 | + ) |
| 1461 | + |
| 1462 | + def ai_agent_decorator(f): |
| 1463 | + # type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]] |
| 1464 | + span_name = name or f.__name__ |
| 1465 | + attributes = { |
| 1466 | + "gen_ai.agent.name": span_name, |
| 1467 | + "gen_ai.operation.name": "invoke_agent", |
| 1468 | + } |
| 1469 | + |
| 1470 | + return span_decorator( |
| 1471 | + f, |
| 1472 | + op=OP.GEN_AI_INVOKE_AGENT, |
| 1473 | + name=f"invoke_agent {span_name}", |
| 1474 | + attributes=attributes, |
| 1475 | + ) |
| 1476 | + |
| 1477 | + def ai_tool_decorator(f): |
| 1478 | + # type: (Optional[Callable[P, R]]) -> Union[Callable[P, R], Callable[[Callable[P, R]], Callable[P, R]]] |
| 1479 | + span_name = name or f.__name__ |
| 1480 | + attributes = { |
| 1481 | + "gen_ai.tool.name": span_name, |
| 1482 | + "gen_ai.operation.name": "execute_tool", |
| 1483 | + } |
| 1484 | + |
| 1485 | + return span_decorator( |
| 1486 | + f, |
| 1487 | + op=OP.GEN_AI_EXECUTE_TOOL, |
| 1488 | + name=f"execute_tool {span_name}", |
| 1489 | + attributes=attributes, |
| 1490 | + ) |
| 1491 | + |
| 1492 | + decorator_for_type = { |
| 1493 | + "ai_chat": ai_chat_decorator, |
| 1494 | + "ai_agent": ai_agent_decorator, |
| 1495 | + "ai_tool": ai_tool_decorator, |
| 1496 | + } |
| 1497 | + decorator = decorator_for_type.get(as_type, span_decorator) |
| 1498 | + |
| 1499 | + if func: |
| 1500 | + return decorator(func) |
| 1501 | + else: |
| 1502 | + return decorator |
| 1503 | + |
| 1504 | + |
1374 | 1505 | # Circular imports |
1375 | 1506 |
|
1376 | 1507 | from sentry_sdk.tracing_utils import ( |
|
0 commit comments