Skip to content

Commit cfc0266

Browse files
committed
add client overloads for call method to use local files at specified path for agents and prompts
1 parent a991bb1 commit cfc0266

File tree

3 files changed

+59
-5
lines changed

3 files changed

+59
-5
lines changed

src/humanloop/client.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from humanloop.evals.types import Dataset, Evaluator, EvaluatorCheck, File
1414

1515
from humanloop.base_client import AsyncBaseHumanloop, BaseHumanloop
16-
from humanloop.overload import overload_call, overload_log
16+
from humanloop.overload import overload_call, overload_log, overload_call_with_local_files
1717
from humanloop.decorators.flow import flow as flow_decorator_factory
1818
from humanloop.decorators.prompt import prompt_decorator_factory
1919
from humanloop.decorators.tool import tool_decorator_factory as tool_decorator_factory
@@ -99,6 +99,7 @@ def __init__(
9999
httpx_client: typing.Optional[httpx.Client] = None,
100100
opentelemetry_tracer_provider: Optional[TracerProvider] = None,
101101
opentelemetry_tracer: Optional[Tracer] = None,
102+
use_local_files: bool = False,
102103
):
103104
"""
104105
Extends the base client with custom evaluation utilities and
@@ -118,6 +119,7 @@ def __init__(
118119
httpx_client=httpx_client,
119120
)
120121

122+
self.use_local_files = use_local_files
121123
self.sync_client = SyncClient(client=self)
122124
eval_client = ExtendedEvalsClient(client_wrapper=self._client_wrapper)
123125
eval_client.client = self
@@ -128,6 +130,16 @@ def __init__(
128130
# and the @flow decorator providing the trace_id
129131
self.prompts = overload_log(client=self.prompts)
130132
self.prompts = overload_call(client=self.prompts)
133+
self.prompts = overload_call_with_local_files(
134+
client=self.prompts,
135+
use_local_files=self.use_local_files,
136+
file_type="prompt"
137+
)
138+
self.agents = overload_call_with_local_files(
139+
client=self.agents,
140+
use_local_files=self.use_local_files,
141+
file_type="agent"
142+
)
131143
self.flows = overload_log(client=self.flows)
132144
self.tools = overload_log(client=self.tools)
133145

src/humanloop/overload.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import inspect
22
import logging
33
import types
4-
from typing import TypeVar, Union
5-
4+
from typing import TypeVar, Union, Literal
5+
from pathlib import Path
66
from humanloop.context import (
77
get_decorator_context,
88
get_evaluation_context,
@@ -13,6 +13,7 @@
1313
from humanloop.evaluators.client import EvaluatorsClient
1414
from humanloop.flows.client import FlowsClient
1515
from humanloop.prompts.client import PromptsClient
16+
from humanloop.agents.client import AgentsClient
1617
from humanloop.tools.client import ToolsClient
1718
from humanloop.types.create_evaluator_log_response import CreateEvaluatorLogResponse
1819
from humanloop.types.create_flow_log_response import CreateFlowLogResponse
@@ -112,6 +113,7 @@ def _overload_call(self, **kwargs) -> PromptCallResponse:
112113
}
113114

114115
try:
116+
logger.info(f"Calling inner overload")
115117
response = self._call(**kwargs)
116118
except Exception as e:
117119
# Re-raising as HumanloopDecoratorError so the decorators don't catch it
@@ -122,3 +124,43 @@ def _overload_call(self, **kwargs) -> PromptCallResponse:
122124
# Replace the original log method with the overloaded one
123125
client.call = types.MethodType(_overload_call, client) # type: ignore [assignment]
124126
return client
127+
128+
def overload_call_with_local_files(
129+
client: Union[PromptsClient, AgentsClient],
130+
use_local_files: bool,
131+
file_type: Literal["prompt", "agent"]
132+
) -> Union[PromptsClient, AgentsClient]:
133+
"""Overload call to handle local files when use_local_files is True.
134+
135+
Args:
136+
client: The client to overload (PromptsClient or AgentsClient)
137+
use_local_files: Whether to use local files
138+
file_type: Type of file ("prompt" or "agent")
139+
"""
140+
original_call = client._call if hasattr(client, '_call') else client.call
141+
142+
def _overload_call(self, **kwargs) -> PromptCallResponse:
143+
if use_local_files and "path" in kwargs:
144+
try:
145+
# Construct path to local file
146+
local_path = Path("humanloop") / kwargs["path"]
147+
# Add appropriate extension
148+
local_path = local_path.parent / f"{local_path.stem}.{file_type}"
149+
150+
if local_path.exists():
151+
# Read the file content
152+
with open(local_path) as f:
153+
file_content = f.read()
154+
155+
kwargs[file_type] = file_content # "prompt" or "agent"
156+
157+
logger.debug(f"Using local file content from {local_path}")
158+
else:
159+
logger.warning(f"Local file not found: {local_path}, falling back to API")
160+
except Exception as e:
161+
logger.error(f"Error reading local file: {e}, falling back to API")
162+
163+
return original_call(**kwargs)
164+
165+
client.call = types.MethodType(_overload_call, client)
166+
return client

tests/sync/test_sync.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ def cleanup_local_files():
7474
shutil.rmtree(local_dir)
7575

7676

77-
def test_sync_basic(humanloop_client: Humanloop, test_file_structure: List[SyncableFile], cleanup_local_files):
77+
def test_pull_basic(humanloop_client: Humanloop, test_file_structure: List[SyncableFile], cleanup_local_files):
7878
"""Test that humanloop.sync() correctly syncs remote files to local filesystem"""
7979
# Run the sync
80-
successful_files = humanloop_client.sync()
80+
successful_files = humanloop_client.pull()
8181

8282
# Verify each file was synced correctly
8383
for file in test_file_structure:

0 commit comments

Comments
 (0)