Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
isahers1 committed Dec 11, 2024
1 parent 7b2e73d commit 347198e
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 124 deletions.
225 changes: 107 additions & 118 deletions js/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}`,
}),
});
}
}
}
}
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -4370,3 +4270,92 @@ export interface LangSmithTracingClientInterface {

updateRun: (runId: string, run: RunUpdate) => Promise<void>;
}

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;
}
2 changes: 1 addition & 1 deletion js/src/evaluation/_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
2 changes: 1 addition & 1 deletion js/src/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export interface AttachmentInfo {
presigned_url: string;
}

export type AttachmentData = Uint8Array | ArrayBuffer;
export type AttachmentData = Uint8Array | Blob;
export type Attachments = Record<string, [string, AttachmentData]>;

/**
Expand Down
13 changes: 10 additions & 3 deletions js/src/tests/client.int.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)}`;

Expand All @@ -1278,15 +1278,20 @@ 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))],
},
};

const example2: ExampleUploadWithAttachments = {
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}`,
}),
],
},
};

Expand All @@ -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 = [];
Expand Down
4 changes: 3 additions & 1 deletion python/langsmith/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
StrictInt,
)

from pathlib import Path

from typing_extensions import Literal

SCORE_TYPE = Union[StrictBool, StrictInt, StrictFloat, None]
Expand All @@ -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)"""

Expand Down
Loading

0 comments on commit 347198e

Please sign in to comment.