mirror of
https://github.com/run-llama/llama_cloud_services.git
synced 2026-06-30 21:47:56 -04:00
destructured keyword params for classify (#996)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"llama-cloud-services": minor
|
||||
---
|
||||
|
||||
Switch to keyword arguments rather than positional args
|
||||
@@ -12,7 +12,7 @@
|
||||
"@tanstack/react-router": "^1.133.22",
|
||||
"@tanstack/react-router-devtools": "^1.133.22",
|
||||
"@tanstack/react-start": "^1.133.22",
|
||||
"llama-cloud-services": "^0.3.10",
|
||||
"llama-cloud-services": "workspace:*",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
|
||||
@@ -15,7 +15,7 @@ export const Route = createFileRoute('/api/classify')({
|
||||
const rawRes = await classifier.classify(
|
||||
classificationRules,
|
||||
parsingConfig,
|
||||
[new Uint8Array(buff)],
|
||||
{ fileContents: [new Uint8Array(buff)] },
|
||||
)
|
||||
const results = rawRes.items
|
||||
let classification = ""
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
"lint-staged": {
|
||||
"ts/llama_cloud_services/src/**/*.{ts,tsx,js,jsx}": [
|
||||
"pnpm --filter llama-cloud-services exec eslint --fix",
|
||||
"pnpm --filter llama-cloud-services exec prettier --write"
|
||||
"pnpm --filter llama-cloud-services exec prettier --write src/ tests/"
|
||||
]
|
||||
},
|
||||
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912"
|
||||
|
||||
Generated
+3368
-19
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@ packages:
|
||||
- "ts/*"
|
||||
- "py"
|
||||
- "py/*"
|
||||
- "examples-ts/*"
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"build": "pnpm run generate && bunchee",
|
||||
"dev": "bunchee --watch",
|
||||
"lint": "eslint src/ --ignore-pattern client/*.ts --no-warn-ignored",
|
||||
"format": "prettier --write ./src/",
|
||||
"format:check": "prettier --check ./src/",
|
||||
"format": "prettier --write ./src/ tests/",
|
||||
"format:check": "prettier --check ./src/ tests/",
|
||||
"test": "vitest run --testTimeout=60000",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:ui": "vitest --ui",
|
||||
|
||||
@@ -36,34 +36,40 @@ export class LlamaClassify {
|
||||
|
||||
async classify(
|
||||
rules: ClassifierRule[],
|
||||
parsingConfiguration: ClassifyParsingConfiguration,
|
||||
fileContents:
|
||||
| Buffer<ArrayBufferLike>[]
|
||||
| File[]
|
||||
| Uint8Array<ArrayBuffer>[]
|
||||
| string[]
|
||||
| undefined = undefined,
|
||||
filePaths: string[] | undefined = undefined,
|
||||
projectId: string | null = null,
|
||||
organizationId: string | null = null,
|
||||
pollingInterval: number = 1,
|
||||
maxPollingIterations: number = 1800,
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<ClassifyJobResults> {
|
||||
const result = await classify(
|
||||
rules,
|
||||
parsingConfiguration,
|
||||
configuration: ClassifyParsingConfiguration,
|
||||
{
|
||||
fileContents,
|
||||
filePaths,
|
||||
projectId,
|
||||
organizationId,
|
||||
this.client,
|
||||
pollingInterval = 1,
|
||||
maxPollingIterations = 1800,
|
||||
maxRetriesOnError = 10,
|
||||
retryInterval = 0.5,
|
||||
}: {
|
||||
fileContents?:
|
||||
| Buffer<ArrayBufferLike>[]
|
||||
| File[]
|
||||
| Uint8Array<ArrayBuffer>[]
|
||||
| string[]
|
||||
| undefined;
|
||||
filePaths?: string[] | undefined;
|
||||
projectId?: string;
|
||||
pollingInterval?: number;
|
||||
maxPollingIterations?: number;
|
||||
maxRetriesOnError?: number;
|
||||
retryInterval?: number;
|
||||
},
|
||||
): Promise<ClassifyJobResults> {
|
||||
const result = await classify(rules, configuration, {
|
||||
fileContents,
|
||||
filePaths,
|
||||
projectId: projectId ?? undefined,
|
||||
client: this.client,
|
||||
pollingInterval,
|
||||
maxPollingIterations,
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,16 +19,23 @@ import { sleep } from "./utils";
|
||||
import { uploadFile } from "./fileUpload";
|
||||
import { File } from "buffer";
|
||||
|
||||
async function createClassifyJob(
|
||||
fileIds: string[],
|
||||
rules: ClassifierRule[],
|
||||
parsingConfiguration: ClassifyParsingConfiguration,
|
||||
organizationId: null | string,
|
||||
projectId: null | string,
|
||||
client: Client | undefined,
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<string> {
|
||||
async function createClassifyJob({
|
||||
fileIds,
|
||||
rules,
|
||||
parsingConfiguration,
|
||||
projectId,
|
||||
client,
|
||||
maxRetriesOnError = 10,
|
||||
retryInterval = 0.5,
|
||||
}: {
|
||||
fileIds: string[];
|
||||
rules: ClassifierRule[];
|
||||
parsingConfiguration: ClassifyParsingConfiguration;
|
||||
projectId?: string | undefined;
|
||||
client?: Client | undefined;
|
||||
maxRetriesOnError?: number;
|
||||
retryInterval?: number;
|
||||
}): Promise<string> {
|
||||
const rawData = {
|
||||
file_ids: fileIds,
|
||||
rules: rules,
|
||||
@@ -38,7 +45,6 @@ async function createClassifyJob(
|
||||
body: rawData,
|
||||
query: {
|
||||
project_id: projectId,
|
||||
organization_id: organizationId,
|
||||
},
|
||||
} as CreateClassifyJobApiV1ClassifierJobsPostData;
|
||||
const options = data as Options<CreateClassifyJobApiV1ClassifierJobsPostData>;
|
||||
@@ -75,12 +81,17 @@ async function createClassifyJob(
|
||||
}
|
||||
}
|
||||
|
||||
async function pollForJobCompletion(
|
||||
jobId: string,
|
||||
interval: number = 1,
|
||||
maxIterations: number = 1800,
|
||||
client: Client | undefined = undefined,
|
||||
): Promise<boolean> {
|
||||
async function pollForJobCompletion({
|
||||
jobId,
|
||||
interval = 1,
|
||||
maxIterations = 1800,
|
||||
client,
|
||||
}: {
|
||||
jobId: string;
|
||||
interval?: number;
|
||||
maxIterations?: number;
|
||||
client?: Client | undefined;
|
||||
}): Promise<boolean> {
|
||||
let status: StatusEnum | undefined = undefined;
|
||||
const jobData = {
|
||||
path: { classify_job_id: jobId },
|
||||
@@ -114,17 +125,22 @@ async function pollForJobCompletion(
|
||||
}
|
||||
}
|
||||
|
||||
async function getJobResult(
|
||||
jobId: string,
|
||||
client: Client | undefined = undefined,
|
||||
projectId: string | null = null,
|
||||
organizationId: string | null = null,
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<ClassifyJobResults> {
|
||||
async function getJobResult({
|
||||
jobId,
|
||||
client,
|
||||
projectId,
|
||||
maxRetriesOnError = 10,
|
||||
retryInterval = 0.5,
|
||||
}: {
|
||||
jobId: string;
|
||||
client?: Client | undefined;
|
||||
projectId?: string | undefined;
|
||||
maxRetriesOnError?: number;
|
||||
retryInterval?: number;
|
||||
}): Promise<ClassifyJobResults> {
|
||||
const jobData = {
|
||||
path: { classify_job_id: jobId },
|
||||
query: { organization_id: organizationId, project_id: projectId },
|
||||
query: { project_id: projectId },
|
||||
} as GetClassificationJobResultsApiV1ClassifierJobsClassifyJobIdResultsGetData;
|
||||
const jobOptions =
|
||||
jobData as Options<GetClassificationJobResultsApiV1ClassifierJobsClassifyJobIdResultsGetData>;
|
||||
@@ -166,20 +182,30 @@ async function getJobResult(
|
||||
export async function classify(
|
||||
rules: ClassifierRule[],
|
||||
parsingConfiguration: ClassifyParsingConfiguration,
|
||||
fileContents:
|
||||
| Buffer<ArrayBufferLike>[]
|
||||
| File[]
|
||||
| Uint8Array<ArrayBuffer>[]
|
||||
| string[]
|
||||
| undefined = undefined,
|
||||
filePaths: string[] | undefined = undefined,
|
||||
projectId: string | null = null,
|
||||
organizationId: string | null = null,
|
||||
client: Client | undefined = undefined,
|
||||
pollingInterval: number = 1,
|
||||
maxPollingIterations: number = 1800,
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
{
|
||||
fileContents,
|
||||
filePaths,
|
||||
projectId,
|
||||
client,
|
||||
pollingInterval = 1,
|
||||
maxPollingIterations = 1800,
|
||||
maxRetriesOnError = 10,
|
||||
retryInterval = 0.5,
|
||||
}: {
|
||||
fileContents?:
|
||||
| Buffer<ArrayBufferLike>[]
|
||||
| File[]
|
||||
| Uint8Array<ArrayBuffer>[]
|
||||
| string[]
|
||||
| undefined;
|
||||
filePaths?: string[] | undefined;
|
||||
projectId?: string | undefined;
|
||||
client?: Client | undefined;
|
||||
pollingInterval?: number;
|
||||
maxPollingIterations?: number;
|
||||
maxRetriesOnError?: number;
|
||||
retryInterval?: number;
|
||||
},
|
||||
): Promise<ClassifyJobResults> {
|
||||
const fileIds: string[] = [];
|
||||
if (!filePaths && !fileContents) {
|
||||
@@ -191,16 +217,13 @@ export async function classify(
|
||||
if (filePaths) {
|
||||
const uploadPromises = filePaths.map(async (name) => {
|
||||
try {
|
||||
const fileId = await uploadFile(
|
||||
name,
|
||||
undefined,
|
||||
undefined,
|
||||
projectId,
|
||||
organizationId,
|
||||
client,
|
||||
const fileId = await uploadFile({
|
||||
filePath: name,
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
);
|
||||
retryInterval: retryInterval,
|
||||
project_id: projectId,
|
||||
client: client,
|
||||
});
|
||||
if (fileId) {
|
||||
return fileId;
|
||||
} else {
|
||||
@@ -220,16 +243,13 @@ export async function classify(
|
||||
if (fileContents) {
|
||||
const uploadPromises = fileContents.map(async (content) => {
|
||||
try {
|
||||
const fileId = await uploadFile(
|
||||
undefined,
|
||||
content,
|
||||
undefined,
|
||||
projectId,
|
||||
organizationId,
|
||||
client,
|
||||
const fileId = await uploadFile({
|
||||
fileContent: content,
|
||||
...(projectId ? { project_id: projectId } : {}),
|
||||
...(client ? { client: client } : {}),
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
);
|
||||
});
|
||||
if (fileId) {
|
||||
return fileId;
|
||||
} else {
|
||||
@@ -252,33 +272,31 @@ export async function classify(
|
||||
);
|
||||
}
|
||||
|
||||
const jobId = await createClassifyJob(
|
||||
const jobId = await createClassifyJob({
|
||||
fileIds,
|
||||
rules,
|
||||
parsingConfiguration,
|
||||
organizationId,
|
||||
projectId,
|
||||
client,
|
||||
...(projectId ? { projectId: projectId } : {}),
|
||||
...(client ? { client: client } : {}),
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
);
|
||||
const success = await pollForJobCompletion(
|
||||
});
|
||||
const success = await pollForJobCompletion({
|
||||
jobId,
|
||||
pollingInterval,
|
||||
maxPollingIterations,
|
||||
interval: pollingInterval,
|
||||
maxIterations: maxPollingIterations,
|
||||
client,
|
||||
);
|
||||
});
|
||||
if (!success) {
|
||||
throw new Error("Your job is taking longer than 10 minutes, timing out...");
|
||||
} else {
|
||||
return (await getJobResult(
|
||||
return (await getJobResult({
|
||||
jobId,
|
||||
client,
|
||||
projectId,
|
||||
organizationId,
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
)) as ClassifyJobResults;
|
||||
})) as ClassifyJobResults;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -378,16 +378,16 @@ export async function extract(
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<ExtractResult | undefined> {
|
||||
const fileId = (await uploadFile(
|
||||
const fileId = (await uploadFile({
|
||||
filePath,
|
||||
fileContent,
|
||||
fileName,
|
||||
project_id,
|
||||
organization_id,
|
||||
project_id: project_id ?? undefined,
|
||||
organization_id: organization_id ?? undefined,
|
||||
client,
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
)) as string;
|
||||
})) as string;
|
||||
const extractJobCreate = {
|
||||
extraction_agent_id: agentId,
|
||||
file_id: fileId,
|
||||
@@ -457,16 +457,16 @@ export async function extractStateless(
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<ExtractResult | undefined> {
|
||||
const fileId = (await uploadFile(
|
||||
const fileId = (await uploadFile({
|
||||
filePath,
|
||||
fileContent,
|
||||
fileName,
|
||||
project_id,
|
||||
organization_id,
|
||||
project_id: project_id ?? undefined,
|
||||
organization_id: organization_id ?? undefined,
|
||||
client,
|
||||
maxRetriesOnError,
|
||||
retryInterval,
|
||||
)) as string;
|
||||
})) as string;
|
||||
const extractStatetelessCreate = {
|
||||
data_schema: dataSchema,
|
||||
file_id: fileId,
|
||||
|
||||
@@ -23,21 +23,30 @@ function textToFile(text: string, fileName: string | null = null) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function uploadFile(
|
||||
filePath: string | undefined = undefined,
|
||||
fileContent:
|
||||
export async function uploadFile({
|
||||
filePath,
|
||||
fileContent,
|
||||
fileName,
|
||||
project_id,
|
||||
organization_id,
|
||||
client,
|
||||
maxRetriesOnError = 10,
|
||||
retryInterval = 0.5,
|
||||
}: {
|
||||
filePath?: string | undefined;
|
||||
fileContent?:
|
||||
| Buffer<ArrayBufferLike>
|
||||
| File
|
||||
| Uint8Array<ArrayBuffer>
|
||||
| string
|
||||
| undefined = undefined,
|
||||
fileName: string | undefined = undefined,
|
||||
project_id: string | null = null,
|
||||
organization_id: string | null = null,
|
||||
client: Client | undefined = undefined,
|
||||
maxRetriesOnError: number = 10,
|
||||
retryInterval: number = 0.5,
|
||||
): Promise<string | undefined> {
|
||||
| undefined;
|
||||
fileName?: string | undefined;
|
||||
project_id?: string | undefined;
|
||||
organization_id?: string | undefined;
|
||||
client?: Client | undefined;
|
||||
maxRetriesOnError?: number;
|
||||
retryInterval?: number;
|
||||
}): Promise<string | undefined> {
|
||||
let file: File | undefined = undefined;
|
||||
if (typeof filePath === "undefined" && typeof fileContent === "undefined") {
|
||||
throw new Error(
|
||||
@@ -79,7 +88,7 @@ export async function uploadFile(
|
||||
} as BodyUploadFileApiV1FilesPost;
|
||||
const uploadData = {
|
||||
body: fileToUpload,
|
||||
query: { organization_id: organization_id, project_id: project_id },
|
||||
query: { project_id: project_id, organization_id: organization_id },
|
||||
} as UploadFileApiV1FilesPostData;
|
||||
const uploadOptions = uploadData as Options<UploadFileApiV1FilesPostData>;
|
||||
if (typeof client != "undefined") {
|
||||
@@ -95,6 +104,8 @@ export async function uploadFile(
|
||||
const uploadResponse = await uploadFileApiV1FilesPost(uploadOptions);
|
||||
let fileId: string | undefined = undefined;
|
||||
if (!uploadResponse.response.ok) {
|
||||
const error = await uploadResponse.response.text();
|
||||
console.error("Error while uploading file: ", error);
|
||||
retries++;
|
||||
await sleep(retryInterval * 1000);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,10 @@ import { LlamaParseReader } from "../src/reader.js";
|
||||
import { LlamaCloudIndex } from "../src/LlamaCloudIndex.js";
|
||||
import { LlamaExtract, LlamaExtractAgent } from "../src/LlamaExtract.js";
|
||||
import { LlamaClassify } from "../src/LlamaClassify.js";
|
||||
import { ClassifierRule, ClassifyParsingConfiguration } from "../src/classify.js";
|
||||
import {
|
||||
ClassifierRule,
|
||||
ClassifyParsingConfiguration,
|
||||
} from "../src/classify.js";
|
||||
import { Document } from "@llamaindex/core/schema";
|
||||
import { fs } from "@llamaindex/env";
|
||||
import { ExtractConfig } from "../src/api.js";
|
||||
@@ -499,25 +502,29 @@ describe("Integration Tests", () => {
|
||||
process.env.LLAMA_CLOUD_API_KEY!,
|
||||
"https://api.cloud.llamaindex.ai",
|
||||
);
|
||||
const testContent =
|
||||
`A Fox one day spied a beautiful bunch of ripe grapes hanging from a vine trained along the branches of a tree. The grapes seemed ready to burst with juice, and the Fox's mouth watered as he gazed longingly at them. The bunch hung from a high branch, and the Fox had to jump for it. The first time he jumped he missed it by a long way. So he walked off a short distance and took a running leap at it, only to fall short once more. Again and again he tried, but in vain. Now he sat down and looked at the grapes in disgust. "What a fool I am," he said. "Here I am wearing myself out to get a bunch of sour grapes that are not worth gaping for." And off he walked very, very scornfully.There are many who pretend to despise and belittle that which is beyond their reach.`;
|
||||
const testContent = `A Fox one day spied a beautiful bunch of ripe grapes hanging from a vine trained along the branches of a tree. The grapes seemed ready to burst with juice, and the Fox's mouth watered as he gazed longingly at them. The bunch hung from a high branch, and the Fox had to jump for it. The first time he jumped he missed it by a long way. So he walked off a short distance and took a running leap at it, only to fall short once more. Again and again he tried, but in vain. Now he sat down and looked at the grapes in disgust. "What a fool I am," he said. "Here I am wearing myself out to get a bunch of sour grapes that are not worth gaping for." And off he walked very, very scornfully.There are many who pretend to despise and belittle that which is beyond their reach.`;
|
||||
const testFilePath = "the_fox_and_the_grapes.md";
|
||||
|
||||
await fs.writeFile(testFilePath, new TextEncoder().encode(testContent));
|
||||
|
||||
const rules: ClassifierRule[] = [
|
||||
{type: "fable", description: "A short story featuring animals whose aim is to teach the reader a lesson (the moral of the story)"},
|
||||
{type: "fairy_tale", description: "A mid-to-long story featuring humans, magic creatures and other characters, whose main aim is to entertain the readers."}
|
||||
]
|
||||
{
|
||||
type: "fable",
|
||||
description:
|
||||
"A short story featuring animals whose aim is to teach the reader a lesson (the moral of the story)",
|
||||
},
|
||||
{
|
||||
type: "fairy_tale",
|
||||
description:
|
||||
"A mid-to-long story featuring humans, magic creatures and other characters, whose main aim is to entertain the readers.",
|
||||
},
|
||||
];
|
||||
|
||||
const parsingConfig: ClassifyParsingConfiguration = {lang: "en"}
|
||||
const parsingConfig: ClassifyParsingConfiguration = { lang: "en" };
|
||||
|
||||
const result = await classifyClient.classify(
|
||||
rules,
|
||||
parsingConfig,
|
||||
undefined,
|
||||
["the_fox_and_the_grapes.md"]
|
||||
);
|
||||
const result = await classifyClient.classify(rules, parsingConfig, {
|
||||
filePaths: ["the_fox_and_the_grapes.md"],
|
||||
});
|
||||
expect("items" in result).toBeTruthy();
|
||||
expect(result.items.length).toBeGreaterThan(0);
|
||||
expect("result" in result.items[0]).toBeTruthy();
|
||||
@@ -527,7 +534,7 @@ describe("Integration Tests", () => {
|
||||
const resultBuffer = await classifyClient.classify(
|
||||
rules,
|
||||
parsingConfig,
|
||||
[buffer],
|
||||
{ fileContents: [buffer] },
|
||||
);
|
||||
expect("items" in resultBuffer).toBeTruthy();
|
||||
expect(resultBuffer.items.length).toBeGreaterThan(0);
|
||||
@@ -535,9 +542,11 @@ describe("Integration Tests", () => {
|
||||
expect(resultBuffer.items[0].result!.type === "fable").toBeTruthy();
|
||||
|
||||
try {
|
||||
await fs.unlink("the_fox_and_the_grapes.md")
|
||||
} catch(err) {
|
||||
console.log(`Unable to delete file the_fox_and_the_grapes.md because of ${err}`)
|
||||
await fs.unlink("the_fox_and_the_grapes.md");
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Unable to delete file the_fox_and_the_grapes.md because of ${err}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
60000,
|
||||
|
||||
Reference in New Issue
Block a user