Spaces:
Build error
Build error
| import * as uuid from "uuid"; | |
| import { AsyncCaller } from "./utils/async_caller.js"; | |
| import { convertLangChainMessageToExample, isLangChainMessage, } from "./utils/messages.js"; | |
| import { getEnvironmentVariable, getRuntimeEnvironment } from "./utils/env.js"; | |
| // utility functions | |
| const isLocalhost = (url) => { | |
| const strippedUrl = url.replace("http://", "").replace("https://", ""); | |
| const hostname = strippedUrl.split("/")[0].split(":")[0]; | |
| return (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1"); | |
| }; | |
| const raiseForStatus = async (response, operation) => { | |
| // consume the response body to release the connection | |
| // https://undici.nodejs.org/#/?id=garbage-collection | |
| const body = await response.text(); | |
| if (!response.ok) { | |
| throw new Error(`Failed to ${operation}: ${response.status} ${response.statusText} ${body}`); | |
| } | |
| }; | |
| async function toArray(iterable) { | |
| const result = []; | |
| for await (const item of iterable) { | |
| result.push(item); | |
| } | |
| return result; | |
| } | |
| function trimQuotes(str) { | |
| if (str === undefined) { | |
| return undefined; | |
| } | |
| return str | |
| .trim() | |
| .replace(/^"(.*)"$/, "$1") | |
| .replace(/^'(.*)'$/, "$1"); | |
| } | |
| function hideInputs(inputs) { | |
| if (getEnvironmentVariable("LANGCHAIN_HIDE_INPUTS") === "true") { | |
| return {}; | |
| } | |
| return inputs; | |
| } | |
| function hideOutputs(outputs) { | |
| if (getEnvironmentVariable("LANGCHAIN_HIDE_OUTPUTS") === "true") { | |
| return {}; | |
| } | |
| return outputs; | |
| } | |
| function assertUuid(str) { | |
| if (!uuid.validate(str)) { | |
| throw new Error(`Invalid UUID: ${str}`); | |
| } | |
| } | |
| export class Client { | |
| constructor(config = {}) { | |
| Object.defineProperty(this, "apiKey", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "apiUrl", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "webUrl", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "caller", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "timeout_ms", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: void 0 | |
| }); | |
| Object.defineProperty(this, "_tenantId", { | |
| enumerable: true, | |
| configurable: true, | |
| writable: true, | |
| value: null | |
| }); | |
| const defaultConfig = Client.getDefaultClientConfig(); | |
| this.apiUrl = trimQuotes(config.apiUrl ?? defaultConfig.apiUrl) ?? ""; | |
| this.apiKey = trimQuotes(config.apiKey ?? defaultConfig.apiKey); | |
| this.webUrl = trimQuotes(config.webUrl ?? defaultConfig.webUrl); | |
| this.validateApiKeyIfHosted(); | |
| this.timeout_ms = config.timeout_ms ?? 4000; | |
| this.caller = new AsyncCaller(config.callerOptions ?? {}); | |
| } | |
| static getDefaultClientConfig() { | |
| const apiKey = getEnvironmentVariable("LANGCHAIN_API_KEY"); | |
| const apiUrl = getEnvironmentVariable("LANGCHAIN_ENDPOINT") ?? | |
| (apiKey ? "https://api.smith.langchain.com" : "http://localhost:1984"); | |
| return { | |
| apiUrl: apiUrl, | |
| apiKey: apiKey, | |
| webUrl: undefined, | |
| }; | |
| } | |
| validateApiKeyIfHosted() { | |
| const isLocal = isLocalhost(this.apiUrl); | |
| if (!isLocal && !this.apiKey) { | |
| throw new Error("API key must be provided when using hosted LangSmith API"); | |
| } | |
| } | |
| getHostUrl() { | |
| if (this.webUrl) { | |
| return this.webUrl; | |
| } | |
| else if (isLocalhost(this.apiUrl)) { | |
| this.webUrl = "http://localhost"; | |
| return "http://localhost"; | |
| } | |
| else if (this.apiUrl.split(".", 1)[0].includes("dev")) { | |
| this.webUrl = "https://dev.smith.langchain.com"; | |
| return "https://dev.smith.langchain.com"; | |
| } | |
| else { | |
| this.webUrl = "https://smith.langchain.com"; | |
| return "https://smith.langchain.com"; | |
| } | |
| } | |
| get headers() { | |
| const headers = {}; | |
| if (this.apiKey) { | |
| headers["x-api-key"] = `${this.apiKey}`; | |
| } | |
| return headers; | |
| } | |
| async _getResponse(path, queryParams) { | |
| const paramsString = queryParams?.toString() ?? ""; | |
| const url = `${this.apiUrl}${path}?${paramsString}`; | |
| const response = await this.caller.call(fetch, url, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch ${path}: ${response.status} ${response.statusText}`); | |
| } | |
| return response; | |
| } | |
| async _get(path, queryParams) { | |
| const response = await this._getResponse(path, queryParams); | |
| return response.json(); | |
| } | |
| async *_getPaginated(path, queryParams = new URLSearchParams()) { | |
| let offset = Number(queryParams.get("offset")) || 0; | |
| const limit = Number(queryParams.get("limit")) || 100; | |
| while (true) { | |
| queryParams.set("offset", String(offset)); | |
| queryParams.set("limit", String(limit)); | |
| const url = `${this.apiUrl}${path}?${queryParams}`; | |
| const response = await this.caller.call(fetch, url, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to fetch ${path}: ${response.status} ${response.statusText}`); | |
| } | |
| const items = await response.json(); | |
| if (items.length === 0) { | |
| break; | |
| } | |
| yield items; | |
| if (items.length < limit) { | |
| break; | |
| } | |
| offset += items.length; | |
| } | |
| } | |
| async createRun(run) { | |
| const headers = { ...this.headers, "Content-Type": "application/json" }; | |
| const extra = run.extra ?? {}; | |
| const runtimeEnv = await getRuntimeEnvironment(); | |
| const session_name = run.project_name; | |
| delete run.project_name; | |
| const runCreate = { | |
| session_name, | |
| ...run, | |
| extra: { | |
| ...run.extra, | |
| runtime: { | |
| ...runtimeEnv, | |
| ...extra.runtime, | |
| }, | |
| }, | |
| }; | |
| runCreate.inputs = hideInputs(runCreate.inputs); | |
| if (runCreate.outputs) { | |
| runCreate.outputs = hideOutputs(runCreate.outputs); | |
| } | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/runs`, { | |
| method: "POST", | |
| headers, | |
| body: JSON.stringify(runCreate), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "create run"); | |
| } | |
| async updateRun(runId, run) { | |
| assertUuid(runId); | |
| if (run.inputs) { | |
| run.inputs = hideInputs(run.inputs); | |
| } | |
| if (run.outputs) { | |
| run.outputs = hideOutputs(run.outputs); | |
| } | |
| const headers = { ...this.headers, "Content-Type": "application/json" }; | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/runs/${runId}`, { | |
| method: "PATCH", | |
| headers, | |
| body: JSON.stringify(run), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "update run"); | |
| } | |
| async readRun(runId, { loadChildRuns } = { loadChildRuns: false }) { | |
| assertUuid(runId); | |
| let run = await this._get(`/runs/${runId}`); | |
| if (loadChildRuns && run.child_run_ids) { | |
| run = await this._loadChildRuns(run); | |
| } | |
| return run; | |
| } | |
| async getRunUrl({ runId, run, projectOpts, }) { | |
| if (run !== undefined) { | |
| let sessionId; | |
| if (run.session_id) { | |
| sessionId = run.session_id; | |
| } | |
| else if (projectOpts?.projectName) { | |
| sessionId = (await this.readProject({ projectName: projectOpts?.projectName })).id; | |
| } | |
| else if (projectOpts?.projectId) { | |
| sessionId = projectOpts?.projectId; | |
| } | |
| else { | |
| const project = await this.readProject({ | |
| projectName: getEnvironmentVariable("LANGCHAIN_PROJECT") || "default", | |
| }); | |
| sessionId = project.id; | |
| } | |
| const tenantId = await this._getTenantId(); | |
| return `${this.getHostUrl()}/o/${tenantId}/projects/p/${sessionId}/r/${run.id}?poll=true`; | |
| } | |
| else if (runId !== undefined) { | |
| const run_ = await this.readRun(runId); | |
| if (!run_.app_path) { | |
| throw new Error(`Run ${runId} has no app_path`); | |
| } | |
| const baseUrl = this.getHostUrl(); | |
| return `${baseUrl}${run_.app_path}`; | |
| } | |
| else { | |
| throw new Error("Must provide either runId or run"); | |
| } | |
| } | |
| async _loadChildRuns(run) { | |
| const childRuns = await toArray(this.listRuns({ id: run.child_run_ids })); | |
| const treemap = {}; | |
| const runs = {}; | |
| // TODO: make dotted order required when the migration finishes | |
| childRuns.sort((a, b) => (a?.dotted_order ?? "").localeCompare(b?.dotted_order ?? "")); | |
| for (const childRun of childRuns) { | |
| if (childRun.parent_run_id === null || | |
| childRun.parent_run_id === undefined) { | |
| throw new Error(`Child run ${childRun.id} has no parent`); | |
| } | |
| if (!(childRun.parent_run_id in treemap)) { | |
| treemap[childRun.parent_run_id] = []; | |
| } | |
| treemap[childRun.parent_run_id].push(childRun); | |
| runs[childRun.id] = childRun; | |
| } | |
| run.child_runs = treemap[run.id] || []; | |
| for (const runId in treemap) { | |
| if (runId !== run.id) { | |
| runs[runId].child_runs = treemap[runId]; | |
| } | |
| } | |
| return run; | |
| } | |
| async *listRuns({ projectId, projectName, parentRunId, referenceExampleId, startTime, executionOrder, runType, error, id, limit, offset, query, filter, }) { | |
| const queryParams = new URLSearchParams(); | |
| let projectId_ = projectId; | |
| if (projectName) { | |
| if (projectId) { | |
| throw new Error("Only one of projectId or projectName may be given"); | |
| } | |
| projectId_ = (await this.readProject({ projectName })).id; | |
| } | |
| if (projectId_) { | |
| queryParams.append("session", projectId_); | |
| } | |
| if (parentRunId) { | |
| queryParams.append("parent_run", parentRunId); | |
| } | |
| if (referenceExampleId) { | |
| queryParams.append("reference_example", referenceExampleId); | |
| } | |
| if (startTime) { | |
| queryParams.append("start_time", startTime.toISOString()); | |
| } | |
| if (executionOrder) { | |
| queryParams.append("execution_order", executionOrder.toString()); | |
| } | |
| if (runType) { | |
| queryParams.append("run_type", runType); | |
| } | |
| if (error !== undefined) { | |
| queryParams.append("error", error.toString()); | |
| } | |
| if (id !== undefined) { | |
| for (const id_ of id) { | |
| queryParams.append("id", id_); | |
| } | |
| } | |
| if (limit !== undefined) { | |
| queryParams.append("limit", limit.toString()); | |
| } | |
| if (offset !== undefined) { | |
| queryParams.append("offset", offset.toString()); | |
| } | |
| if (query !== undefined) { | |
| queryParams.append("query", query); | |
| } | |
| if (filter !== undefined) { | |
| queryParams.append("filter", filter); | |
| } | |
| for await (const runs of this._getPaginated("/runs", queryParams)) { | |
| yield* runs; | |
| } | |
| } | |
| async shareRun(runId, { shareId } = {}) { | |
| const data = { | |
| run_id: runId, | |
| share_token: shareId || uuid.v4(), | |
| }; | |
| assertUuid(runId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/runs/${runId}/share`, { | |
| method: "PUT", | |
| headers: this.headers, | |
| body: JSON.stringify(data), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const result = await response.json(); | |
| if (result === null || !("share_token" in result)) { | |
| throw new Error("Invalid response from server"); | |
| } | |
| return `${this.getHostUrl()}/public/${result["share_token"]}/r`; | |
| } | |
| async unshareRun(runId) { | |
| assertUuid(runId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/runs/${runId}/share`, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "unshare run"); | |
| } | |
| async readRunSharedLink(runId) { | |
| assertUuid(runId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/runs/${runId}/share`, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const result = await response.json(); | |
| if (result === null || !("share_token" in result)) { | |
| return undefined; | |
| } | |
| return `${this.getHostUrl()}/public/${result["share_token"]}/r`; | |
| } | |
| async listSharedRuns(shareToken, { runIds, } = {}) { | |
| const queryParams = new URLSearchParams({ | |
| share_token: shareToken, | |
| }); | |
| if (runIds !== undefined) { | |
| for (const runId of runIds) { | |
| queryParams.append("id", runId); | |
| } | |
| } | |
| assertUuid(shareToken); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/public/${shareToken}/runs${queryParams}`, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const runs = await response.json(); | |
| return runs; | |
| } | |
| async readDatasetSharedSchema(datasetId, datasetName) { | |
| if (!datasetId && !datasetName) { | |
| throw new Error("Either datasetId or datasetName must be given"); | |
| } | |
| if (!datasetId) { | |
| const dataset = await this.readDataset({ datasetName }); | |
| datasetId = dataset.id; | |
| } | |
| assertUuid(datasetId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/datasets/${datasetId}/share`, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const shareSchema = await response.json(); | |
| shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`; | |
| return shareSchema; | |
| } | |
| async shareDataset(datasetId, datasetName) { | |
| if (!datasetId && !datasetName) { | |
| throw new Error("Either datasetId or datasetName must be given"); | |
| } | |
| if (!datasetId) { | |
| const dataset = await this.readDataset({ datasetName }); | |
| datasetId = dataset.id; | |
| } | |
| const data = { | |
| dataset_id: datasetId, | |
| }; | |
| assertUuid(datasetId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/datasets/${datasetId}/share`, { | |
| method: "PUT", | |
| headers: this.headers, | |
| body: JSON.stringify(data), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const shareSchema = await response.json(); | |
| shareSchema.url = `${this.getHostUrl()}/public/${shareSchema.share_token}/d`; | |
| return shareSchema; | |
| } | |
| async unshareDataset(datasetId) { | |
| assertUuid(datasetId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/datasets/${datasetId}/share`, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "unshare dataset"); | |
| } | |
| async readSharedDataset(shareToken) { | |
| assertUuid(shareToken); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/public/${shareToken}/datasets`, { | |
| method: "GET", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const dataset = await response.json(); | |
| return dataset; | |
| } | |
| async createProject({ projectName, projectExtra, upsert, referenceDatasetId, }) { | |
| const upsert_ = upsert ? `?upsert=true` : ""; | |
| const endpoint = `${this.apiUrl}/sessions${upsert_}`; | |
| const body = { | |
| name: projectName, | |
| }; | |
| if (projectExtra !== undefined) { | |
| body["extra"] = projectExtra; | |
| } | |
| if (referenceDatasetId !== undefined) { | |
| body["reference_dataset_id"] = referenceDatasetId; | |
| } | |
| const response = await this.caller.call(fetch, endpoint, { | |
| method: "POST", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(body), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| const result = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(`Failed to create session ${projectName}: ${response.status} ${response.statusText}`); | |
| } | |
| return result; | |
| } | |
| async readProject({ projectId, projectName, }) { | |
| let path = "/sessions"; | |
| const params = new URLSearchParams(); | |
| if (projectId !== undefined && projectName !== undefined) { | |
| throw new Error("Must provide either projectName or projectId, not both"); | |
| } | |
| else if (projectId !== undefined) { | |
| assertUuid(projectId); | |
| path += `/${projectId}`; | |
| } | |
| else if (projectName !== undefined) { | |
| params.append("name", projectName); | |
| } | |
| else { | |
| throw new Error("Must provide projectName or projectId"); | |
| } | |
| const response = await this._get(path, params); | |
| let result; | |
| if (Array.isArray(response)) { | |
| if (response.length === 0) { | |
| throw new Error(`Project[id=${projectId}, name=${projectName}] not found`); | |
| } | |
| result = response[0]; | |
| } | |
| else { | |
| result = response; | |
| } | |
| return result; | |
| } | |
| async _getTenantId() { | |
| if (this._tenantId !== null) { | |
| return this._tenantId; | |
| } | |
| const queryParams = new URLSearchParams({ limit: "1" }); | |
| for await (const projects of this._getPaginated("/sessions", queryParams)) { | |
| this._tenantId = projects[0].tenant_id; | |
| return projects[0].tenant_id; | |
| } | |
| throw new Error("No projects found to resolve tenant."); | |
| } | |
| async *listProjects({ projectIds, name, nameContains, referenceDatasetId, referenceDatasetName, referenceFree, } = {}) { | |
| const params = new URLSearchParams(); | |
| if (projectIds !== undefined) { | |
| for (const projectId of projectIds) { | |
| params.append("id", projectId); | |
| } | |
| } | |
| if (name !== undefined) { | |
| params.append("name", name); | |
| } | |
| if (nameContains !== undefined) { | |
| params.append("name_contains", nameContains); | |
| } | |
| if (referenceDatasetId !== undefined) { | |
| params.append("reference_dataset", referenceDatasetId); | |
| } | |
| else if (referenceDatasetName !== undefined) { | |
| const dataset = await this.readDataset({ | |
| datasetName: referenceDatasetName, | |
| }); | |
| params.append("reference_dataset", dataset.id); | |
| } | |
| if (referenceFree !== undefined) { | |
| params.append("reference_free", referenceFree.toString()); | |
| } | |
| for await (const projects of this._getPaginated("/sessions", params)) { | |
| yield* projects; | |
| } | |
| } | |
| async deleteProject({ projectId, projectName, }) { | |
| let projectId_; | |
| if (projectId === undefined && projectName === undefined) { | |
| throw new Error("Must provide projectName or projectId"); | |
| } | |
| else if (projectId !== undefined && projectName !== undefined) { | |
| throw new Error("Must provide either projectName or projectId, not both"); | |
| } | |
| else if (projectId === undefined) { | |
| projectId_ = (await this.readProject({ projectName })).id; | |
| } | |
| else { | |
| projectId_ = projectId; | |
| } | |
| assertUuid(projectId_); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/sessions/${projectId_}`, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, `delete session ${projectId_} (${projectName})`); | |
| } | |
| async uploadCsv({ csvFile, fileName, inputKeys, outputKeys, description, dataType, name, }) { | |
| const url = `${this.apiUrl}/datasets/upload`; | |
| const formData = new FormData(); | |
| formData.append("file", csvFile, fileName); | |
| inputKeys.forEach((key) => { | |
| formData.append("input_keys", key); | |
| }); | |
| outputKeys.forEach((key) => { | |
| formData.append("output_keys", key); | |
| }); | |
| if (description) { | |
| formData.append("description", description); | |
| } | |
| if (dataType) { | |
| formData.append("data_type", dataType); | |
| } | |
| if (name) { | |
| formData.append("name", name); | |
| } | |
| const response = await this.caller.call(fetch, url, { | |
| method: "POST", | |
| headers: this.headers, | |
| body: formData, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| const result = await response.json(); | |
| if (result.detail && result.detail.includes("already exists")) { | |
| throw new Error(`Dataset ${fileName} already exists`); | |
| } | |
| throw new Error(`Failed to upload CSV: ${response.status} ${response.statusText}`); | |
| } | |
| const result = await response.json(); | |
| return result; | |
| } | |
| async createDataset(name, { description, dataType, } = {}) { | |
| const body = { | |
| name, | |
| description, | |
| }; | |
| if (dataType) { | |
| body.data_type = dataType; | |
| } | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/datasets`, { | |
| method: "POST", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(body), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| const result = await response.json(); | |
| if (result.detail && result.detail.includes("already exists")) { | |
| throw new Error(`Dataset ${name} already exists`); | |
| } | |
| throw new Error(`Failed to create dataset ${response.status} ${response.statusText}`); | |
| } | |
| const result = await response.json(); | |
| return result; | |
| } | |
| async readDataset({ datasetId, datasetName, }) { | |
| let path = "/datasets"; | |
| // limit to 1 result | |
| const params = new URLSearchParams({ limit: "1" }); | |
| if (datasetId !== undefined && datasetName !== undefined) { | |
| throw new Error("Must provide either datasetName or datasetId, not both"); | |
| } | |
| else if (datasetId !== undefined) { | |
| assertUuid(datasetId); | |
| path += `/${datasetId}`; | |
| } | |
| else if (datasetName !== undefined) { | |
| params.append("name", datasetName); | |
| } | |
| else { | |
| throw new Error("Must provide datasetName or datasetId"); | |
| } | |
| const response = await this._get(path, params); | |
| let result; | |
| if (Array.isArray(response)) { | |
| if (response.length === 0) { | |
| throw new Error(`Dataset[id=${datasetId}, name=${datasetName}] not found`); | |
| } | |
| result = response[0]; | |
| } | |
| else { | |
| result = response; | |
| } | |
| return result; | |
| } | |
| async readDatasetOpenaiFinetuning({ datasetId, datasetName, }) { | |
| const path = "/datasets"; | |
| if (datasetId !== undefined) { | |
| // do nothing | |
| } | |
| else if (datasetName !== undefined) { | |
| datasetId = (await this.readDataset({ datasetName })).id; | |
| } | |
| else { | |
| throw new Error("Must provide datasetName or datasetId"); | |
| } | |
| const response = await this._getResponse(`${path}/${datasetId}/openai_ft`); | |
| const datasetText = await response.text(); | |
| const dataset = datasetText | |
| .trim() | |
| .split("\n") | |
| .map((line) => JSON.parse(line)); | |
| return dataset; | |
| } | |
| async *listDatasets({ limit = 100, offset = 0, datasetIds, datasetName, datasetNameContains, } = {}) { | |
| const path = "/datasets"; | |
| const params = new URLSearchParams({ | |
| limit: limit.toString(), | |
| offset: offset.toString(), | |
| }); | |
| if (datasetIds !== undefined) { | |
| for (const id_ of datasetIds) { | |
| params.append("id", id_); | |
| } | |
| } | |
| if (datasetName !== undefined) { | |
| params.append("name", datasetName); | |
| } | |
| if (datasetNameContains !== undefined) { | |
| params.append("name_contains", datasetNameContains); | |
| } | |
| for await (const datasets of this._getPaginated(path, params)) { | |
| yield* datasets; | |
| } | |
| } | |
| async deleteDataset({ datasetId, datasetName, }) { | |
| let path = "/datasets"; | |
| let datasetId_ = datasetId; | |
| if (datasetId !== undefined && datasetName !== undefined) { | |
| throw new Error("Must provide either datasetName or datasetId, not both"); | |
| } | |
| else if (datasetName !== undefined) { | |
| const dataset = await this.readDataset({ datasetName }); | |
| datasetId_ = dataset.id; | |
| } | |
| if (datasetId_ !== undefined) { | |
| assertUuid(datasetId_); | |
| path += `/${datasetId_}`; | |
| } | |
| else { | |
| throw new Error("Must provide datasetName or datasetId"); | |
| } | |
| const response = await this.caller.call(fetch, this.apiUrl + path, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to delete ${path}: ${response.status} ${response.statusText}`); | |
| } | |
| await response.json(); | |
| } | |
| async createExample(inputs, outputs, { datasetId, datasetName, createdAt, exampleId }) { | |
| let datasetId_ = datasetId; | |
| if (datasetId_ === undefined && datasetName === undefined) { | |
| throw new Error("Must provide either datasetName or datasetId"); | |
| } | |
| else if (datasetId_ !== undefined && datasetName !== undefined) { | |
| throw new Error("Must provide either datasetName or datasetId, not both"); | |
| } | |
| else if (datasetId_ === undefined) { | |
| const dataset = await this.readDataset({ datasetName }); | |
| datasetId_ = dataset.id; | |
| } | |
| const createdAt_ = createdAt || new Date(); | |
| const data = { | |
| dataset_id: datasetId_, | |
| inputs, | |
| outputs, | |
| created_at: createdAt_.toISOString(), | |
| id: exampleId, | |
| }; | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/examples`, { | |
| method: "POST", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(data), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to create example: ${response.status} ${response.statusText}`); | |
| } | |
| const result = await response.json(); | |
| return result; | |
| } | |
| async createLLMExample(input, generation, options) { | |
| return this.createExample({ input }, { output: generation }, options); | |
| } | |
| async createChatExample(input, generations, options) { | |
| const finalInput = input.map((message) => { | |
| if (isLangChainMessage(message)) { | |
| return convertLangChainMessageToExample(message); | |
| } | |
| return message; | |
| }); | |
| const finalOutput = isLangChainMessage(generations) | |
| ? convertLangChainMessageToExample(generations) | |
| : generations; | |
| return this.createExample({ input: finalInput }, { output: finalOutput }, options); | |
| } | |
| async readExample(exampleId) { | |
| assertUuid(exampleId); | |
| const path = `/examples/${exampleId}`; | |
| return await this._get(path); | |
| } | |
| async *listExamples({ datasetId, datasetName, exampleIds, } = {}) { | |
| let datasetId_; | |
| if (datasetId !== undefined && datasetName !== undefined) { | |
| throw new Error("Must provide either datasetName or datasetId, not both"); | |
| } | |
| else if (datasetId !== undefined) { | |
| datasetId_ = datasetId; | |
| } | |
| else if (datasetName !== undefined) { | |
| const dataset = await this.readDataset({ datasetName }); | |
| datasetId_ = dataset.id; | |
| } | |
| else { | |
| throw new Error("Must provide a datasetName or datasetId"); | |
| } | |
| const params = new URLSearchParams({ dataset: datasetId_ }); | |
| if (exampleIds !== undefined) { | |
| for (const id_ of exampleIds) { | |
| params.append("id", id_); | |
| } | |
| } | |
| for await (const examples of this._getPaginated("/examples", params)) { | |
| yield* examples; | |
| } | |
| } | |
| async deleteExample(exampleId) { | |
| assertUuid(exampleId); | |
| const path = `/examples/${exampleId}`; | |
| const response = await this.caller.call(fetch, this.apiUrl + path, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to delete ${path}: ${response.status} ${response.statusText}`); | |
| } | |
| await response.json(); | |
| } | |
| async updateExample(exampleId, update) { | |
| assertUuid(exampleId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/examples/${exampleId}`, { | |
| method: "PATCH", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(update), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to update example ${exampleId}: ${response.status} ${response.statusText}`); | |
| } | |
| const result = await response.json(); | |
| return result; | |
| } | |
| async evaluateRun(run, evaluator, { sourceInfo, loadChildRuns, } = { loadChildRuns: false }) { | |
| let run_; | |
| if (typeof run === "string") { | |
| run_ = await this.readRun(run, { loadChildRuns }); | |
| } | |
| else if (typeof run === "object" && "id" in run) { | |
| run_ = run; | |
| } | |
| else { | |
| throw new Error(`Invalid run type: ${typeof run}`); | |
| } | |
| let referenceExample = undefined; | |
| if (run_.reference_example_id !== null && | |
| run_.reference_example_id !== undefined) { | |
| referenceExample = await this.readExample(run_.reference_example_id); | |
| } | |
| const feedbackResult = await evaluator.evaluateRun(run_, referenceExample); | |
| let sourceInfo_ = sourceInfo ?? {}; | |
| if (feedbackResult.evaluatorInfo) { | |
| sourceInfo_ = { ...sourceInfo_, ...feedbackResult.evaluatorInfo }; | |
| } | |
| return await this.createFeedback(run_.id, feedbackResult.key, { | |
| score: feedbackResult.score, | |
| value: feedbackResult.value, | |
| comment: feedbackResult.comment, | |
| correction: feedbackResult.correction, | |
| sourceInfo: sourceInfo_, | |
| feedbackSourceType: "model", | |
| }); | |
| } | |
| async createFeedback(runId, key, { score, value, correction, comment, sourceInfo, feedbackSourceType = "api", sourceRunId, feedbackId, eager = false, }) { | |
| const feedback_source = { | |
| type: feedbackSourceType ?? "api", | |
| metadata: sourceInfo ?? {}, | |
| }; | |
| if (sourceRunId !== undefined && | |
| feedback_source?.metadata !== undefined && | |
| !feedback_source.metadata["__run"]) { | |
| feedback_source.metadata["__run"] = { run_id: sourceRunId }; | |
| } | |
| if (feedback_source?.metadata !== undefined && | |
| feedback_source.metadata["__run"]?.run_id !== undefined) { | |
| assertUuid(feedback_source.metadata["__run"].run_id); | |
| } | |
| const feedback = { | |
| id: feedbackId ?? uuid.v4(), | |
| run_id: runId, | |
| key, | |
| score, | |
| value, | |
| correction, | |
| comment, | |
| feedback_source: feedback_source, | |
| }; | |
| const url = `${this.apiUrl}/feedback` + (eager ? "/eager" : ""); | |
| const response = await this.caller.call(fetch, url, { | |
| method: "POST", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(feedback), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "create feedback"); | |
| return feedback; | |
| } | |
| async updateFeedback(feedbackId, { score, value, correction, comment, }) { | |
| const feedbackUpdate = {}; | |
| if (score !== undefined && score !== null) { | |
| feedbackUpdate["score"] = score; | |
| } | |
| if (value !== undefined && value !== null) { | |
| feedbackUpdate["value"] = value; | |
| } | |
| if (correction !== undefined && correction !== null) { | |
| feedbackUpdate["correction"] = correction; | |
| } | |
| if (comment !== undefined && comment !== null) { | |
| feedbackUpdate["comment"] = comment; | |
| } | |
| assertUuid(feedbackId); | |
| const response = await this.caller.call(fetch, `${this.apiUrl}/feedback/${feedbackId}`, { | |
| method: "PATCH", | |
| headers: { ...this.headers, "Content-Type": "application/json" }, | |
| body: JSON.stringify(feedbackUpdate), | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| await raiseForStatus(response, "update feedback"); | |
| } | |
| async readFeedback(feedbackId) { | |
| assertUuid(feedbackId); | |
| const path = `/feedback/${feedbackId}`; | |
| const response = await this._get(path); | |
| return response; | |
| } | |
| async deleteFeedback(feedbackId) { | |
| assertUuid(feedbackId); | |
| const path = `/feedback/${feedbackId}`; | |
| const response = await this.caller.call(fetch, this.apiUrl + path, { | |
| method: "DELETE", | |
| headers: this.headers, | |
| signal: AbortSignal.timeout(this.timeout_ms), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`Failed to delete ${path}: ${response.status} ${response.statusText}`); | |
| } | |
| await response.json(); | |
| } | |
| async *listFeedback({ runIds, feedbackKeys, feedbackSourceTypes, } = {}) { | |
| const queryParams = new URLSearchParams(); | |
| if (runIds) { | |
| queryParams.append("run", runIds.join(",")); | |
| } | |
| if (feedbackKeys) { | |
| for (const key of feedbackKeys) { | |
| queryParams.append("key", key); | |
| } | |
| } | |
| if (feedbackSourceTypes) { | |
| for (const type of feedbackSourceTypes) { | |
| queryParams.append("source", type); | |
| } | |
| } | |
| for await (const feedbacks of this._getPaginated("/feedback", queryParams)) { | |
| yield* feedbacks; | |
| } | |
| } | |
| } | |