Skip to content

Commit 190233f

Browse files
author
Andrei Bratu
committed
Merge branch 'master' into flow-complete-dx
2 parents 0919d30 + a71c816 commit 190233f

22 files changed

+1060
-531
lines changed

.fernignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
src/humanloop/evals
77
src/humanloop/prompt_utils.py
8+
src/humanloop/path_utils.py
89
src/humanloop/client.py
910
src/humanloop/overload.py
1011
src/humanloop/context.py

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,48 @@ for chunk in response:
187187
yield chunk
188188
```
189189

190+
## Local File Development
191+
192+
Humanloop allows you to clone files from your Humanloop workspace to your local filesystem and use them in your code.
193+
194+
### Syncing Files with the CLI
195+
196+
```bash
197+
# Pull all files
198+
humanloop pull
199+
200+
# Pull from a directory
201+
humanloop pull --path="examples/chat"
202+
203+
# Pull a specific file
204+
humanloop pull --path="examples/chat/basic.prompt"
205+
206+
# Pull versions deployed to specific environment
207+
humanloop pull --environment="production"
208+
```
209+
210+
### Using Local Files in the SDK
211+
212+
To use local files in your code:
213+
214+
```python
215+
# Enable local file support
216+
client = Humanloop(
217+
api_key="YOUR_API_KEY",
218+
use_local_files=True
219+
)
220+
221+
# Use a local prompt file
222+
response = client.prompts.call(
223+
path="examples/chat/basic",
224+
inputs={"query": "Hello world"}
225+
)
226+
```
227+
228+
For detailed instructions on syncing, see our [Guide to Syncing and Using Local Files](https://humanloop.com/docs/v5/guides/prompts/syncing-files).
229+
230+
For information about the `.prompt` and `.agent` file formats, see our [File Format Reference](https://humanloop.com/docs/v5/reference/prompt-and-agent-files).
231+
190232
## Pagination
191233

192234
Paginated requests will return a `SyncPager` or `AsyncPager`, which can be used as generators for the underlying object.
@@ -253,6 +295,7 @@ client.prompts.log(..., request_options={
253295

254296
You can override the `httpx` client to customize it for your use-case. Some common use-cases include support for proxies
255297
and transports.
298+
256299
```python
257300
import httpx
258301
from humanloop import Humanloop

poetry.lock

Lines changed: 109 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "humanloop"
33

44
[tool.poetry]
55
name = "humanloop"
6-
version = "0.8.39"
6+
version = "0.8.40b3"
77
description = ""
88
readme = "README.md"
99
authors = []

src/humanloop/cli/__main__.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from dotenv import load_dotenv
1010

1111
from humanloop import Humanloop
12-
from humanloop.sync.sync_client import SyncClient
12+
from humanloop.sync.file_syncer import FileSyncer
1313

1414
# Set up logging
1515
logger = logging.getLogger(__name__)
@@ -154,6 +154,7 @@ def cli(): # Does nothing because used as a group for other subcommands (pull,
154154
"-p",
155155
help="Path in the Humanloop workspace to pull from (file or directory). You can pull an entire directory (e.g. 'my/directory') "
156156
"or a specific file (e.g. 'my/directory/my_prompt.prompt'). When pulling a directory, all files within that directory and its subdirectories will be included. "
157+
"Paths should not contain leading or trailing slashes. "
157158
"If not specified, pulls from the root of the remote workspace.",
158159
default=None,
159160
)
@@ -218,7 +219,12 @@ def pull(
218219
219220
Currently only supports syncing Prompt and Agent files. Other file types will be skipped."""
220221
client = get_client(api_key, env_file, base_url)
221-
sync_client = SyncClient(
222+
# Although pull() is available on the Humanloop client, we instantiate FileSyncer separately to control its log level.
223+
# This allows CLI users to toggle between detailed logging (--verbose) and minimal output without affecting the
224+
# main Humanloop client logger. The FileSyncer uses its own logger namespace (humanloop.sdk.file_syncer), making this
225+
# modification isolated from the client's OpenTelemetry setup. This client instance is short-lived and only
226+
# exists for the duration of the CLI command execution.
227+
file_syncer = FileSyncer(
222228
client, base_dir=local_files_directory, log_level=logging.DEBUG if verbose else logging.WARNING
223229
)
224230

@@ -227,7 +233,7 @@ def pull(
227233
click.echo(click.style(f"Environment: {environment or '(default)'}", fg=INFO_COLOR))
228234

229235
start_time = time.time()
230-
successful_files, failed_files = sync_client.pull(path, environment)
236+
successful_files, failed_files = file_syncer.pull(path, environment)
231237
duration_ms = int((time.time() - start_time) * 1000)
232238

233239
# Determine if the operation was successful based on failed_files

src/humanloop/client.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,34 @@
1+
import logging
12
import os
23
import typing
34
from typing import Any, List, Optional, Sequence, Tuple
4-
import logging
55

66
import httpx
77
from opentelemetry.sdk.resources import Resource
88
from opentelemetry.sdk.trace import TracerProvider
99
from opentelemetry.trace import Tracer
1010

11+
from humanloop.base_client import AsyncBaseHumanloop, BaseHumanloop
1112
from humanloop.core.client_wrapper import SyncClientWrapper
12-
13+
from humanloop.decorators.flow import flow as flow_decorator_factory
14+
from humanloop.decorators.prompt import prompt_decorator_factory
15+
from humanloop.decorators.tool import tool_decorator_factory as tool_decorator_factory
16+
from humanloop.environment import HumanloopEnvironment
1317
from humanloop.evals import run_eval
1418
from humanloop.evals.types import (
1519
DatasetEvalConfig,
16-
EvaluatorEvalConfig,
1720
EvaluatorCheck,
21+
EvaluatorEvalConfig,
1822
FileEvalConfig,
1923
)
20-
21-
from humanloop.base_client import AsyncBaseHumanloop, BaseHumanloop
22-
from humanloop.overload import overload_client
23-
from humanloop.decorators.flow import flow as flow_decorator_factory
24-
from humanloop.decorators.prompt import prompt_decorator_factory
25-
from humanloop.decorators.tool import tool_decorator_factory as tool_decorator_factory
26-
from humanloop.environment import HumanloopEnvironment
2724
from humanloop.evaluations.client import EvaluationsClient
2825
from humanloop.otel import instrument_provider
2926
from humanloop.otel.exporter import HumanloopSpanExporter
3027
from humanloop.otel.processor import HumanloopSpanProcessor
28+
from humanloop.overload import overload_client
3129
from humanloop.prompt_utils import populate_template
3230
from humanloop.prompts.client import PromptsClient
33-
from humanloop.sync.sync_client import SyncClient, DEFAULT_CACHE_SIZE
31+
from humanloop.sync.file_syncer import DEFAULT_CACHE_SIZE, FileSyncer
3432

3533
logger = logging.getLogger("humanloop.sdk")
3634

@@ -160,19 +158,20 @@ def __init__(
160158
)
161159

162160
# Check if cache_size is non-default but use_local_files is False
163-
self._sync_client = SyncClient(client=self, base_dir=local_files_directory, cache_size=cache_size)
161+
self._file_syncer = FileSyncer(client=self, base_dir=local_files_directory, cache_size=cache_size)
164162
eval_client = ExtendedEvalsClient(client_wrapper=self._client_wrapper)
165163
eval_client.client = self
166164
self.evaluations = eval_client
167165
self.prompts = ExtendedPromptsClient(client_wrapper=self._client_wrapper)
168166

169167
# Overload the .log method of the clients to be aware of Evaluation Context
170168
# and the @flow decorator providing the trace_id
169+
# Additionally, call and log methods are overloaded in the prompts and agents client to support the use of local files
171170
self.prompts = overload_client(
172-
client=self.prompts, sync_client=self._sync_client, use_local_files=self.use_local_files
171+
client=self.prompts, file_syncer=self._file_syncer, use_local_files=self.use_local_files
173172
)
174173
self.agents = overload_client(
175-
client=self.agents, sync_client=self._sync_client, use_local_files=self.use_local_files
174+
client=self.agents, file_syncer=self._file_syncer, use_local_files=self.use_local_files
176175
)
177176
self.flows = overload_client(client=self.flows)
178177
self.tools = overload_client(client=self.tools)
@@ -440,7 +439,7 @@ def pull(self, path: Optional[str] = None, environment: Optional[str] = None) ->
440439
or filesystem issues)
441440
:raises HumanloopRuntimeError: If there's an error communicating with the API
442441
"""
443-
return self._sync_client.pull(environment=environment, path=path)
442+
return self._file_syncer.pull(environment=environment, path=path)
444443

445444

446445
class AsyncHumanloop(AsyncBaseHumanloop):

src/humanloop/core/client_wrapper.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ def __init__(self, *, api_key: str, base_url: str, timeout: typing.Optional[floa
1414

1515
def get_headers(self) -> typing.Dict[str, str]:
1616
headers: typing.Dict[str, str] = {
17-
"User-Agent": "humanloop/0.8.39",
17+
"User-Agent": "humanloop/0.8.40b3",
1818
"X-Fern-Language": "Python",
1919
"X-Fern-SDK-Name": "humanloop",
20-
"X-Fern-SDK-Version": "0.8.39",
20+
"X-Fern-SDK-Version": "0.8.40b3",
2121
}
2222
headers["X-API-KEY"] = self.api_key
2323
return headers

0 commit comments

Comments
 (0)