Skip to content

Commit c235a32

Browse files
author
Andrei Bratu
committed
Re-added files not mentioned in .fernignore
1 parent ee9cbcb commit c235a32

File tree

8 files changed

+718
-1
lines changed

8 files changed

+718
-1
lines changed

.fernignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ README.md
88

99
# Directories used by SDK decorators
1010

11-
src/humanloop/decorators
11+
src/humanloop/utilities
1212
src/humanloop/otel
1313

1414
# Tests

src/humanloop/otel/processor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ def _wait_for_children(self, span: ReadableSpan):
109109
"""Wait for all children spans to complete before processing the Humanloop span."""
110110
span_id = span.context.span_id
111111
while not all(child["complete"] for child in self._children[span_id]):
112+
# TODO: This assumes that the children spans will complete
113+
# The LLM provider might fail; address in future
112114
logger.debug(
113115
"[HumanloopSpanProcessor] Span %s %s waiting for children to complete: %s",
114116
span_id,

src/humanloop/utilities/__init__.py

Whitespace-only changes.

src/humanloop/utilities/flow.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import logging
2+
from functools import wraps
3+
from typing import Any, Callable, Mapping, Optional, Sequence
4+
5+
from opentelemetry.sdk.trace import Span
6+
from opentelemetry.trace import Tracer
7+
from typing_extensions import Unpack
8+
9+
from humanloop.utilities.helpers import args_to_inputs
10+
from humanloop.eval_utils.types import File
11+
from humanloop.otel.constants import (
12+
HUMANLOOP_FILE_KEY,
13+
HUMANLOOP_FILE_TYPE_KEY,
14+
HUMANLOOP_LOG_KEY,
15+
HUMANLOOP_PATH_KEY,
16+
)
17+
from humanloop.otel.helpers import jsonify_if_not_string, write_to_opentelemetry_span
18+
from humanloop.requests import FlowKernelRequestParams as FlowDict
19+
from humanloop.requests.flow_kernel_request import FlowKernelRequestParams
20+
21+
logger = logging.getLogger("humanloop.sdk")
22+
23+
24+
def flow(
25+
opentelemetry_tracer: Tracer,
26+
path: Optional[str] = None,
27+
**flow_kernel: Unpack[FlowKernelRequestParams], # type: ignore
28+
):
29+
flow_kernel["attributes"] = {k: v for k, v in flow_kernel.get("attributes", {}).items() if v is not None}
30+
31+
def decorator(func: Callable):
32+
@wraps(func)
33+
def wrapper(*args: Sequence[Any], **kwargs: Mapping[str, Any]) -> Any:
34+
span: Span
35+
with opentelemetry_tracer.start_as_current_span("humanloop.flow") as span: # type: ignore
36+
span.set_attribute(HUMANLOOP_PATH_KEY, path if path else func.__name__)
37+
span.set_attribute(HUMANLOOP_FILE_TYPE_KEY, "flow")
38+
39+
if flow_kernel:
40+
write_to_opentelemetry_span(
41+
span=span,
42+
key=f"{HUMANLOOP_FILE_KEY}.flow",
43+
value=flow_kernel, # type: ignore
44+
)
45+
46+
# Call the decorated function
47+
try:
48+
output = func(*args, **kwargs)
49+
output_stringified = jsonify_if_not_string(
50+
func=func,
51+
output=output,
52+
)
53+
error = None
54+
except Exception as e:
55+
logger.error(f"Error calling {func.__name__}: {e}")
56+
output = None
57+
output_stringified = jsonify_if_not_string(
58+
func=func,
59+
output=None,
60+
)
61+
error = str(e)
62+
63+
flow_log = {
64+
"inputs": args_to_inputs(func, args, kwargs),
65+
"output": output_stringified,
66+
"error": error,
67+
}
68+
69+
# Write the Flow Log to the Span on HL_LOG_OT_KEY
70+
if flow_log:
71+
write_to_opentelemetry_span(
72+
span=span,
73+
key=HUMANLOOP_LOG_KEY,
74+
value=flow_log, # type: ignore
75+
)
76+
77+
# Return the output of the decorated function
78+
return output
79+
80+
wrapper.file = File( # type: ignore
81+
path=path if path else func.__name__,
82+
type="flow",
83+
version=FlowDict(**flow_kernel), # type: ignore
84+
callable=wrapper,
85+
)
86+
87+
return wrapper
88+
89+
return decorator

src/humanloop/utilities/helpers.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import inspect
2+
from typing import Any, Callable
3+
4+
5+
def args_to_inputs(func: Callable, args: tuple, kwargs: dict) -> dict[str, Any]:
6+
"""Maps arguments to their corresponding parameter names in the function signature.
7+
8+
For example:
9+
```python
10+
def foo(a, b=2, c=3):
11+
pass
12+
13+
assert args_to_inputs(foo, (1, 2), {}) == {'a': 1, 'b': 2, 'c': 3}
14+
assert args_to_inputs(foo, (1,), {'b': 8}) == {'a': 1, 'b': 8, 'c': 3}
15+
assert args_to_inputs(foo, (1,), {}) == {'a': 1, 'b': 2, 'c': 3}
16+
```
17+
"""
18+
signature = inspect.signature(func)
19+
bound_args = signature.bind(*args, **kwargs)
20+
bound_args.apply_defaults()
21+
return dict(bound_args.arguments)

src/humanloop/utilities/prompt.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import logging
2+
from functools import wraps
3+
from typing import Any, Callable, Mapping, Optional, Sequence
4+
5+
from opentelemetry.sdk.trace import Span
6+
from opentelemetry.trace import Tracer
7+
from typing_extensions import Unpack
8+
9+
from humanloop.utilities.helpers import args_to_inputs
10+
from humanloop.utilities.types import DecoratorPromptKernelRequestParams
11+
from humanloop.eval_utils import File
12+
from humanloop.otel.constants import (
13+
HUMANLOOP_FILE_KEY,
14+
HUMANLOOP_FILE_TYPE_KEY,
15+
HUMANLOOP_LOG_KEY,
16+
HUMANLOOP_PATH_KEY,
17+
)
18+
from humanloop.otel.helpers import jsonify_if_not_string, write_to_opentelemetry_span
19+
20+
logger = logging.getLogger("humanloop.sdk")
21+
22+
23+
def prompt(
24+
opentelemetry_tracer: Tracer,
25+
path: Optional[str] = None,
26+
# TODO: Template can be a list of objects?
27+
**prompt_kernel: Unpack[DecoratorPromptKernelRequestParams], # type: ignore
28+
):
29+
def decorator(func: Callable):
30+
@wraps(func)
31+
def wrapper(*args: Sequence[Any], **kwargs: Mapping[str, Any]) -> Any:
32+
span: Span
33+
with opentelemetry_tracer.start_as_current_span("humanloop.prompt") as span: # type: ignore
34+
span.set_attribute(HUMANLOOP_PATH_KEY, path if path else func.__name__)
35+
span.set_attribute(HUMANLOOP_FILE_TYPE_KEY, "prompt")
36+
37+
if prompt_kernel:
38+
write_to_opentelemetry_span(
39+
span=span,
40+
key=f"{HUMANLOOP_FILE_KEY}.prompt",
41+
value={
42+
**prompt_kernel, # type: ignore
43+
"attributes": prompt_kernel.get("attributes") or None, # type: ignore
44+
}, # type: ignore
45+
)
46+
47+
# Call the decorated function
48+
try:
49+
output = func(*args, **kwargs)
50+
output_stringified = jsonify_if_not_string(
51+
func=func,
52+
output=output,
53+
)
54+
error = None
55+
except Exception as e:
56+
logger.error(f"Error calling {func.__name__}: {e}")
57+
output = None
58+
output_stringified = jsonify_if_not_string(
59+
func=func,
60+
output=output,
61+
)
62+
error = str(e)
63+
64+
prompt_log = {
65+
"inputs": args_to_inputs(func, args, kwargs),
66+
"output": output_stringified,
67+
"error": error,
68+
}
69+
70+
write_to_opentelemetry_span(
71+
span=span,
72+
key=HUMANLOOP_LOG_KEY,
73+
value=prompt_log, # type: ignore
74+
)
75+
76+
# Return the output of the decorated function
77+
return output
78+
79+
wrapper.file = File( # type: ignore
80+
path=path if path else func.__name__,
81+
type="prompt",
82+
version={**prompt_kernel}, # type: ignore
83+
callable=wrapper,
84+
)
85+
86+
return wrapper
87+
88+
return decorator

0 commit comments

Comments
 (0)