Skip to content

Commit 550fb44

Browse files
committed
Improve overloading and warnings; content -> raw file content
1 parent 0414fe6 commit 550fb44

File tree

2 files changed

+50
-29
lines changed

2 files changed

+50
-29
lines changed

src/humanloop/overload.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
import types
44
import warnings
5-
from typing import TypeVar, Union, Literal, Optional
5+
from typing import TypeVar, Union
66
from pathlib import Path
77
from humanloop.context import (
88
get_decorator_context,
@@ -143,10 +143,25 @@ def overload_with_local_files(
143143
) -> Union[PromptsClient, AgentsClient]:
144144
"""Overload call and log methods to handle local files when use_local_files is True.
145145
146-
When use_local_files is True:
147-
- If only path is specified (no version_id or environment), attempts to use local file
148-
- If local file is not found or cannot be read, raises an error
149-
- If version_id and/or environment is specified, uses remote version with a warning
146+
When use_local_files is True, the following prioritization strategy is used:
147+
1. Direct Parameters: If {file_type} parameters are provided directly (as a PromptKernelRequestParams or AgentKernelRequestParams object),
148+
these take precedence and the local file is ignored.
149+
2. Version/Environment: If version_id or environment is specified, the remote version is used instead
150+
of the local file.
151+
3. Local File: If neither of the above are specified, attempts to use the local file at the given path.
152+
153+
For example, with a prompt client:
154+
- If prompt={model: "gpt-4", ...} is provided, uses those parameters directly
155+
- If version_id="123" is provided, uses that remote version
156+
- Otherwise, tries to load from the local file at the given path
157+
158+
Args:
159+
client: The client to overload (PromptsClient or AgentsClient)
160+
sync_client: The sync client used for file operations
161+
use_local_files: Whether to enable local file handling
162+
163+
Returns:
164+
The client with overloaded methods
150165
151166
Raises:
152167
HumanloopRuntimeError: If use_local_files is True and local file cannot be accessed
@@ -156,28 +171,34 @@ def overload_with_local_files(
156171
file_type = _get_file_type_from_client(client)
157172

158173
def _overload(self, function_name: str, **kwargs) -> PromptCallResponse:
159-
if "id" and "path" in kwargs:
174+
if "id" in kwargs and "path" in kwargs:
160175
raise HumanloopRuntimeError(f"Can only specify one of `id` or `path` when {function_name}ing a {file_type}")
161176
# Handle local files if enabled
162177
if use_local_files and "path" in kwargs:
163178
# Check if version_id or environment is specified
164179
has_version_info = "version_id" in kwargs or "environment" in kwargs
180+
normalized_path = sync_client._normalize_path(kwargs["path"])
165181

166182
if has_version_info:
167-
warnings.warn(
168-
f"Ignoring local file for {kwargs['path']} as version_id or environment was specified. "
169-
"Using remote version instead.",
170-
UserWarning
183+
logger.warning(
184+
f"Ignoring local file for `{normalized_path}` as version_id or environment was specified. "
185+
"Using remote version instead."
171186
)
172187
else:
173188
# Only use local file if no version info is specified
174-
normalized_path = sync_client._normalize_path(kwargs["path"])
175189
try:
176-
file_content = sync_client.get_file_content(normalized_path, file_type)
177-
kwargs[file_type] = file_content
190+
# If file_type is already specified in kwargs, it means user provided a PromptKernelRequestParams object
191+
if file_type in kwargs and not isinstance(kwargs[file_type], str):
192+
logger.warning(
193+
f"Ignoring local file for `{normalized_path}` as {file_type} parameters were directly provided. "
194+
"Using provided parameters instead."
195+
)
196+
else:
197+
file_content = sync_client.get_file_content(normalized_path, file_type)
198+
kwargs[file_type] = file_content
178199
except (HumanloopRuntimeError) as e:
179200
# Re-raise with more context
180-
raise HumanloopRuntimeError(f"Failed to use local file for {kwargs['path']}: {str(e)}")
201+
raise HumanloopRuntimeError(f"Failed to use local file for `{normalized_path}`: {str(e)}")
181202

182203
try:
183204
if function_name == "call":

src/humanloop/sync/sync_client.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _get_file_content_impl(self, path: str, file_type: FileType) -> str:
6565
file_type: The type of file (Prompt or Agent)
6666
6767
Returns:
68-
The file content
68+
The raw file content
6969
7070
Raises:
7171
HumanloopRuntimeError: If the file doesn't exist or can't be read
@@ -79,7 +79,7 @@ def _get_file_content_impl(self, path: str, file_type: FileType) -> str:
7979
raise HumanloopRuntimeError(f"Local file not found: {local_path}")
8080

8181
try:
82-
# Read the file content
82+
# Read the raw file content
8383
with open(local_path) as f:
8484
file_content = f.read()
8585
logger.debug(f"Using local file content from {local_path}")
@@ -88,7 +88,7 @@ def _get_file_content_impl(self, path: str, file_type: FileType) -> str:
8888
raise HumanloopRuntimeError(f"Error reading local file {local_path}: {str(e)}")
8989

9090
def get_file_content(self, path: str, file_type: FileType) -> str:
91-
"""Get the content of a file from cache or filesystem.
91+
"""Get the raw file content of a file from cache or filesystem.
9292
9393
This method uses an LRU cache to store file contents. When the cache is full,
9494
the least recently accessed files are automatically removed to make space.
@@ -98,7 +98,7 @@ def get_file_content(self, path: str, file_type: FileType) -> str:
9898
file_type: The type of file (Prompt or Agent)
9999
100100
Returns:
101-
The file content
101+
The raw file content
102102
103103
Raises:
104104
HumanloopRuntimeError: If the file doesn't exist or can't be read
@@ -153,7 +153,7 @@ def _save_serialized_file(self, serialized_content: str, file_path: str, file_ty
153153
"""Save serialized file to local filesystem.
154154
155155
Args:
156-
serialized_content: The content to save
156+
serialized_content: The raw file content to save
157157
file_path: The path to save the file to
158158
file_type: The type of file (Prompt or Agent)
159159
@@ -169,7 +169,7 @@ def _save_serialized_file(self, serialized_content: str, file_path: str, file_ty
169169
# Add file type extension
170170
new_path = full_path.parent / f"{full_path.stem}.{file_type}"
171171

172-
# Write content to file
172+
# Write raw file content to file
173173
with open(new_path, "w") as f:
174174
f.write(serialized_content)
175175

@@ -195,16 +195,16 @@ def _pull_file(self, path: str, environment: str | None = None) -> None:
195195
file = self.client.files.retrieve_by_path(
196196
path=path,
197197
environment=environment,
198-
include_content=True
198+
include_raw_file_content=True
199199
)
200200

201201
if file.type not in ["prompt", "agent"]:
202202
raise ValueError(f"Unsupported file type: {file.type}")
203203

204-
self._save_serialized_file(file.content, file.path, file.type)
204+
self._save_serialized_file(file.raw_file_content, file.path, file.type)
205205

206206
def _pull_directory(self,
207-
directory: str | None = None,
207+
path: str | None = None,
208208
environment: str | None = None,
209209
) -> List[str]:
210210
"""Sync Prompt and Agent files from Humanloop to local filesystem.
@@ -231,9 +231,9 @@ def _pull_directory(self,
231231
response = self.client.files.list_files(
232232
type=["prompt", "agent"],
233233
page=page,
234-
include_content=True,
234+
include_raw_file_content=True,
235235
environment=environment,
236-
directory=directory
236+
path=path
237237
)
238238

239239
if len(response.records) == 0:
@@ -246,13 +246,13 @@ def _pull_directory(self,
246246
logger.warning(f"Skipping unsupported file type: {file.type}")
247247
continue
248248

249-
# Skip if no content
250-
if not getattr(file, "content", None):
249+
# Skip if no raw file content
250+
if not getattr(file, "raw_file_content", None):
251251
logger.warning(f"No content found for {file.type} {getattr(file, 'id', '<unknown>')}")
252252
continue
253253

254254
try:
255-
self._save_serialized_file(file.content, file.path, file.type)
255+
self._save_serialized_file(file.raw_file_content, file.path, file.type)
256256
successful_files.append(file.path)
257257
except Exception as e:
258258
failed_files.append(file.path)
@@ -275,7 +275,7 @@ def pull(self, path: str | None = None, environment: str | None = None) -> List[
275275
"""Pull files from Humanloop to local filesystem.
276276
277277
If the path ends with .prompt or .agent, pulls that specific file.
278-
Otherwise, pulls all files under the specified directory path.
278+
Otherwise, pulls all files under the specified path.
279279
If no path is provided, pulls all files from the root.
280280
281281
Args:

0 commit comments

Comments
 (0)