@@ -29,7 +29,8 @@ import {
2929import { HumanloopSpanExporter } from "./otel/exporter" ;
3030import { HumanloopSpanProcessor } from "./otel/processor" ;
3131import { overloadCall , overloadLog } from "./overload" ;
32- import { SyncClient , FileSyncerOptions } from "./sync" ;
32+ import { FileSyncerOptions , SyncClient } from "./sync" ;
33+ import Logger from "./utils/Logger" ;
3334import { SDK_VERSION } from "./version" ;
3435
3536const RED = "\x1b[91m" ;
@@ -200,6 +201,39 @@ class HumanloopTracerSingleton {
200201 }
201202}
202203
204+ export interface HumanloopClientOptions extends BaseHumanloopClient . Options {
205+ /**
206+ * Whether to use local files for prompts and agents
207+ */
208+ useLocalFiles ?: boolean ;
209+
210+ /**
211+ * Base directory where local prompt and agent files are stored (default: "humanloop").
212+ * This is relative to the current working directory. For example:
213+ * - "humanloop" will look for files in "./humanloop/"
214+ * - "data/humanloop" will look for files in "./data/humanloop/"
215+ * When using paths in the API, they must be relative to this directory. For example,
216+ * if localFilesDirectory="humanloop" and you have a file at "humanloop/samples/test.prompt",
217+ * you would reference it as "samples/test" in your code.
218+ */
219+ localFilesDirectory ?: string ;
220+
221+ /**
222+ * Maximum number of files to cache when useLocalFiles is true (default: DEFAULT_CACHE_SIZE).
223+ * This parameter has no effect if useLocalFiles is false.
224+ */
225+ cacheSize ?: number ;
226+
227+ /**
228+ * LLM provider modules to instrument. Allows the prompt decorator to spy on provider calls and log them to Humanloop
229+ */
230+ instrumentProviders ?: {
231+ OpenAI ?: any ;
232+ Anthropic ?: any ;
233+ CohereAI ?: any ;
234+ } ;
235+ }
236+
203237export class HumanloopClient extends BaseHumanloopClient {
204238 protected readonly _evaluations : ExtendedEvaluations ;
205239 protected readonly _prompts_overloaded : Prompts ;
@@ -212,6 +246,7 @@ export class HumanloopClient extends BaseHumanloopClient {
212246 CohereAI ?: any ;
213247 } ;
214248 protected readonly _syncClient : SyncClient ;
249+ protected readonly useLocalFiles : boolean ;
215250
216251 protected get opentelemetryTracer ( ) : Tracer {
217252 return HumanloopTracerSingleton . getInstance ( {
@@ -245,21 +280,25 @@ export class HumanloopClient extends BaseHumanloopClient {
245280 * const anthropic = new Anthropic({apiKey: process.env.ANTHROPIC_KEY});
246281 * ```
247282 */
248- constructor (
249- _options : BaseHumanloopClient . Options & {
250- instrumentProviders ?: {
251- OpenAI ?: any ;
252- Anthropic ?: any ;
253- CohereAI ?: any ;
254- } ;
255- sync ?: FileSyncerOptions ;
256- } ,
257- ) {
258- super ( _options ) ;
259-
260- this . _syncClient = new SyncClient ( this , _options . sync ) ;
261-
262- this . instrumentProviders = _options . instrumentProviders || { } ;
283+ constructor ( options : HumanloopClientOptions = { } ) {
284+ super ( options ) ;
285+
286+ this . useLocalFiles = options . useLocalFiles || false ;
287+
288+ // Warn user if cacheSize is non-default but useLocalFiles is false
289+ if ( ! this . useLocalFiles && options . cacheSize !== undefined ) {
290+ Logger . warn (
291+ `The specified cacheSize=${ options . cacheSize } will have no effect because useLocalFiles=false. ` +
292+ `File caching is only active when local files are enabled.` ,
293+ ) ;
294+ }
295+
296+ this . _syncClient = new SyncClient ( this , {
297+ baseDir : options . localFilesDirectory || "humanloop" ,
298+ cacheSize : options . cacheSize ,
299+ } ) ;
300+
301+ this . instrumentProviders = options . instrumentProviders || { } ;
263302
264303 this . _prompts_overloaded = overloadLog ( super . prompts ) ;
265304 this . _prompts_overloaded = overloadCall ( this . _prompts_overloaded ) ;
@@ -270,7 +309,7 @@ export class HumanloopClient extends BaseHumanloopClient {
270309
271310 this . _evaluators_overloaded = overloadLog ( super . evaluators ) ;
272311
273- this . _evaluations = new ExtendedEvaluations ( _options , this ) ;
312+ this . _evaluations = new ExtendedEvaluations ( options , this ) ;
274313
275314 // Initialize the tracer singleton
276315 HumanloopTracerSingleton . getInstance ( {
@@ -364,14 +403,14 @@ ${RESET}`,
364403 * temperature: 0.5,
365404 * });
366405 * const openaiContent = openaiResponse.choices[0].message.content;
367- *
406+
368407 * const anthropicClient = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
369408 * const anthropicResponse = await anthropicClient.messages.create({
370409 * model: "claude-3-5-sonnet-20240620",
371410 * temperature: 0.5,
372411 * });
373412 * const anthropicContent = anthropicResponse.content;
374- *
413+
375414 * return { openaiContent, anthropicContent };
376415 * }
377416 * });
@@ -570,40 +609,50 @@ ${RESET}`,
570609 *
571610 * This method will:
572611 * 1. Fetch Prompt and Agent files from your Humanloop workspace
573- * 2. Save them to the local filesystem using the client's files_directory (set during initialization )
612+ * 2. Save them to your local filesystem (directory specified by `localFilesDirectory`, default: "humanloop" )
574613 * 3. Maintain the same directory structure as in Humanloop
575- * 4. Add appropriate file extensions (.prompt or .agent)
614+ * 4. Add appropriate file extensions (` .prompt` or ` .agent` )
576615 *
577616 * The path parameter can be used in two ways:
578617 * - If it points to a specific file (e.g. "path/to/file.prompt" or "path/to/file.agent"), only that file will be pulled
579- * - If it points to a directory (e.g. "path/to/directory"), all Prompt and Agent files in that directory will be pulled
618+ * - If it points to a directory (e.g. "path/to/directory"), all Prompt and Agent files in that directory and its subdirectories will be pulled
580619 * - If no path is provided, all Prompt and Agent files will be pulled
581620 *
582621 * The operation will overwrite existing files with the latest version from Humanloop
583622 * but will not delete local files that don't exist in the remote workspace.
584623 *
585- * Currently only supports syncing prompt and agent files. Other file types will be skipped.
624+ * Currently only supports syncing Prompt and Agent files. Other file types will be skipped.
586625 *
587- * The files will be saved with the following structure :
626+ * For example, with the default `localFilesDirectory="humanloop"`, files will be saved as :
588627 * ```
589- * {files_directory}/
590- * ├── prompts/
591- * │ ├── my_prompt.prompt
592- * │ └── nested/
593- * │ └── another_prompt.prompt
594- * └── agents/
595- * └── my_agent.agent
628+ * ./humanloop/
629+ * ├── my_project/
630+ * │ ├── prompts/
631+ * │ │ ├── my_prompt.prompt
632+ * │ │ └── nested/
633+ * │ │ └── another_prompt.prompt
634+ * │ └── agents/
635+ * │ └── my_agent.agent
636+ * └── another_project/
637+ * └── prompts/
638+ * └── other_prompt.prompt
596639 * ```
597640 *
641+ * If you specify `localFilesDirectory="data/humanloop"`, files will be saved in ./data/humanloop/ instead.
642+ *
598643 * @param path - Optional path to either a specific file (e.g. "path/to/file.prompt") or a directory (e.g. "path/to/directory").
599644 * If not provided, all Prompt and Agent files will be pulled.
600645 * @param environment - The environment to pull the files from.
601- * @returns List of successfully processed file paths.
646+ * @returns An array containing two string arrays:
647+ * - First array contains paths of successfully synced files
648+ * - Second array contains paths of files that failed to sync (due to API errors, missing content,
649+ * or filesystem issues)
650+ * @throws HumanloopRuntimeError If there's an error communicating with the API
602651 */
603652 public async pull (
604653 path ?: string ,
605654 environment ?: string ,
606- ) : Promise < string [ ] > {
655+ ) : Promise < [ string [ ] , string [ ] ] > {
607656 return this . _syncClient . pull ( path , environment ) ;
608657 }
609658
0 commit comments