From e8189aeeba05a045ae9d297e0f41b0be0330c988 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 10:25:51 -0800 Subject: [PATCH 01/11] remove attachment prefix --- js/src/client.ts | 4 +- js/src/tests/client.int.test.ts | 22 ++++----- js/src/tests/evaluate_attachments.int.test.ts | 48 +++++++++---------- python/langsmith/client.py | 4 +- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/js/src/client.ts b/js/src/client.ts index 23f33d515..b3f0a1e03 100644 --- a/js/src/client.ts +++ b/js/src/client.ts @@ -2735,7 +2735,7 @@ export class Client implements LangSmithTracingClientInterface { // add attachments back to the example example.attachments = Object.entries(attachment_urls).reduce( (acc, [key, value]) => { - acc[key] = { + acc[key.slice("attachment.".length)] = { presigned_url: value.presigned_url, }; return acc; @@ -2832,7 +2832,7 @@ export class Client implements LangSmithTracingClientInterface { if (attachment_urls) { example.attachments = Object.entries(attachment_urls).reduce( (acc, [key, value]) => { - acc[key] = { + acc[key.slice("attachment.".length)] = { presigned_url: value.presigned_url, }; return acc; diff --git a/js/src/tests/client.int.test.ts b/js/src/tests/client.int.test.ts index 18582a01b..7c4f58b9b 100644 --- a/js/src/tests/client.int.test.ts +++ b/js/src/tests/client.int.test.ts @@ -1410,23 +1410,23 @@ test("update examples multipart", async () => { let updatedExample = await client.readExample(exampleId); expect(updatedExample.inputs.text).toEqual("hello world2"); expect(Object.keys(updatedExample.attachments ?? {}).sort()).toEqual( - ["attachment.bar", "attachment.test_file"].sort() + ["bar", "test_file"].sort() ); expect(updatedExample.metadata).toEqual({ bar: "foo" }); let attachmentData: Uint8Array | undefined = updatedExample.attachments?.[ - "attachment.test_file" + "test_file" ].presigned_url ? new Uint8Array( (await fetch( - updatedExample.attachments?.["attachment.test_file"].presigned_url + updatedExample.attachments?.["test_file"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; expect(attachmentData).toEqual(new Uint8Array(fs.readFileSync(pathname))); - attachmentData = updatedExample.attachments?.["attachment.bar"].presigned_url + attachmentData = updatedExample.attachments?.["bar"].presigned_url ? new Uint8Array( (await fetch( - updatedExample.attachments?.["attachment.bar"].presigned_url + updatedExample.attachments?.["bar"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; @@ -1444,13 +1444,13 @@ test("update examples multipart", async () => { updatedExample = await client.readExample(exampleId); expect(updatedExample.metadata).toEqual({ foo: "bar" }); expect(Object.keys(updatedExample.attachments ?? {})).toEqual([ - "attachment.test_file2", + "test_file2", ]); - attachmentData = updatedExample.attachments?.["attachment.test_file2"] + attachmentData = updatedExample.attachments?.["test_file2"] .presigned_url ? new Uint8Array( (await fetch( - updatedExample.attachments?.["attachment.test_file2"].presigned_url + updatedExample.attachments?.["test_file2"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; @@ -1472,13 +1472,13 @@ test("update examples multipart", async () => { dataset_split: ["foo", "bar"], }); expect(Object.keys(updatedExample.attachments ?? {})).toEqual([ - "attachment.test_file", + "test_file", ]); - attachmentData = updatedExample.attachments?.["attachment.test_file"] + attachmentData = updatedExample.attachments?.["test_file"] .presigned_url ? new Uint8Array( (await fetch( - updatedExample.attachments?.["attachment.test_file"].presigned_url + updatedExample.attachments?.["test_file"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; diff --git a/js/src/tests/evaluate_attachments.int.test.ts b/js/src/tests/evaluate_attachments.int.test.ts index 9a78a3f9a..e1c346d91 100644 --- a/js/src/tests/evaluate_attachments.int.test.ts +++ b/js/src/tests/evaluate_attachments.int.test.ts @@ -34,18 +34,18 @@ test("evaluate can handle examples with attachments", async () => { config?: TargetConfigT ) => { // Verify we receive the attachment data - if (!config?.attachments?.["attachment.image"]) { + if (!config?.attachments?.["image"]) { throw new Error("Image attachment not found"); } const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = config?.attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( (await fetch( - config?.attachments?.["attachment.image"].presigned_url + config?.attachments?.["image"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; @@ -57,15 +57,15 @@ test("evaluate can handle examples with attachments", async () => { const customEvaluator = async ({ attachments }: { attachments?: any }) => { expect(attachments).toBeDefined(); - expect(attachments?.["attachment.image"]).toBeDefined(); + expect(attachments?.["image"]).toBeDefined(); const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( - (await fetch(attachments?.["attachment.image"].presigned_url).then( + (await fetch(attachments?.["image"].presigned_url).then( (res) => res.arrayBuffer() )) as ArrayBuffer ) @@ -135,15 +135,15 @@ test("evaluate with attachments not in target function", async () => { const customEvaluator = async ({ attachments }: { attachments?: any }) => { expect(attachments).toBeDefined(); - expect(attachments?.["attachment.image"]).toBeDefined(); + expect(attachments?.["image"]).toBeDefined(); const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( - (await fetch(attachments?.["attachment.image"].presigned_url).then( + (await fetch(attachments?.["image"].presigned_url).then( (res) => res.arrayBuffer() )) as ArrayBuffer ) @@ -212,18 +212,18 @@ test("multiple evaluators with attachments", async () => { config?: TargetConfigT ) => { // Verify we receive the attachment data - if (!config?.attachments?.["attachment.image"]) { + if (!config?.attachments?.["image"]) { throw new Error("Image attachment not found"); } const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = config?.attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( (await fetch( - config?.attachments?.["attachment.image"].presigned_url + config?.attachments?.["image"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; @@ -235,15 +235,15 @@ test("multiple evaluators with attachments", async () => { const customEvaluatorOne = async ({ attachments }: { attachments?: any }) => { expect(attachments).toBeDefined(); - expect(attachments?.["attachment.image"]).toBeDefined(); + expect(attachments?.["image"]).toBeDefined(); const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( - (await fetch(attachments?.["attachment.image"].presigned_url).then( + (await fetch(attachments?.["image"].presigned_url).then( (res) => res.arrayBuffer() )) as ArrayBuffer ) @@ -259,15 +259,15 @@ test("multiple evaluators with attachments", async () => { const customEvaluatorTwo = async ({ attachments }: { attachments?: any }) => { expect(attachments).toBeDefined(); - expect(attachments?.["attachment.image"]).toBeDefined(); + expect(attachments?.["image"]).toBeDefined(); const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( - (await fetch(attachments?.["attachment.image"].presigned_url).then( + (await fetch(attachments?.["image"].presigned_url).then( (res) => res.arrayBuffer() )) as ArrayBuffer ) @@ -333,18 +333,18 @@ test("evaluate with attachments runnable target function", async () => { await client.uploadExamplesMultipart(dataset.id, [example]); const myFunction = async (_input: any, config?: any) => { - if (!config?.attachments?.["attachment.image"]) { + if (!config?.attachments?.["image"]) { throw new Error("Image attachment not found"); } const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = config?.attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( (await fetch( - config?.attachments?.["attachment.image"].presigned_url + config?.attachments?.["image"].presigned_url ).then((res) => res.arrayBuffer())) as ArrayBuffer ) : undefined; @@ -359,15 +359,15 @@ test("evaluate with attachments runnable target function", async () => { const customEvaluator = async ({ attachments }: { attachments?: any }) => { expect(attachments).toBeDefined(); - expect(attachments?.["attachment.image"]).toBeDefined(); + expect(attachments?.["image"]).toBeDefined(); const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); const attachmentData: Uint8Array | undefined = attachments?.[ - "attachment.image" + "image" ].presigned_url ? new Uint8Array( - (await fetch(attachments?.["attachment.image"].presigned_url).then( + (await fetch(attachments?.["image"].presigned_url).then( (res) => res.arrayBuffer() )) as ArrayBuffer ) diff --git a/python/langsmith/client.py b/python/langsmith/client.py index a92a89659..f683a36f1 100644 --- a/python/langsmith/client.py +++ b/python/langsmith/client.py @@ -3900,7 +3900,7 @@ def read_example( response = requests.get(value["presigned_url"], stream=True) response.raise_for_status() reader = io.BytesIO(response.content) - attachments[key.split(".")[1]] = { + attachments[key.removeprefix("attachment.")] = { "presigned_url": value["presigned_url"], "reader": reader, } @@ -3986,7 +3986,7 @@ def list_examples( response = requests.get(value["presigned_url"], stream=True) response.raise_for_status() reader = io.BytesIO(response.content) - attachments[key.split(".")[1]] = { + attachments[key.removeprefix("attachment.")] = { "presigned_url": value["presigned_url"], "reader": reader, } From 7b2e73d14bdad5e93c99dec891ded50149a7ed52 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 10:35:21 -0800 Subject: [PATCH 02/11] fmt --- js/src/tests/client.int.test.ts | 20 +++--- js/src/tests/evaluate_attachments.int.test.ts | 63 +++++++++---------- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/js/src/tests/client.int.test.ts b/js/src/tests/client.int.test.ts index 7c4f58b9b..de8710e1f 100644 --- a/js/src/tests/client.int.test.ts +++ b/js/src/tests/client.int.test.ts @@ -1425,9 +1425,9 @@ test("update examples multipart", async () => { expect(attachmentData).toEqual(new Uint8Array(fs.readFileSync(pathname))); attachmentData = updatedExample.attachments?.["bar"].presigned_url ? new Uint8Array( - (await fetch( - updatedExample.attachments?.["bar"].presigned_url - ).then((res) => res.arrayBuffer())) as ArrayBuffer + (await fetch(updatedExample.attachments?.["bar"].presigned_url).then( + (res) => res.arrayBuffer() + )) as ArrayBuffer ) : undefined; expect(attachmentData).toEqual(new Uint8Array(fs.readFileSync(pathname))); @@ -1443,11 +1443,8 @@ test("update examples multipart", async () => { await client.updateExamplesMultipart(dataset.id, [exampleUpdate4]); updatedExample = await client.readExample(exampleId); expect(updatedExample.metadata).toEqual({ foo: "bar" }); - expect(Object.keys(updatedExample.attachments ?? {})).toEqual([ - "test_file2", - ]); - attachmentData = updatedExample.attachments?.["test_file2"] - .presigned_url + expect(Object.keys(updatedExample.attachments ?? {})).toEqual(["test_file2"]); + attachmentData = updatedExample.attachments?.["test_file2"].presigned_url ? new Uint8Array( (await fetch( updatedExample.attachments?.["test_file2"].presigned_url @@ -1471,11 +1468,8 @@ test("update examples multipart", async () => { foo: "bar", dataset_split: ["foo", "bar"], }); - expect(Object.keys(updatedExample.attachments ?? {})).toEqual([ - "test_file", - ]); - attachmentData = updatedExample.attachments?.["test_file"] - .presigned_url + expect(Object.keys(updatedExample.attachments ?? {})).toEqual(["test_file"]); + attachmentData = updatedExample.attachments?.["test_file"].presigned_url ? new Uint8Array( (await fetch( updatedExample.attachments?.["test_file"].presigned_url diff --git a/js/src/tests/evaluate_attachments.int.test.ts b/js/src/tests/evaluate_attachments.int.test.ts index e1c346d91..0a64a54f9 100644 --- a/js/src/tests/evaluate_attachments.int.test.ts +++ b/js/src/tests/evaluate_attachments.int.test.ts @@ -44,9 +44,9 @@ test("evaluate can handle examples with attachments", async () => { "image" ].presigned_url ? new Uint8Array( - (await fetch( - config?.attachments?.["image"].presigned_url - ).then((res) => res.arrayBuffer())) as ArrayBuffer + (await fetch(config?.attachments?.["image"].presigned_url).then( + (res) => res.arrayBuffer() + )) as ArrayBuffer ) : undefined; if (!arraysEqual(attachmentData ?? new Uint8Array(), expectedData)) { @@ -61,12 +61,11 @@ test("evaluate can handle examples with attachments", async () => { const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); - const attachmentData: Uint8Array | undefined = attachments?.[ - "image" - ].presigned_url + const attachmentData: Uint8Array | undefined = attachments?.["image"] + .presigned_url ? new Uint8Array( - (await fetch(attachments?.["image"].presigned_url).then( - (res) => res.arrayBuffer() + (await fetch(attachments?.["image"].presigned_url).then((res) => + res.arrayBuffer() )) as ArrayBuffer ) : undefined; @@ -139,12 +138,11 @@ test("evaluate with attachments not in target function", async () => { const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); - const attachmentData: Uint8Array | undefined = attachments?.[ - "image" - ].presigned_url + const attachmentData: Uint8Array | undefined = attachments?.["image"] + .presigned_url ? new Uint8Array( - (await fetch(attachments?.["image"].presigned_url).then( - (res) => res.arrayBuffer() + (await fetch(attachments?.["image"].presigned_url).then((res) => + res.arrayBuffer() )) as ArrayBuffer ) : undefined; @@ -222,9 +220,9 @@ test("multiple evaluators with attachments", async () => { "image" ].presigned_url ? new Uint8Array( - (await fetch( - config?.attachments?.["image"].presigned_url - ).then((res) => res.arrayBuffer())) as ArrayBuffer + (await fetch(config?.attachments?.["image"].presigned_url).then( + (res) => res.arrayBuffer() + )) as ArrayBuffer ) : undefined; if (!arraysEqual(attachmentData ?? new Uint8Array(), expectedData)) { @@ -239,12 +237,11 @@ test("multiple evaluators with attachments", async () => { const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); - const attachmentData: Uint8Array | undefined = attachments?.[ - "image" - ].presigned_url + const attachmentData: Uint8Array | undefined = attachments?.["image"] + .presigned_url ? new Uint8Array( - (await fetch(attachments?.["image"].presigned_url).then( - (res) => res.arrayBuffer() + (await fetch(attachments?.["image"].presigned_url).then((res) => + res.arrayBuffer() )) as ArrayBuffer ) : undefined; @@ -263,12 +260,11 @@ test("multiple evaluators with attachments", async () => { const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); - const attachmentData: Uint8Array | undefined = attachments?.[ - "image" - ].presigned_url + const attachmentData: Uint8Array | undefined = attachments?.["image"] + .presigned_url ? new Uint8Array( - (await fetch(attachments?.["image"].presigned_url).then( - (res) => res.arrayBuffer() + (await fetch(attachments?.["image"].presigned_url).then((res) => + res.arrayBuffer() )) as ArrayBuffer ) : undefined; @@ -343,9 +339,9 @@ test("evaluate with attachments runnable target function", async () => { "image" ].presigned_url ? new Uint8Array( - (await fetch( - config?.attachments?.["image"].presigned_url - ).then((res) => res.arrayBuffer())) as ArrayBuffer + (await fetch(config?.attachments?.["image"].presigned_url).then( + (res) => res.arrayBuffer() + )) as ArrayBuffer ) : undefined; if (!arraysEqual(attachmentData ?? new Uint8Array(), expectedData)) { @@ -363,12 +359,11 @@ test("evaluate with attachments runnable target function", async () => { const expectedData = new Uint8Array( Buffer.from("fake image data for testing") ); - const attachmentData: Uint8Array | undefined = attachments?.[ - "image" - ].presigned_url + const attachmentData: Uint8Array | undefined = attachments?.["image"] + .presigned_url ? new Uint8Array( - (await fetch(attachments?.["image"].presigned_url).then( - (res) => res.arrayBuffer() + (await fetch(attachments?.["image"].presigned_url).then((res) => + res.arrayBuffer() )) as ArrayBuffer ) : undefined; From 347198e331eb16625fcd4fd945383c62a1644689 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:27:31 -0800 Subject: [PATCH 03/11] wip --- js/src/client.ts | 225 +++++++++--------- js/src/evaluation/_runner.ts | 2 +- js/src/schemas.ts | 2 +- js/src/tests/client.int.test.ts | 13 +- python/langsmith/schemas.py | 4 +- python/tests/integration_tests/test_client.py | 87 +++++++ 6 files changed, 209 insertions(+), 124 deletions(-) diff --git a/js/src/client.ts b/js/src/client.ts index b3f0a1e03..7afeca094 100644 --- a/js/src/client.ts +++ b/js/src/client.ts @@ -59,13 +59,14 @@ import { RunEvaluator, } from "./evaluation/evaluator.js"; import { __version__ } from "./index.js"; -import { assertUuid } from "./utils/_uuid.js"; +import { assertUuid } from "./utils/uuid.js"; import { warnOnce } from "./utils/warn.js"; import { parsePromptIdentifier } from "./utils/prompts.js"; import { raiseForStatus } from "./utils/error.js"; import { _getFetchImplementation } from "./singletons/fetch.js"; import { stringify as stringifyForTracing } from "./utils/fast-safe-stringify/index.js"; +import { v4 as uuid4 } from "uuid"; export interface ClientConfig { apiUrl?: string; @@ -1139,12 +1140,20 @@ export class Client implements LangSmithTracingClientInterface { ); continue; } - accumulatedParts.push({ - name: `attachment.${payload.id}.${name}`, - payload: new Blob([content], { - type: `${contentType}; length=${content.byteLength}`, - }), - }); + // eslint-disable-next-line no-instanceof/no-instanceof + if (content instanceof Blob) { + accumulatedParts.push({ + name: `attachment.${payload.id}.${name}`, + payload: content, + }); + } else { + accumulatedParts.push({ + name: `attachment.${payload.id}.${name}`, + payload: new Blob([content], { + type: `${contentType}; length=${content.byteLength}`, + }), + }); + } } } } @@ -3909,70 +3918,7 @@ export class Client implements LangSmithTracingClientInterface { "Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version." ); } - const formData = new FormData(); - - for (const example of updates) { - const exampleId = example.id; - - // Prepare the main example body - const exampleBody = { - ...(example.metadata && { metadata: example.metadata }), - ...(example.split && { split: example.split }), - }; - - // Add main example data - const stringifiedExample = stringifyForTracing(exampleBody); - const exampleBlob = new Blob([stringifiedExample], { - type: "application/json", - }); - formData.append(exampleId, exampleBlob); - - // Add inputs - if (example.inputs) { - const stringifiedInputs = stringifyForTracing(example.inputs); - const inputsBlob = new Blob([stringifiedInputs], { - type: "application/json", - }); - formData.append(`${exampleId}.inputs`, inputsBlob); - } - - // Add outputs if present - if (example.outputs) { - const stringifiedOutputs = stringifyForTracing(example.outputs); - const outputsBlob = new Blob([stringifiedOutputs], { - type: "application/json", - }); - formData.append(`${exampleId}.outputs`, outputsBlob); - } - - // Add attachments if present - if (example.attachments) { - for (const [name, [mimeType, data]] of Object.entries( - example.attachments - )) { - const attachmentBlob = new Blob([data], { - type: `${mimeType}; length=${data.byteLength}`, - }); - formData.append(`${exampleId}.attachment.${name}`, attachmentBlob); - } - } - - if (example.attachments_operations) { - const stringifiedAttachmentsOperations = stringifyForTracing( - example.attachments_operations - ); - const attachmentsOperationsBlob = new Blob( - [stringifiedAttachmentsOperations], - { - type: "application/json", - } - ); - formData.append( - `${exampleId}.attachments_operations`, - attachmentsOperationsBlob - ); - } - } + const formData = _prepareMultiPartData(updates); const response = await this.caller.call( _getFetchImplementation(), @@ -4001,53 +3947,7 @@ export class Client implements LangSmithTracingClientInterface { "Your LangSmith version does not allow using the multipart examples endpoint, please update to the latest version." ); } - const formData = new FormData(); - - for (const example of uploads) { - const exampleId = (example.id ?? uuid.v4()).toString(); - - // Prepare the main example body - const exampleBody = { - created_at: example.created_at, - ...(example.metadata && { metadata: example.metadata }), - ...(example.split && { split: example.split }), - }; - - // Add main example data - const stringifiedExample = stringifyForTracing(exampleBody); - const exampleBlob = new Blob([stringifiedExample], { - type: "application/json", - }); - formData.append(exampleId, exampleBlob); - - // Add inputs - const stringifiedInputs = stringifyForTracing(example.inputs); - const inputsBlob = new Blob([stringifiedInputs], { - type: "application/json", - }); - formData.append(`${exampleId}.inputs`, inputsBlob); - - // Add outputs if present - if (example.outputs) { - const stringifiedOutputs = stringifyForTracing(example.outputs); - const outputsBlob = new Blob([stringifiedOutputs], { - type: "application/json", - }); - formData.append(`${exampleId}.outputs`, outputsBlob); - } - - // Add attachments if present - if (example.attachments) { - for (const [name, [mimeType, data]] of Object.entries( - example.attachments - )) { - const attachmentBlob = new Blob([data], { - type: `${mimeType}; length=${data.byteLength}`, - }); - formData.append(`${exampleId}.attachment.${name}`, attachmentBlob); - } - } - } + const formData = _prepareMultiPartData(uploads); const response = await this.caller.call( _getFetchImplementation(), @@ -4370,3 +4270,92 @@ export interface LangSmithTracingClientInterface { updateRun: (runId: string, run: RunUpdate) => Promise; } + +function isExampleUpdateWithAttachments( + obj: ExampleUpdateWithAttachments | ExampleUploadWithAttachments +): obj is ExampleUpdateWithAttachments { + return ( + (obj as ExampleUpdateWithAttachments).attachments_operations !== undefined + ); +} + +function _prepareMultiPartData( + examples: ExampleUpdateWithAttachments[] | ExampleUploadWithAttachments[] +): FormData { + const formData = new FormData(); + + for (const example of examples) { + const exampleId = example.id ?? uuid4(); + + // Prepare the main example body + const exampleBody = { + ...(example.metadata && { metadata: example.metadata }), + ...(example.split && { split: example.split }), + }; + + // Add main example data + const stringifiedExample = stringifyForTracing(exampleBody); + const exampleBlob = new Blob([stringifiedExample], { + type: "application/json", + }); + formData.append(exampleId, exampleBlob); + + // Add inputs + if (example.inputs) { + const stringifiedInputs = stringifyForTracing(example.inputs); + const inputsBlob = new Blob([stringifiedInputs], { + type: "application/json", + }); + formData.append(`${exampleId}.inputs`, inputsBlob); + } + + // Add outputs if present + if (example.outputs) { + const stringifiedOutputs = stringifyForTracing(example.outputs); + const outputsBlob = new Blob([stringifiedOutputs], { + type: "application/json", + }); + formData.append(`${exampleId}.outputs`, outputsBlob); + } + + // Add attachments if present + if (example.attachments) { + for (const [name, [mimeType, data]] of Object.entries( + example.attachments + )) { + // eslint-disable-next-line no-instanceof/no-instanceof + if (data instanceof Blob) { + formData.append(`${exampleId}.attachment.${name}`, data); + } else { + formData.append( + `${exampleId}.attachment.${name}`, + new Blob([data], { + type: `${mimeType}; length=${data.byteLength}`, + }) + ); + } + } + } + + if ( + isExampleUpdateWithAttachments(example) && + example.attachments_operations + ) { + const stringifiedAttachmentsOperations = stringifyForTracing( + example.attachments_operations + ); + const attachmentsOperationsBlob = new Blob( + [stringifiedAttachmentsOperations], + { + type: "application/json", + } + ); + formData.append( + `${exampleId}.attachments_operations`, + attachmentsOperationsBlob + ); + } + } + + return formData; +} diff --git a/js/src/evaluation/_runner.ts b/js/src/evaluation/_runner.ts index 6c74afa34..e9bed2e4a 100644 --- a/js/src/evaluation/_runner.ts +++ b/js/src/evaluation/_runner.ts @@ -9,7 +9,7 @@ import { } from "../schemas.js"; import { traceable } from "../traceable.js"; import { getDefaultRevisionId, getGitInfo } from "../utils/_git.js"; -import { assertUuid } from "../utils/_uuid.js"; +import { assertUuid } from "../utils/uuid.js"; import { AsyncCaller } from "../utils/async_caller.js"; import { atee } from "../utils/atee.js"; import { getLangChainEnvVarsMetadata } from "../utils/env.js"; diff --git a/js/src/schemas.ts b/js/src/schemas.ts index af7848e59..ca00e96d8 100644 --- a/js/src/schemas.ts +++ b/js/src/schemas.ts @@ -67,7 +67,7 @@ export interface AttachmentInfo { presigned_url: string; } -export type AttachmentData = Uint8Array | ArrayBuffer; +export type AttachmentData = Uint8Array | Blob; export type Attachments = Record; /** diff --git a/js/src/tests/client.int.test.ts b/js/src/tests/client.int.test.ts index de8710e1f..5ec6bd2e1 100644 --- a/js/src/tests/client.int.test.ts +++ b/js/src/tests/client.int.test.ts @@ -1251,7 +1251,7 @@ test("annotationqueue crud", async () => { } }); -test("upload examples multipart", async () => { +test.only("upload examples multipart", async () => { const client = new Client(); const datasetName = `__test_upload_examples_multipart${uuidv4().slice(0, 4)}`; @@ -1278,7 +1278,7 @@ test("upload examples multipart", async () => { inputs: { text: "hello world" }, // check that passing no outputs works fine attachments: { - test_file: ["image/png", fs.readFileSync(pathname)], + test_file: ["image/png", new Uint8Array(fs.readFileSync(pathname))], }, }; @@ -1286,7 +1286,12 @@ test("upload examples multipart", async () => { inputs: { text: "foo bar" }, outputs: { response: "baz" }, attachments: { - my_file: ["image/png", fs.readFileSync(pathname)], + my_file: [ + "image/png", + new Blob([fs.readFileSync(pathname)], { + type: `image/png; length=${fs.readFileSync(pathname).byteLength}`, + }), + ], }, }; @@ -1300,12 +1305,14 @@ test("upload examples multipart", async () => { const createdExample1 = await client.readExample(exampleId); expect(createdExample1.inputs["text"]).toBe("hello world"); + expect(createdExample1.attachments?.["test_file"]).toBeDefined(); const createdExample2 = await client.readExample( createdExamples.example_ids.find((id) => id !== exampleId)! ); expect(createdExample2.inputs["text"]).toBe("foo bar"); expect(createdExample2.outputs?.["response"]).toBe("baz"); + expect(createdExample2.attachments?.["my_file"]).toBeDefined(); // Test examples were sent to correct dataset const allExamplesInDataset = []; diff --git a/python/langsmith/schemas.py b/python/langsmith/schemas.py index acedaf177..1a6cc4847 100644 --- a/python/langsmith/schemas.py +++ b/python/langsmith/schemas.py @@ -39,6 +39,8 @@ StrictInt, ) +from pathlib import Path + from typing_extensions import Literal SCORE_TYPE = Union[StrictBool, StrictInt, StrictFloat, None] @@ -63,7 +65,7 @@ def my_function(bar: int, my_val: Attachment): data: bytes -Attachments = Dict[str, Union[Tuple[str, bytes], Attachment]] +Attachments = Dict[str, Union[Tuple[str, bytes], Attachment, Tuple[str, Path]]] """Attachments associated with the run. Each entry is a tuple of (mime_type, bytes), or (mime_type, file_path)""" diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index a2425c2af..5b733e00f 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -10,6 +10,7 @@ import time import uuid from datetime import timedelta +from pathlib import Path from typing import Any, Callable, Dict from unittest import mock from uuid import uuid4 @@ -1808,6 +1809,92 @@ def test_bulk_update_examples_with_attachments_operations( langchain_client.delete_dataset(dataset_id=dataset.id) +def test_examples_multipart_attachment_path(langchain_client: Client) -> None: + """Test uploading examples with attachments via multipart endpoint.""" + langchain_client = Client( + api_key="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" + ) + dataset_name = "__test_upload_examples_multipart" + uuid4().hex[:4] + if langchain_client.has_dataset(dataset_name=dataset_name): + langchain_client.delete_dataset(dataset_name=dataset_name) + + dataset = langchain_client.create_dataset( + dataset_name, + description="Test dataset for multipart example uploads", + data_type=DataType.kv, + ) + + example_id = uuid4() + example = ExampleUploadWithAttachments( + id=example_id, + inputs={"text": "hello world"}, + attachments={ + "file1": ("text/plain", b"original content 1"), + "file2": ("image/png", Path(__file__).parent / "test_data/parrot-icon.png"), + }, + ) + + created_examples = langchain_client.upload_examples_multipart( + dataset_id=dataset.id, uploads=[example] + ) + assert created_examples["count"] == 1 + + # Verify the upload + retrieved = langchain_client.read_example(example_id) + + assert len(retrieved.attachments) == 2 + assert "file1" in retrieved.attachments + assert "file2" in retrieved.attachments + assert retrieved.attachments["file1"]["reader"].read() == b"original content 1" + assert ( + retrieved.attachments["file2"]["reader"].read() + == (Path(__file__).parent / "test_data/parrot-icon.png").read_bytes() + ) + + example_update = ExampleUpdateWithAttachments( + id=example_id, + attachments={ + "new_file1": ( + "image/png", + Path(__file__).parent / "test_data/parrot-icon.png", + ), + }, + ) + + langchain_client.update_examples_multipart( + dataset_id=dataset.id, updates=[example_update] + ) + + retrieved = langchain_client.read_example(example_id) + + assert len(retrieved.attachments) == 1 + assert "new_file1" in retrieved.attachments + assert ( + retrieved.attachments["new_file1"]["reader"].read() + == (Path(__file__).parent / "test_data/parrot-icon.png").read_bytes() + ) + + example_wrong_path = ExampleUploadWithAttachments( + id=example_id, + inputs={"text": "hello world"}, + attachments={ + "file1": ( + "text/plain", + Path(__file__).parent / "test_data/not-a-real-file.txt", + ), + }, + ) + + with pytest.raises(FileNotFoundError) as exc_info: + langchain_client.upload_examples_multipart( + dataset_id=dataset.id, uploads=[example_wrong_path] + ) + assert "test_data/not-a-real-file.txt" in str(exc_info.value) + + # Clean up + langchain_client.delete_dataset(dataset_id=dataset.id) + + def test_update_examples_multipart(langchain_client: Client) -> None: """Test updating examples with attachments via multipart endpoint.""" dataset_name = "__test_update_examples_multipart" + uuid4().hex[:4] From 9fbc0c83672b4763acc85410138e3d6d4ffc8491 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:29:48 -0800 Subject: [PATCH 04/11] x --- js/src/schemas.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/schemas.ts b/js/src/schemas.ts index ca00e96d8..6ca108b1d 100644 --- a/js/src/schemas.ts +++ b/js/src/schemas.ts @@ -67,7 +67,7 @@ export interface AttachmentInfo { presigned_url: string; } -export type AttachmentData = Uint8Array | Blob; +export type AttachmentData = ArrayBuffer | Uint8Array | Blob; export type Attachments = Record; /** From 6275c1c73facc79382b670ded92b258e2293b11d Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:37:22 -0800 Subject: [PATCH 05/11] fmt --- js/src/client.ts | 2 +- js/src/evaluation/_runner.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/client.ts b/js/src/client.ts index 7afeca094..bd7590bda 100644 --- a/js/src/client.ts +++ b/js/src/client.ts @@ -59,7 +59,7 @@ import { RunEvaluator, } from "./evaluation/evaluator.js"; import { __version__ } from "./index.js"; -import { assertUuid } from "./utils/uuid.js"; +import { assertUuid } from "./utils/_uuid.js"; import { warnOnce } from "./utils/warn.js"; import { parsePromptIdentifier } from "./utils/prompts.js"; import { raiseForStatus } from "./utils/error.js"; diff --git a/js/src/evaluation/_runner.ts b/js/src/evaluation/_runner.ts index e9bed2e4a..6c74afa34 100644 --- a/js/src/evaluation/_runner.ts +++ b/js/src/evaluation/_runner.ts @@ -9,7 +9,7 @@ import { } from "../schemas.js"; import { traceable } from "../traceable.js"; import { getDefaultRevisionId, getGitInfo } from "../utils/_git.js"; -import { assertUuid } from "../utils/uuid.js"; +import { assertUuid } from "../utils/_uuid.js"; import { AsyncCaller } from "../utils/async_caller.js"; import { atee } from "../utils/atee.js"; import { getLangChainEnvVarsMetadata } from "../utils/env.js"; From 52ff721e873d25afed8ad2e462743ae0ad725dc5 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:41:20 -0800 Subject: [PATCH 06/11] add icon --- .../integration_tests/test_data/parrot-icon.png | Bin 0 -> 35267 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 python/tests/integration_tests/test_data/parrot-icon.png diff --git a/python/tests/integration_tests/test_data/parrot-icon.png b/python/tests/integration_tests/test_data/parrot-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd3de1dc7018c6c53165b7272411a7c6f771852 GIT binary patch literal 35267 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 z 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 Date: Wed, 11 Dec 2024 12:43:09 -0800 Subject: [PATCH 07/11] x --- python/tests/integration_tests/test_client.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index 5b733e00f..ffc9d4165 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -1811,9 +1811,6 @@ def test_bulk_update_examples_with_attachments_operations( def test_examples_multipart_attachment_path(langchain_client: Client) -> None: """Test uploading examples with attachments via multipart endpoint.""" - langchain_client = Client( - api_key="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" - ) dataset_name = "__test_upload_examples_multipart" + uuid4().hex[:4] if langchain_client.has_dataset(dataset_name=dataset_name): langchain_client.delete_dataset(dataset_name=dataset_name) From 52dd2b345882f5b33ee561e7dd35ae93d68ecc6c Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:55:41 -0800 Subject: [PATCH 08/11] separate PRs --- python/langsmith/schemas.py | 4 +- python/tests/integration_tests/test_client.py | 84 ------------------- 2 files changed, 1 insertion(+), 87 deletions(-) diff --git a/python/langsmith/schemas.py b/python/langsmith/schemas.py index 1a6cc4847..acedaf177 100644 --- a/python/langsmith/schemas.py +++ b/python/langsmith/schemas.py @@ -39,8 +39,6 @@ StrictInt, ) -from pathlib import Path - from typing_extensions import Literal SCORE_TYPE = Union[StrictBool, StrictInt, StrictFloat, None] @@ -65,7 +63,7 @@ def my_function(bar: int, my_val: Attachment): data: bytes -Attachments = Dict[str, Union[Tuple[str, bytes], Attachment, Tuple[str, Path]]] +Attachments = Dict[str, Union[Tuple[str, bytes], Attachment]] """Attachments associated with the run. Each entry is a tuple of (mime_type, bytes), or (mime_type, file_path)""" diff --git a/python/tests/integration_tests/test_client.py b/python/tests/integration_tests/test_client.py index ffc9d4165..a2425c2af 100644 --- a/python/tests/integration_tests/test_client.py +++ b/python/tests/integration_tests/test_client.py @@ -10,7 +10,6 @@ import time import uuid from datetime import timedelta -from pathlib import Path from typing import Any, Callable, Dict from unittest import mock from uuid import uuid4 @@ -1809,89 +1808,6 @@ def test_bulk_update_examples_with_attachments_operations( langchain_client.delete_dataset(dataset_id=dataset.id) -def test_examples_multipart_attachment_path(langchain_client: Client) -> None: - """Test uploading examples with attachments via multipart endpoint.""" - dataset_name = "__test_upload_examples_multipart" + uuid4().hex[:4] - if langchain_client.has_dataset(dataset_name=dataset_name): - langchain_client.delete_dataset(dataset_name=dataset_name) - - dataset = langchain_client.create_dataset( - dataset_name, - description="Test dataset for multipart example uploads", - data_type=DataType.kv, - ) - - example_id = uuid4() - example = ExampleUploadWithAttachments( - id=example_id, - inputs={"text": "hello world"}, - attachments={ - "file1": ("text/plain", b"original content 1"), - "file2": ("image/png", Path(__file__).parent / "test_data/parrot-icon.png"), - }, - ) - - created_examples = langchain_client.upload_examples_multipart( - dataset_id=dataset.id, uploads=[example] - ) - assert created_examples["count"] == 1 - - # Verify the upload - retrieved = langchain_client.read_example(example_id) - - assert len(retrieved.attachments) == 2 - assert "file1" in retrieved.attachments - assert "file2" in retrieved.attachments - assert retrieved.attachments["file1"]["reader"].read() == b"original content 1" - assert ( - retrieved.attachments["file2"]["reader"].read() - == (Path(__file__).parent / "test_data/parrot-icon.png").read_bytes() - ) - - example_update = ExampleUpdateWithAttachments( - id=example_id, - attachments={ - "new_file1": ( - "image/png", - Path(__file__).parent / "test_data/parrot-icon.png", - ), - }, - ) - - langchain_client.update_examples_multipart( - dataset_id=dataset.id, updates=[example_update] - ) - - retrieved = langchain_client.read_example(example_id) - - assert len(retrieved.attachments) == 1 - assert "new_file1" in retrieved.attachments - assert ( - retrieved.attachments["new_file1"]["reader"].read() - == (Path(__file__).parent / "test_data/parrot-icon.png").read_bytes() - ) - - example_wrong_path = ExampleUploadWithAttachments( - id=example_id, - inputs={"text": "hello world"}, - attachments={ - "file1": ( - "text/plain", - Path(__file__).parent / "test_data/not-a-real-file.txt", - ), - }, - ) - - with pytest.raises(FileNotFoundError) as exc_info: - langchain_client.upload_examples_multipart( - dataset_id=dataset.id, uploads=[example_wrong_path] - ) - assert "test_data/not-a-real-file.txt" in str(exc_info.value) - - # Clean up - langchain_client.delete_dataset(dataset_id=dataset.id) - - def test_update_examples_multipart(langchain_client: Client) -> None: """Test updating examples with attachments via multipart endpoint.""" dataset_name = "__test_update_examples_multipart" + uuid4().hex[:4] From 85d5b92d2cb69a3fc46f69daed23d3fca3b50996 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 12:57:09 -0800 Subject: [PATCH 09/11] separate PRs --- .../integration_tests/test_data/parrot-icon.png | Bin 35267 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 python/tests/integration_tests/test_data/parrot-icon.png diff --git a/python/tests/integration_tests/test_data/parrot-icon.png b/python/tests/integration_tests/test_data/parrot-icon.png deleted file mode 100644 index 7fd3de1dc7018c6c53165b7272411a7c6f771852..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35267 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 z zL148K@e&v+#~!r;#GgQ+=W+#A{WQ(EN5{S2!M!K_!kbrbSS+dKy^-a;g?=z%t$w5i zQx1qJ1r#S;DRp(CvWAD*fsU0%b?i-4R7C%OzBJ;UT&HnCQfuS;t4vwV!(w`D2TSy5$4W zY!GW_1OKW2@wz(>4opdgDmQ6DQibVnVhv@y9aYk0ar~uYc-)KwiI0yaG$TI(c1Bbb zWA6agC{-#AdD>_Lt#XArdZ(32ckeZBGqCZS$%61m4VYFlumgE-?z3R+r%*DPxM==Z zyS;2J%<(UfNi`K4Xde( zI~o#l;@u-UBJz%cPsG9C5QwVW&D>%F`#5(IhwhR&KVloEsk&fJ4{fiDDcn2H*ewLO zpa)||6?o_vp*-O|SCGZ04&mZkTo9)2}@6Yqin=H_Q3fDW1;*(kZf<;3x znu<7q@sm`JlUIYXMVPi;ll8X?Zijq`2H2B2un3RU7sN62p|L*~)Y&ei22}1A{zLb> zc{BMt3{8h(fH+}sG;h2L9XOi$m!^d!o29i&q^*9#c8KpCu*v9f;nF;1hz{l903hLM z#1dwf!WV_a=T?=bHlx_?NYaVOG@%o1Al2hfP1_H@!m>^ihr0zn@k~poN*C#nxBix! zK+=j6F})B>_FHcxV6Wvz=z3cHYb_m7chS}-syC_pwS3##Gj%8(%MI7gz8%%bhEl5U z-$b}wX~BJ2fkU#w2Gn}bpHVEG|8Ewc{4e-)GHEX(RHsvKEdW;?SP5^U5l9f|R4J^; zF;orhIS=fS+$}Q*GEq&i6 zYv7pEa>D8Q?)#FeyfWc%k}F-f=sfnI{P*RQv__=&nOYMge8pAY@zNCP@-)53eLpVj zhhpe3WZ0;#4~egU_5&z;tV?`ZYFO2<%2a8|XtK>H0oRCyr!c~klm;r`JEDgvu#3P1 zB-}k&iCGT6hoBAz|4UefgZkHV>5jJalnfGe&Cgf~4piuPD=75rPhnA5F@1)i>cISw z8bHQAunY1E{{H~VKsCR?OBV1th$Pb_QSi0_KwN`KA&f9zten1*(B4Dy)Kjer`F)E&&;ZfS(6KBf+L^5?HtyK>n-%Akau0 zQ$}+OR@V!XuoX72~&QYB%&|EH1-7iyC-J))MNZVBDgk+AAio?Glnbn14hqUZVHqec2PFcPR)De!P+kct zDg>$o6;+_}hpIu9Ko!-Xq6U=L{0kMO0$NI{pr2M)2J%WkP65cw1)Own_mPOT9TJ6x zM@5l8B>)mg8KNY^RbFe0qRvjN?jEfEeuB|ql34)7`V`eRnCftj?sS&wa-OpM8g1QO z`qohTt_b>`DC)i!gk4W$H{BCiegW9~1Izuu*&A#P65kt)I&cwr_}a2#x5a|)Ej#^4 z>|ChWm2lBJQQ&bLc#;5~CW2>)-~}7RrGexOz|I1^T#%jzvI-$z=9hxPQczR^iiF1! zP*Ms?OF?-Vs3?V0Sq7@hKutNQt^hR^(6PEwpej%+(6=j9psEs7R)EUN?}Su>@(NH^ z2K}^>Vo+EBa`HfSHsGa!)4`%lZ4?@Qs21|)13*FodP)k(QdWJVg7$Vr!O94N;Q^w_ z5olS2-5ID5vOi66I!pGrOkH)0wlRdUBZ9s=ioQ3B2b}>nKEP@p@bm%O zPb}MaPW<3S(W6&Ez)g`8cfsig;6f<45e^E<`>IGk_ zD+jgZ5GOTd0wgLxRXGGtRVk<}1?5Gcq!1M3f!s`xl@4A!2VP#HBr*c-h7ms-0MN&1 z%b*mrrB&Bqw6-hi?8NEqAsFn3mdBa;lB|QsHm9Jik`8AmuIDMsuTs}PWNZ&t+8w2| zH=4RT62I$-{FZymR$l?mLBP@*SnL7I4}+a2#P*+EcIbkL?-dYu1Dw1Kf**j(q2Nvg z2#p5evEW$@coGYul0hODu+l(kI^bu3j4Y6y19EdgejX^y2Zec{C=V3pfs#B>nh(kf zKt(>NDg-sfptc0mm4b#c&{zhV%AuoBO#=P-q_GV8HX$Lkr4TtaCD2c(E&>$=pezp* zXM_9Pc(h^3*xSD#T}1P8~!i$-ovZSE6@Lb zgKbcusP}>pLLh-elZ5EK7Xj*YFdf_2*ap)~F&HqtxtAnP9LJ6;w!ygIUSd0SCX>m` ze!t&6zxHSR&LbzYv%9l9liAtLObq9Jp7Vf_442RAe%igy9dpa>L(&cyYGABFK{G1a z-RrvCntHK)0J{#LV-(#J7?{D>5@wEJ_84Z5#A#j`cMSi{RZ`0OrT-6Qn+9$w%5ce+F9)m`G- zUf#v?yLfg7&u;&qCo9Cet>3`iYgoC6D`(Ny0Zqu3jboQQ-!%YS&rGL?VtoV6j9>>V zq@1a(VQOo*VKuzSdR|PMFmAUnez(ANo{}(kNNk>ymL8K;o|IJ_6_y`iS1d6dGo+$n zWbH#jD|FR}twKQ)D)*qi3oX6az8~#F=o~@MI0h#%GKYyJ%pAtT2^@b9AAO7q7YSXw zfJ+x}psIt2;nVuU?!1J8~ zz>`EGdy&~HZ+SjF$j(qXnZyF=YPPnD6H&`EY!k)q62-L(H==wO8avU_jU9d1GvH!i3m6Z{f2WczFXamtDG!7uWFY3Z7iX zgNs=C0P{nLkKW|7v3Zj8jsf6FB6;&D;$+`Iiz^VSU~4Mbp=E4sB`2zhPqYPg31ZuL ziMxfVU81Z3anY32dBoqjAa&03E0#EwhuNib6zjNK!2mKk5Z4S{6%6Ibu0!QcGbn(zzDK3RSuTg z$x=Ir!f1FMSHGPfvy&gQgB!n-pVT4D=n>})i%TY@mT9qlR#>_yC_l`rSfV>-Jxj)r z*N?Q_KQd53Fi_Ek=1y$ehn-#6(}#Wi=o`lHI1Wu=W)2GrID8n#ju54o_fFuG_i*I{ zTsw>94{`G&+&+g@*Y)0ctbc+BpWxv|Jidgdm+<^DUSGr4H(ZA9vi0w75!bJ-J#Hq~fb5idW%!w+zF9L_v9F57cs!@#=) zfNNclH;*DsqAQEpK^9h!jj6J;h?Jm`6WK@{rD1618nO4L_kCz`|^(f}{!5HpN_454r|0qAV-X#D?B$79Y9qKL1p$A!*L1sp;b@8+I3-X7BC5J@TX@PT|U%texSYnpVQ*9IOrUS_CLGmt_ zAE<`D61nxL+>YjUY}UQ;OZ${KaJ(n zxOf~Fj$!2t?tXxKXYueHo_vg_=kfeJVd$?u!J7;C`ZB(~hVPg0yJh_9GG3m;(+>%Q z{{Axl&vlos;h(SJpO^8wYxw3OzC4ewKlxGT|3#mFga^lQc>--t1j{#VCeovy|L^iM z>fHjsgG}<`kOk4+fhF`HGdPG4wf&qOk(I3dFMMW!G+OcB~c6DIyKJ@ls zXb_{rm>R?UBo0qwX$p&zI5mg!OSp6l*N)@rahzMihYPrR3~Q%x_dTq?kH=>T`+o5e zF=4;{7_ZOa&3Sxv0l&VCZ!h7iPw?s^Jo^C8-^1r0;`@vEzpmo{eyi*FUsnh#fBgY| z{V~4%n7ID>oa^ek`Q}4>b`p0NaHbFTbax*g&kX=b@03Us65XFF%kfp3X-bRB3E1f& z4yLAz6;{QH+{)HBa$>ge5_a)ZJNTJ>{JbGy(WuBgA+XO1oC}-exo&cFn78tw(3K+@~W{{cbVt^T1%FB*#c=G|ibm=SC^^5oM`5D5{zc_<$&*Fc7jQ{6T{Qf*vXYu|J zZcXEh)A;_ZOYh^i@8P@mi0g08;IqS6o500>RAxfNA-V$Ztll{Q-ipel3iPyqLKg!T zW{{N`Y-ffznVJe_L@hh2o^5Dh#kFyg_i)p@c-i~;`3LyL<9y2$&oRp_UtpCl(#jTm z95Wu4Lof}y7dAH}J0LTAQtegfC0u`-7{3%Gw6kB;H_ zF?@C$pPwQu`_W-MIfAcFDsNdPP5EO& ztge1Yc-P_T*6Fs}pzZ-+QG2DTJ`*5fm(|uSN z#L;1#93`y(^cdbB#f5QPpTw;xtj%J59*-CBd=ak>4%?FoE!6ki;{n9CA1(jE!fHku`xrP3~f0pyqXi|q1wcIhm`I^kP1LdxxT%jhJ^qp_`s ztV388j1?%VL1P1Ux1w_gdUm337Y24?Xb*wG_&&_^VzD2`hjHp4P9Man5qvO=izB!` zhFgblXA)2sGXs4GQuiYku^+RJu|w66Ssqt)Xq)c$ItBLM)5&vUjOFIE+n@T3`EsKQ;o<mcro;r=)tP2%aa%ce(hb%eM+ zo58OR$MY;Se4lz=I*& zA0({*-hSNa#aa(O+J?FmH-@jrJ6IBZrvV_UqGXa6pDHl;1QxoSfSIATF+z#X03*DT z8AT)mEMqe_ZU--U4?k@mFQb>2H^40%=9$O%_DOE(oS z1Di9tkhB{y+Yng~Z8gFwkXQjr4YoC)qZ!?;=-Gz;9T?h)gS#-X2XkFG(u3oDIN6UA z{W#uB)agGR#PT3khKX$T{wN+D!rgJ)7{S#cTpq;o5D~|JHHYt)@cTvlegWSv;I|9- zb`HOq!WHH9u&iueXrJVijQZvcdS-UJC+~KP*@mbFXloE!f$%b9 zSE8mCyBpEngx(hPw_cr75oan`|ZXD^tdp)?+iyQs8Ie^tc+#M#S z^W|Y&8^W~#eA%&+Zz;YL^ zbl`d??sQ|l8|z)TzYq61aAz;>?7^j0G^aqo-14ril->Zq#Q?EPh$=MrC<<)69t{Vj*@~2PdRV6}85myRlHQMUX-H6^s^fh6q1*6+Bu@iIcSlELj z9XQ&F!<{(Xf%iIbsR!44akCGr{a7Br)dK_o*9UNQKR)fo`MtQb4-W_N;u@J}D%#PpAN!-Or-OJ7F<`N7H z@hxK_$FyJNlCtWEvhr|1#iFcqPGq0tmy9v;2dU}%JQ7`gK;MAyTBs`!T#ATNWLKb} z7M=CzZNUCU3^d_jE2ef}whfCrakw2v+OgD5+??5qPdadIAFg%bLN_k<5!QcW05|sI zdJisj;=*o1%lq)C4__R>Z^rQ3aeP0HZ%6U<2;L0gvVGC1#8IcNibKN=2Vr4WT5^@ARA*FO}1tX%GsoTmj zG_Yb?xbfRLNko5uo7u(7?c)_45L(7%rL)S)#o+40s_Mha%0+qEoYX!kC>dqt4)~^Z zk&;{t=o%1SO)#KzBE*iQGE`JyPc3@tu)iKdjTmdjWGm*jV_^rDb`aP39XPxTC)@F1 zJI?LF`+IS=6IXhOjP~Y!+~~n_C$8_ldOL1+;7Jd@K7ijI#IHy2!pxr3))PSn0;fKCJA;&33}3 zFK)+2EjZnXQ+2qs9k2WF+aY|@k2k&etOw8b;n7~)-;32fxUmygT5+`**PC&(8EehB z(}erYxKxeZ$q=!&Z1N<(BbmSk0DmeLqLVplFaLBOWr$mD0{h@8D+j@C%0|<}tZ_DyVEuRWTo2u^3#rpeUd7b4&@$2MGo;_K}iy zZ;EMk)724mp+7JXVnw%y=0#7F=({)p}g3$BhQ8*5gh+Zq?wV8JR)uKGb(*al{4ye|lUfzGQ~N%P-X@ z*zBva(bP7&#>v!_F~X{t5w$E`9V5Dt8Qa22*uhF^XQ%JwWc3L02c;#W0rp8{>5Qs; zUR|{qRJ{;bG3#%i5}U_(`2&osZtvvX9x<((BJ14WGN5!qW`)86qYYLEc9o&89K)3u ztHwkPMyoMcf#E7l)?l^{^Nm<)#*r2rZNbTHIKRuqz&_mGgVi>|rf==UjW%4}h6_zN zU4vs)xX_4ad+@7neA$UtyYYNC9`DBeow(DA+f7($#Bx2Z*5YyvuGC_=1}inVRf&V? z2p70hDDMOV8vy)=LqnpHXflc0{5Don8!K%u zC#y@4*Dom=4zP?X9aF*Ob3v60gdZrI_jk@nE#rLC0ZvwrPtqPr%yti5gS)l{Ar%O6 zKyHPf8Np_xTTyLCrxQbE7%9hKIrf(lI#7l~RhX&4{8r4@VW|N}8gZ%x=XT)cE@DPs zYs2krxV;@KJ8)wMuD9S)13sw6@iLsR#e?nmvYpVYU3k6=4|m{x3+^;vwE;J4alIOs zt8l53(3MKusK7-l+M?ml@^}|Yq8kAGXQSgm@$}|V_&RSz9!+g!sO>bhgC1JS3@c|w zRx_fu()A4tV-q8`nUT1IncU7!>l9}7N(zVM=Fvd=WKh{$aQS>t<)X53F`#tL&ps(C z8s=s9F;n(>$G3SJnl^>k68(YTQpjzPSRg5ZwgmYWG}+PZK))0H4)od4Yr~)u;}w{$ z!b}zBYOuH!hwE^xjtF>eY{$JFxZ8p|EkrK6+KO8(xYdj+b@;FhCv14%i92=pd#hH!E?y5?9M{xg3{D30<+{!y-6B+-UDWUu*+_KQ~5Xk{6rI z3GtF=(1J>QR5ssG2TfDT2&-U5R52oJ==wTFbUiDki4nJrk+hSYx|f&LCC=~nFCJFf z4h5CY5`Lg+Nm;cRP%-Ckn-CX`@N)Ju(>lEqc9No-JtDTcX{y{*rBK@8XNA;)z!Jop zVKZZ=75mNDZ$YmaJtY{hVa$oCGR#z9t^x~HgpSnUY&~wa5CGh3!rf-vap`sgZq(v( z1wOXpj0GP#2wQ*Ij5jUB^=Shh)nUCBcdD>bftzJmF2ywmVfvSB_|%F|EjW}4gLsqg zFFhI90N~G$k%wzDF)NTFP4-b1`?_KwrpCz(D`!MhG9s(#x>|;zo*C1~jB8;KnelFJ z=00)m{($1)K>LKUVm7FHA*g0aQL_|SHcK#2Ji^cIXQu7*P2A-b-Qp2dw^>u^rY=K} z6AC-QfTS3~MaU^aOA)$C&|iX{VssZ{z=}}^CQFHx3p3@It-yQ*ma6d4R@|z`gLVCHxg6+N;Q@%aMg*6Hk`HKtd)rGU)1AG176nQ=~g_d#=Q!xm15P2n|3T)am|9O zW?Z)5vKi+Juq_Hf?9Ifl|8tw^1^_?*NRhoszGA8*i5^(YRNELDJ3~t_zznZsL{`yt zwQT)XR&+f*wuzarjh(!UpV1-C?ei}l4zM307^qnasyPz$wmfQ^loTHn}&{ITQ`-*YUibFO`IWXx;oPahYB_F}V%dpHc6@9mbi;|~)p%Wl7gcy#frq77ci@f< zx2?Ek#*Jbu7vWkFt`y-?Ax>q&5#+}6@%RM?LK`07FCHvUqBO(}r_1xWL1vc9LJzSs zG^H$U8ADr653gcG)zEacG<}_~v4Iia%t_k8OYIQl^vQ~c6}E}M^0~n3!%Ct}wh&M{ zEwheGiw+2K`#2e$tfY2YOe;y>=&r3rsEYyRj|_;5pejUi0jl${w*Wo)=*q+XLJXH+ z+=599rmdK^VaA398_qbfT!lxqcvy=EwYXP~yQNrjV#R?QHe4~|ya}I}2t$A7#AoGr zQHCc@JhEwgf>QthAOJ~3K~&4rwR~L3!)Pp`1@0RF{QRW; zJ&1U;Rw!MbCk!YQC<@t1Geboz9i?lCEd;cP3R)zwaENB8qsKHd;#ydVZQQiI;+#JJ zl3|5oLQy^!NF)Ob%Cgx2`=p6xMufjMYvRei+O~VKe6DI9WSkTQi4ZCSU2HL0dD7GB@efVmKe;g*a4*2@@twm@3AMndoF) zb>dMOp1NlD2WH$W!JT5PnurO0xd4~*a4843^6=P%&x-NVgr|jgl#ly)xSNZ$9Nf;t zN*ZpZ;bt1HrDEQIB!T-cb&=c#06%5Cs3cY(EwE6aw2A_XM1Hw^Ne)kz%MK`HtE|K> zg0M11WED$a!-}qB#1IUyiCCzOpV2AJACQ|z70yXT`D{ShY=Cpx-#YGBJR&I^66f^` zvbs6x9n8dC-m%+Bh6ay_t(&zqZlP6fsxmjFgK!1%5;ti9BzchM!Qk=+HQ8v-#$YZ+ zb1{~O@q8T0$5a8P3vkqgPt91j;;9*ri(C_YA@1d4EgviSxRHnJxww`|O!Mp6c$SM7 zd3ctCM_G7~iS=~cO~dVEtR~@B0&XPWYCPJMP|!C2BDc$I0Ps`Bi$Y=r_y!dTLY$&t zi!i_>kmd3vnLJSjSC+?8mM}vdbYdG#HOo-Timqi6<OXl8T2ZxSxzW30RBAY8+N#u^Nj-Epi2J6!I^y zEwBN=PZ=*Vi7xXFHu1G(q7a8L*dholA{Y>7^7!d&Q6@{C&rn(E;S~&B4NF(U)YY)` zTNyF+%!C$BdWR@?zpP|L?qVR&F(tPh^0$o1ibwoR2PFCZ;=DdV=00}nUPi($-`MS5 zhGtS!{g&{pn>1Bh)McBMHur!M1Qa8n1o9Hd%m}c!1zFtn#mFs0Q!e_la3}+l8JNnz zWF{uEu#k(7a|xq=nS3EQiyXm-_ircBUnT+c(xMsxdI6O?iizK{A#M3xD zip9DSca6Afz*-bmqi`hx`vM^H{soqUHURhugXKZ?q=~(Q^7!G^5^bfJ@B=nsU@_l6 zpD)Sc@zc11G>%^mOIgg)l(LB0Xbn?e!!*<~jSZ~io&2nBY2l#UGODmo20Eq!>=Sb9 zxXe82S3Dv$4T=l;#W}tF41xh>Lc4EV8`aoK)-`%Y)NavKcm~_I1e)CyX174Io6?G4 z8-g9~O2_6v`=(Hvdx9CZLTt}Peg2ySu9KW87 zdx@?&J_a`pSk_@hkM$Tljm7h5JTu_29uIW5ABnr+Saa!UFyh(nzq}}D1Aw0bfG5eD z>#fY@>uM!YwNh=Rh;Riqfzr&E7x4UYxWWvsFr6z-=lbPx0!vsLCp)r|a0U8WW?VBb zZI38_zpVIx+&-cB5deS7xS#o;U(o?c;gF$ec6J%y;oy@2Tma(3bypxyPD>V(utcT=|DLDbagxogaZyED18SyI`mKF_5 z3I>EZeca40c3LMZc{e?-%{#h<8o8CEDJKV62>_H9sO(UeBBY#HN2IBQrqWGQxjDGf zGqB7644i6%6FC2HYxUI#C z2Dd_SOM^S1SP#J?6`_a0c&NmBAZ{zr$%lyYORI-&0PxcQpfV`(BzAPY*w`dC)Qj}B z;)p7twpt%wuxf zB;f(Cu()tgnA^)I80cUpwKL+j`|27gVO3rs4yw|!CD7s) z>;m8i>#sss4Z>>Nv^8#;D)-RJEy3km)TNuE>~2}bsLn-421b%G7mrhMM7aN0hu2Yf zrNfITJdePWP{PU|sBu38_tdx(gcT)Lg0P~%nm_Kya7T_cIaVaNA;b|bN@#9AzpTTC zHvsr4quPo*%Ok8;$SC+#S zXAp^jAeAFd=cvrQ=P#i-0hXBtM6a3mI2ba)nyH(_|K#Y-)otMN1tj|1^A2oDsvufSayVfw57SdrqE z5I2RmA;EP%t}<}Z2g59c@H~D=J%J4Xei{H28bub%GS-XZS|u^flDB@qP$!J65rkI? zG-doyCojZKkf1E#Doi|iK1Y_zk!EuRDI9SUM_5&wV_*uqfmT|dlBA|38uxwW0 zoDLxBLK8CcxU6VYnm;JY=@Ddh@>2J(6WUnDX5Xl-zM3-MATw22vL(<=G{{uW9~jWO z7zlR(5MJvRR^t{{?G{$)9$K*}xZFco>Je(+lw9I&E5MF)j3(fm9uKtmLWM7b@k)so z0eI$*r~Y{2her}T5MW(^yF%O%;Wh`WG~8m~1`{`Yune51qEYQ85mQJcA{+SG&36NU z|9Jq&eny6|P8_#Q8rLF?YZk{eij9q;=sJO}mLFLyh^P{TRR}diV!%_GdCFpLU=dGV z$P=e=xZ!klE<2-BTzEiQe9({Z1LFbC8D;5gpmSE?oRQn7{H+87egy}_Ieo&ceS*|I zyo5Hkv58o89ac#TvQiaATNEX3fflzQJF#3sRYvTx53MGosdjmTYGTW!rW%?mx6sN> zstS*w3Xh;NPqo7%#_Vn?z>W+|$74ARpDFQ`3~wa(OoA7FcrL+HJ|3|Nt#fdXg}Xjj zqvAFRD_d}D3vRmO7I0Jva|(sUqy92Vq8lFIr;Qicn=A^a>uX&AY?H)o7ss}UVh9XG z(e(mDozPGx)YS?jYlPZzA(0r^xWN{VvV<#1W77kC{Pgt1ZQ}evKhps}(~zHe)Za0q za1sCz3^)h|WY!6pX+)a4U!2t?Oz+?(?PA9?GYoaCh-!w~MwOd9{7st_CGLS%_dpx5 zp)9xz!4*(dAfytZRYXwq0}oZuRBR5Z@K9HHs0dd;3UrW^_AL>XP3a~yWnwNC_f_~p zfG-(%&BkY3ykO#)FP?hgu@4?nuU>tMn}c@vYLBAHpGtu~A}d5JxwNjP+uDjX0u0q%9K=L6ME` zm(2DRd2tjz`f`3=zbt=1R?sgkJ}7fcD~Rde#eic*;g|`qO!*g$O7jN9S>3{nPC?3U zZhR{{x`7o{!&KY6<)$sNLU;e-O$y6qg=KS~ZIjYLY$Q;YA-J4aMHf;*DD>^Md`oD# zXGpn6NSUYFNme;2!47huousgPs4Sb~P43$=a6*q~JbdMeFUWXB#!D}}AmjOFJOLhW z!Xr041lECjaADwv+rTw31}c4GGC00m>d$V$8vy)I4;fADrM2;*o5jXfacrwNu2me@ zB93h)82C}*m_|u-y;#3h6j?2bs1$0;1kyOB7mMo6p{g?3nO%O_y^`!6ap91kZBpTK z0t)9`pmRoHp9-)}`WKB!TxozPqm!SyN06|cAKl2&)v!YCG)a>qlHaC$Cax;>P zFqDdGDtu1H*T5IRYd5?Ao&(Q-ryxjp1U&c=gSRAHNW@Cl6)eZ5FmD-J6a2*PP3 zZ!+17EDU4BH44)@L>XP8y!}%1L82sTn^D?l0`1cQ*2w_tq`ZX4gT*<0qRcK~+FoHo zo5$hKKYTsH+-iC(hO^)w1UGqI||Xm_hJyUiuymLFbu5)8ZnUI8ybknqgK zz`tPNhWo%89q(=PY8elRD&kXVlnnsh6?wP{LH;SMh$^0;QDkfs$25y#2@-^{Eh2(} z?UK0dlKAb?*zJiMBgA%7xS6^#KtO563LfGRp+sj+63txqO5Ll{-A%!Sb$|R z&^i%l9wjNIq;bqfx%mEKmoVDSXH|nJ1*OPDkLPG>{{T6RDZlP>Zm`H;yp zGVz9FLR-kBEuJL8y?BzyR00ey7R8%O_7ReOr6iiaCqwDM(s*(rJb3yof;bOR@)mKr znz34yK!NVH>{B=v>D6$kqfC!(4gQOk|2d-D1OQ|oGTECx>0B(!XbgNNf>SyaFsL?^#V>r1n{rRjS`sk=pKdnFm2(zHF|#2tdz7M^}9PpqR+859Bl zpMMPig-jw7?uA78(?9SZZaiG_B$LQAvJaO`^CQuNwlE{zdGT(-H24=HvSxFBFRSTD z!0z*^rgOnnhh?=R>^&`>hw|OeDzHK(qM(;93_f!skXUh0jz8DUzcNlfh}?ZPG`U>F zlz4Cc$H@ma0Qjp0j!dGmC>)Kq+{h2fmW0~bno63siWy$RiKyj8)$#NVJbe={s+up0 zVS2GB|MqF)L?%u3i)9)c1j&0uDSL(KoubTr;*4%_W)G40rtJ|W?Ie`EOPtguN!%uk zYZmA#_yP@$?E4k~;!5%&te-?7yF^$&0l=S{fPcW_kN=7Mqr8aUh3-#c>)mqpX`Q09PH{@RD5*`9uw4|t zO&H%QOlalDH3`BjT$aB#G1+^Q|Hb-$0N@Af|I;481;BsyyO2m^A2Kb#lc9HG$HPB= zlfIUc*Trr;OfWEfE41%Q@ZR%5E$;>+pl9897Cx-W*=`xF6DOtR0Ac zSei1H*hr_)h=(lyiPySTR!d^&`mOA^?cDgC1O|!i!o=Oeq;_#an;?FhAfZ(l+aio> zCZ>O5Jzts5@!@!RQhp46ewhCMA^`q844%Zn2z2!pMhrNKP!w#6uJ_LBU{y^?x<64J zycIffE2RIby6d89&w16}i|X#{n!%fzu@&viy~tzF4X0ik&%807colv4nPKLUeq=SW zayT$Fnd8eQYF|IQmH#K&@Bsg%;S)*3Mx7xhI$9gA2+rXuEj~)Scd)}J*h=?HrTL2g zv&x_cl|++JR7G^+;F)cl*d6@1Hh$a=e(ZLBObaijksH^{i*4e^HVKW5{D@MnP)j3w zlRYRz9riy0;J=iBQb=AxPgc}scDx%m32Lin{5D$pZl?X9u=8Bd!4>W3isry_=>E&< zo=;VS%i4+i5lb%(CqIul^Csr>o0v0ijHlm3AOFm-^h`hh#BktdWc_S#L?+jp`FGtW z+W_FdGJNC!xjZ!`IWsLiE;c4CIwT-kC^GtrV!ilb-abMK+3U~5!6Y9NSL3ZR({+t3 zLo3U;jUCgt0ZA-ho|h+uMDilq$z-oT0lx~&qHNX*pZt#Nnj^KlH;bUC1-Xp()Th_cG6A#oNXrp_FqO`T5J|`VP0;u-oWMZ$baZb z{ly}lKTahv*z`zUcv?YRk~KWa?5`~rs0+9O$xMO9$A?cLf*%r@u=uwVKlR@w0Dr5= zl}z%al4%kWCwdb%75p^F3*FoimnH{X;Hb(9~j&oGhxa7a{v|{gv%8rkM_M8pe z_i@nvOCf_-LI*xo_g)MhysDnM6TbLFxAa6mx2_wy8kW~AWeC0h4)@T!V*rrJMASj0 z`pINQgCRRBGcPwcD>E}8K0ZvVRR#t52Ly;E5;lk9OQ%!4z28#d@kb(#3h?$8i^V#f zt}s8(Zncy-?4@>FQDJ^!LY&OckLu<1hyRX7r;Ej6i9|x9)Bo%b#^do)5)#cOQ(-|~ zQc7%Obf{7-=kPdW>W_7#A13>M!@%Dd0Fvu{NK`UiPT?fDbJM^{gRHznv|~5J`aTYI zcKQcLGbuhB9^hxSema98@$*wCmFkd?g!uTP{CtPqX1Cc&ii&b`a?(;#Qd3gWQd5$X z5@Tax!XqMrgM(x;nMfoS3Wa=ufG-dTL?WrwFGQ_QN=z^n7C7y;GN+@mthBPsX}4Jl z3-aP(W8`u)$miHVId1}GF>K0g1k^%E{dp-|-IX4@_15|b%CDa8;T zr4Cd{1Y#dwpSKg|+eYsX`cnnazhNNcNg`1g6lMsCo8->R06!i61?~y0zUE2c)3gNWI?;ghRjgP&?D`U66e zI8+}BF@yY_r2)Tu58y!}F<7jy@Nlcm*3#0lb?a8A!(l2cEGj6lmXs8mOc`nE@v(8K zDJjLKLc7)Muv+X^i@CU{upmD-J2NXIJu@T2bxqIC%E-&jF_{Xj7PH-Eb=s{?yUjsd ztz}Mod8yNBw-w~&CMPB)CnRKNW)u|`6cKNdoe&o<^^;Pl)PGsD^GA7z2Z_vLv!nI8 z5|hbpvy>E>(o&LR42B4;CRC-CN+jOC|0;MO4E;x2cYXc8XWaprI4+hMOyVbP5@dj% z0eR7uxE7jYTwFElS2iiJ9Tb}Sd3n8T>xi)Vn0(-hYJ62YeLrIUVbtL#`s2?H$DSLG zJ~J#miX6KYw(W?@SR$bFy#9(@>%UdMd;olCG`(J5Rax27-96CXzpb^UtkjvCo0F5B zRa97zmzxtG8*9)TGSbtm=Hj;jz0G2_TP+TorPOXMb=b-r_A-avX}8&}7E4J{u_@nF zkZ&r;GZo|)HWlP#XJ@3Phlhu=*zC92 z_7)Gq^vM*8w~r5##T1K#TCFA}DY3XP-(j=??%i$&>eXab?k-y#7pDx=Z3|{x{3QyEr(Un zMLZhcYs-HVv453*;Q&y)sDVmlWkp3(V?$Y4so8AK&&$b3Pft!x&dkir&&keAPs>P6 zEi51`&tbD#ONwn)!unkRSe-Vj!)mdbi-~F3lj7r(;^PwI;}YWH;$nwj)NlHpjP0h{9G#8nOm)mVNa|sdD=jJ3QCkF=yGZ;)3i^UfRq*AFeNU70; zMjLcViScPE$;pWcru^Jehn)Z*AtByqj59{Z8lz*Q4UyqtiaL8VZ%jbC0d_28KTU==TZO4#GqtNAKj=_VU?#|CbfO%7VtG8`Tv~|gU z>!Q4B*57_eRCGX4G{~=*lJ5CXIkK#oz8A6ZF!JzI-LV(?<1eF+J~u2p){otZ+IB)6 zUn=A&dYqZ*^sL1H(=)}at%#4ietjxlK zyoS2Eoo#J(wY8R#;)2{vf57v8hRk8R@B68R^*>X=$m+aj`M+u`wm4!g8mhC_g_XF(Dx~=B)_8^g2VNE<98l z6cp$m;1>`i(}bxLW8*VZ(sD8~v(nRJV~pCcP`N_J;c=+mUVqziyN9bt<;A10wPbGW z7Jiz$U%q>EBcW+@R4fPEsQ(<~~YD#i)ejdSqsUR;e zJ1a9iJta9gAwE7XCMH_1i*(t#R-;j?R6#+(N~JPTp$G^Fh=_==SS(w&Zf$C4tlzrT zX(ufHtpjn|tYvna!)7%X7Zn#26z1jTp}5BlYpov8hSP*_j#nxj9Aod4+koC8i>q z*;;HWPD)7%2$K6Se76ugQ~t(7O7S843aM-jg>CQ@rfv!=qZagXt7iSS9`>(Ukd;qM ztweszEgIz4&dK^dRZZQ~&OeM?dJ=W`nf}N#{rqG7p?gsSH^O&)q{-PS^NXTWd`Me< zZaewK0`OKG4ks`$kS7pOyu3(cGKoy4dV7l{5?xeOa#E7TVyP%EFDNKTNKA|&K8*44 zap`HPS?TF%DJjW`3Gp#8(K^EJwHi&3GSFWp6A6Vp9*@an`qF4VzP>CDCnPkaxVX5p zv$K8Iu9})^hs~awn^TaN=gJ34iVN}!a}x&e9zh4{S8W+~20Pfd!Ck26La4LZWZgoTG|wc%l5;aY8kCNx5; z)kQ?cL>rP56Ef3Nb6jb9PIgvdK|yg*v8C8jkXsO@4g2$j*S|_63Yp|XA~Q(d0tzdL z;-BOlTf?yqi7O|h)pLH8b5h5IxM)aF(8nvE6z%#zF>)hx=6?A6qsYaly2U3^GY=xi z*24OiwQXlZtOw+pOg2sUFID58MZahO$VBIeLLoLx{h^BTwhtf_3KA0&91eSSPEL44 zL}X-SjL{fFlq_Qs;$q`sV`HKX`lu*PXsAM=5Q#)A7R$@qoAhH1=WX?d>g^pA9Bi}M z8tUtv4o6;YZfbIBLPBC(e0)|$hS^kDn46oPk{llw8*R|(q9P+*R4EP%E-z{&InQ{DPI2#n{>&9YbCO9*NSeE7l|(R4oYOk zmvc>BV#lboY*K0;6BhOJaymKrJ>1F(zwJi?d(NrHZ)v9Qh0Q#On7SW6zNQ_x5!Q80 zQ!}qrrLakqpR+&kiw5977M;nAi;F8RE;bsCQa?XGnJhd!+(5+lI=#zQwHi&JLc!zl zd}uVPmlv5rbYlPb9Y|!dKqyR0O|7h`FclWY5+-PjiH?bOeI&-mrzIsN#m5_?4N(z< zDTjop0|ONjsg%d(^Y}a>304MbwVG(XJ|iuy%wel4bC#G2(^FH727P3hRuiHMQ3b1m zgMyWTfdK(>nM^8?NJJuuL=qsE2L%SHgH>9!ns5u+urQ5Q5vbsc_CE^$kj5Mw&RWrJRIHPIMV3(##6YVyfa8x*Tp! zow#;5VDBka@5dp1pM>oDD75WNh@~$;u4De8r2DhzT?D{`L}Ibov2k%3>FIL0oJ^rm zyu3JEu0o+utJNyCIv_AmC=~ko_>jot|7e*I#mg%sG_<6oB)^~_Osn;m%l+l@z#yec zr3wjAhlgn+!oniLwP6}fh+3^sD0qCnFO5c~QhjK&kdTn{jP(4xyrROw@=`~2B{BDx z6cwZ-Cg^mLp`jtF;2=eS+)pYI2m~AsheoG)dwWy8yeL#Eg-WH-Xaa%2UoKNB6u|+? zAbFrzB=%+azO_p-mF(^7<3snQc>RRM9iBfOfFD{~6tV}2yxG;$AbF9!=oDWzl_Bt= z3%z{UREjUzgX~H2BKa~Y0)L+%9V0x2qyPWgJC~j&q9_1w=$t$6SD&;+T8p%Rks^ea zL@Wg)8X*;g5+BhOo3h~lugr9KRD}hKjL~!bvx@Xn~U>v^_g0=I5`=Hq2oA)WkszJ`c=nXe6^-+`k|l6X7l-cCX+F3`>He+ z?N9#If3s7+64Qm~jO@X3PnoLYYSFYQ-1wSuU8&fvd#UERpt^25P7nm?bm;lf4l4dDnuz1N?NqeY++`4&QMC1Yy|ic27=D zdcEH3VY|fP{)fHyZ`azbxq3a9%UO;+nv?%&=t@g;<9>%h0{l63SgaDS{*npT*cY_(detEahh0aDS{dkuaUERI7zTbjB^0i__E5GuTqG==^*dA%p~Y zloA-GXyDP#2)SPftN;l+$EIHrw9 zie4ZjfPkVI4hbNj=mtds2q@a2kpKdUc>pAUfMOs92_T@D2txu0C`KZY00N4cU?hM5 zL-9zU#yt&qD@5}5fDjS@2_PV>07w7oq5D*GsNB{w$5{v{85LzLS00KfU5E4K@Xof=q2ngMvNB{w$9U2KBAj|_G0R)7B z7$kszFcF3X5D-QpkpKe1OfV7% Date: Wed, 11 Dec 2024 14:05:52 -0800 Subject: [PATCH 10/11] fmt --- js/src/tests/client.int.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/src/tests/client.int.test.ts b/js/src/tests/client.int.test.ts index 5ec6bd2e1..c8d92aed3 100644 --- a/js/src/tests/client.int.test.ts +++ b/js/src/tests/client.int.test.ts @@ -1251,7 +1251,7 @@ test("annotationqueue crud", async () => { } }); -test.only("upload examples multipart", async () => { +test("upload examples multipart", async () => { const client = new Client(); const datasetName = `__test_upload_examples_multipart${uuidv4().slice(0, 4)}`; From 1218bd9891cfa24f18560b978bf7d97b21751d78 Mon Sep 17 00:00:00 2001 From: isaac hershenson Date: Wed, 11 Dec 2024 15:42:11 -0800 Subject: [PATCH 11/11] test --- js/src/tests/traceable.int.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/js/src/tests/traceable.int.test.ts b/js/src/tests/traceable.int.test.ts index 80d6b5830..79815c5dd 100644 --- a/js/src/tests/traceable.int.test.ts +++ b/js/src/tests/traceable.int.test.ts @@ -671,6 +671,10 @@ test.concurrent( const testAttachment2 = new Uint8Array([5, 6, 7, 8]); const testAttachment3 = new ArrayBuffer(4); new Uint8Array(testAttachment3).set([13, 14, 15, 16]); + const testAttachment4Content = new Blob(["Hello world!"]); + const testAttachment4 = new Blob([testAttachment4Content], { + type: `text/plain; length=${testAttachment4Content.size}`, + }); const traceableWithAttachmentsAndInputs = traceable( ( @@ -696,6 +700,7 @@ test.concurrent( { test1bin: ["application/octet-stream", testAttachment1], test2bin: ["application/octet-stream", testAttachment2], + test3bin: ["application/octet-stream", testAttachment4], inputbin: ["application/octet-stream", attachment], input2bin: [ "application/octet-stream", @@ -749,6 +754,10 @@ test.concurrent( "application/octet-stream", testAttachment2, ]); + expect(runCreate?.attachments?.["test3bin"]).toEqual([ + "application/octet-stream", + testAttachment4, + ]); expect(runCreate?.attachments?.["inputbin"]).toEqual([ "application/octet-stream", new Uint8Array([9, 10, 11, 12]),