Skip to content

Commit d19e453

Browse files
authored
Merge pull request #26 from AI21Labs/rc_file_support
feat: Added File Support
2 parents e5399f7 + 43b0954 commit d19e453

36 files changed

+693
-40
lines changed

.github/workflows/integration-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Install dependencies
2323
run: |
2424
npm install
25-
npm install ai21
25+
npm run build
2626
2727
- name: Run Integration Tests
2828
env:

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.txt

README.md

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,43 @@
1-
# AI21 API Client
1+
<h1 align="center">
2+
<a href="https://github.com/AI21Labs/ai21-typescript">AI21 Labs TypeScript SDK</a>
3+
</h1>
4+
5+
<p align="center">
6+
<a href="https://github.com/AI21Labs/ai21-typescript/actions/workflows/unittests.yml"><img src="https://github.com/AI21Labs/ai21-typescript/actions/workflows/unittests.yml/badge.svg?branch=main" alt="Test"></a>
7+
<a href="https://github.com/AI21Labs/ai21-typescript/actions/workflows/integration-tests.yml"><img src="https://github.com/AI21Labs/ai21-typescript/actions/workflows/integration-tests.yml/badge.svg?branch=main" alt="Integration Tests"></a>
8+
<a href="https://www.npmjs.com/package/ai21" target="_blank"><img src="https://img.shields.io/npm/v/ai21?color=%2334D058&label=npm%20package" alt="Package version"></a>
9+
<a href="https://nodejs.org/" target="_blank"><img src="https://img.shields.io/badge/node->=18.0.0-brightgreen" alt="Supported Node.js versions"></a>
10+
<a href="https://github.com/semantic-release/semantic-release" target="_blank"><img src="https://img.shields.io/badge/semantic--release-typescript-e10079?logo=semantic-release" alt="Semantic Release Support"></a>
11+
<a href="https://opensource.org/licenses/Apache-2.0" target="_blank"><img src="https://img.shields.io/badge/License-Apache_2.0-blue.svg" alt="License"></a>
12+
</p>
213

314
The AI21 API Client is a TypeScript library that provides a convenient interface for interacting with the AI21 API. It abstracts away the low-level details of making API requests and handling responses, allowing developers to focus on building their applications.
415

16+
- [Installation](#Installation) 💿
17+
- [Examples](#examples-tldr) 🗂️
18+
- [AI21 Official Documentation](#Documentation)
19+
- [Chat](#Chat)
20+
- [Conversational RAG (Beta)](#Conversational-RAG)
21+
- [Files](#Files)
22+
23+
24+
## Environment Support
25+
26+
This client supports both Node.js and browser environments:
27+
28+
- **Node.js**: Works out of the box with Node.js >=18.0.0
29+
- **Browser**: Requires explicit opt-in by setting `dangerouslyAllowBrowser: true` in the client options
30+
31+
```typescript
32+
// Browser usage example
33+
const client = new AI21({
34+
apiKey: process.env.AI21_API_KEY, // or pass it in directly
35+
dangerouslyAllowBrowser: true // Required for browser environments
36+
});
37+
```
38+
39+
> ⚠️ **Security Notice**: Using this client in the browser could expose your API key to end users. Only enable `dangerouslyAllowBrowser` if you understand the security implications and have implemented appropriate security measures.
40+
541
## Installation
642

743
You can install the AI21 API Client using npm or yarn:
@@ -16,10 +52,25 @@ or
1652
yarn add ai21
1753
```
1854

19-
## Usage
55+
## Examples (tl;dr)
56+
57+
If you want to quickly get a glance how to use the AI21 Typescript SDK and jump straight to business, you can check out the examples. Take a look at our models and see them in action! Several examples and demonstrations have been put together to show our models' functionality and capabilities.
58+
59+
### [Check out the Examples](examples/)
60+
61+
Feel free to dive in, experiment, and adapt these examples to suit your needs. We believe they'll help you get up and running quickly.
62+
63+
## Documentation
64+
65+
The full documentation for the REST API can be found on [docs.ai21.com](https://docs.ai21.com/).
66+
67+
68+
## Chat
2069

2170
To use the AI21 API Client, you'll need to have an API key. You can obtain an API key by signing up for an account on the AI21 website.
2271

72+
The `AI21` class provides a `chat` property that gives you access to the Chat API. You can use this to generate text, complete prompts, and more.
73+
2374
Here's an example of how to use the `AI21` class to interact with the API:
2475

2576
```typescript
@@ -44,7 +95,7 @@ The client supports streaming responses for real-time processing. Here are examp
4495
#### Using Async Iterator
4596

4697
```typescript
47-
const streamResponse = await ai21.chat.completions.create({
98+
const streamResponse = await client.chat.completions.create({
4899
model: 'jamba-1.5-mini',
49100
messages: [{ role: 'user', content: 'Write a story about a space cat' }],
50101
stream: true,
@@ -54,8 +105,51 @@ for await (const chunk of streamResponse) {
54105
console.log(chunk.choices[0]?.delta?.content || '');
55106
}
56107
```
108+
---
109+
### Files
110+
111+
112+
The `AI21` class provides a `files` property that gives you access to the Files API. You can use it to upload, retrieve, update, list, and delete files.
113+
114+
115+
```typescript
116+
import { AI21 } from 'ai21';
117+
118+
const client = new AI21({
119+
apiKey: process.env.AI21_API_KEY, // or pass it in directly
120+
});
121+
122+
const fileUploadResponse = await client.files.create({
123+
file: './articles/article1.pdf',
124+
labels: ['science', 'biology'],
125+
path: 'virtual-path/to/science-articles',
126+
});
127+
128+
129+
const file = await client.files.get(fileUploadResponse.fileId);
130+
131+
```
132+
133+
---
134+
### Conversational-RAG
135+
136+
137+
The `AI21` class provides a `conversationalRag` property that gives you access to the Conversational RAG API. You can use it to ask questions that are answered based on the files you uploaded.
138+
139+
140+
```typescript
141+
import { AI21 } from 'ai21';
142+
143+
const client = new AI21({
144+
apiKey: process.env.AI21_API_KEY, // or pass it in directly
145+
});
146+
147+
const convRagResponse = await client.conversationalRag.create({
148+
messages: [{ role: 'user', content: 'This question presumes that the answer can be found within the uploaded files.' }],
149+
});
150+
151+
```
57152

58-
The `AI21` class provides a `chat` property that gives you access to the Chat API. You can use this to generate text, complete prompts, and more.
59153

60154
## Configuration
61155

@@ -65,6 +159,7 @@ The `AI21` class accepts several configuration options, which you can pass in wh
65159
- `apiKey`: Your AI21 API Key
66160
- `maxRetries`: The maximum number of retries for failed requests (default: `3`)
67161
- `timeout`: The request timeout in seconds
162+
- `dangerouslyAllowBrowser`: Set to `true` to allow the client to be used in a browser environment.
68163

69164
## API Reference
70165

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
The meerkat (Suricata suricatta) or suricate is a small mongoose found in southern Africa. It is characterised by a broad head, large eyes, a pointed snout, long legs, a thin tapering tail, and a brindled coat pattern. The head-and-body length is around 24–35 cm (9.4–13.8 in), and the weight is typically between 0.62 and 0.97 kg (1.4 and 2.1 lb). The coat is light grey to yellowish-brown with alternate, poorly-defined light and dark bands on the back. Meerkats have foreclaws adapted for digging and have the ability to thermoregulate to survive in their harsh, dry habitat. Three subspecies are recognised.
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
import { AI21, FileResponse, UploadFileResponse } from 'ai21';
2+
import path from 'path';
3+
import fs from 'fs';
4+
5+
function sleep(ms) {
6+
return new Promise((resolve) => setTimeout(resolve, ms));
7+
}
8+
9+
async function waitForFileProcessing(
10+
client: AI21,
11+
fileId: string,
12+
timeout: number = 30000,
13+
interval: number = 1000,
14+
) {
15+
const startTime = Date.now();
16+
17+
while (Date.now() - startTime < timeout) {
18+
const file: FileResponse = await client.files.get(fileId);
19+
if (file.status !== 'PROCESSING') {
20+
return file;
21+
}
22+
await sleep(interval);
23+
}
24+
25+
throw new Error(`File processing timed out after ${timeout}ms`);
26+
}
27+
28+
async function uploadGetUpdateDelete(fileInput, path) {
29+
const client = new AI21({ apiKey: process.env.AI21_API_KEY });
30+
try {
31+
console.log(`Starting upload for file:`, typeof fileInput);
32+
const uploadFileResponse: UploadFileResponse = await client.files.create({
33+
file: fileInput,
34+
path: path,
35+
});
36+
console.log(`✓ Upload completed. File ID: ${uploadFileResponse.fileId}`);
37+
38+
console.log('Waiting for file processing...');
39+
let file: FileResponse = await waitForFileProcessing(client, uploadFileResponse.fileId);
40+
console.log(`✓ File processing completed with status: ${file.status}`);
41+
42+
if (file.status === 'PROCESSED') {
43+
console.log('Starting file update...');
44+
await client.files.update({
45+
fileId: uploadFileResponse.fileId,
46+
labels: ['test99'],
47+
publicUrl: 'https://www.miri.com',
48+
});
49+
file = await client.files.get(uploadFileResponse.fileId);
50+
console.log('✓ File update completed');
51+
} else {
52+
console.log(`⚠ File processing failed with status ${file.status}`);
53+
return; // Exit early if processing failed
54+
}
55+
56+
console.log('Starting file deletion...');
57+
await client.files.delete(uploadFileResponse.fileId);
58+
console.log('✓ File deletion completed');
59+
60+
// Add buffer time between operations
61+
await sleep(2000);
62+
} catch (error) {
63+
console.error('❌ Error in uploadGetUpdateDelete:', error);
64+
throw error;
65+
}
66+
}
67+
68+
async function listFiles() {
69+
const client = new AI21({ apiKey: process.env.AI21_API_KEY });
70+
const files = await client.files.list({ limit: 4 });
71+
console.log(`Listed files: ${files}`);
72+
}
73+
74+
const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
75+
76+
const createNodeFile = (content: Buffer, filename: string, type: string) => {
77+
if (process.platform === 'linux') {
78+
console.log('Running on Linux (GitHub Actions)');
79+
// Special handling for Linux (GitHub Actions)
80+
return {
81+
name: filename,
82+
type: type,
83+
buffer: content,
84+
[Symbol.toStringTag]: 'File',
85+
};
86+
} else {
87+
console.log('Running on other platforms');
88+
// Regular handling for other platforms
89+
return new File([content], filename, { type });
90+
}
91+
};
92+
93+
if (isBrowser) {
94+
console.log('Cannot run upload examples in Browser environment');
95+
} else {
96+
/* Log environment details */
97+
console.log('=== Environment Information ===');
98+
console.log(`Node.js Version: ${process.version}`);
99+
console.log(`Platform: ${process.platform}`);
100+
console.log(`Architecture: ${process.arch}`);
101+
console.log(`Process ID: ${process.pid}`);
102+
console.log(`Current Working Directory: ${process.cwd()}`);
103+
console.log('===========================\n');
104+
105+
/* Run all operations sequentially */
106+
(async () => {
107+
try {
108+
console.log('=== Starting first operation ===');
109+
// First operation - upload file from path
110+
const filePath = path.resolve(process.cwd(), 'examples/studio/conversational-rag/files', 'meerkat.txt');
111+
if (!fs.existsSync(filePath)) {
112+
throw new Error(`File not found: ${filePath}`);
113+
} else {
114+
console.log(`File found: ${filePath}`);
115+
}
116+
117+
await uploadGetUpdateDelete(filePath, Date.now().toString());
118+
console.log('=== First operation completed ===\n');
119+
await sleep(2000);
120+
121+
console.log('=== Starting second operation ===');
122+
// Second operation - upload file from File instance
123+
const fileContent = Buffer.from(
124+
'Opossums are members of the marsupial order Didelphimorphia endemic to the Americas.',
125+
);
126+
const dummyFile = createNodeFile(fileContent, 'example.txt', 'text/plain');
127+
await uploadGetUpdateDelete(dummyFile, Date.now().toString());
128+
console.log('=== Second operation completed ===\n');
129+
await sleep(2000);
130+
131+
console.log('=== Starting file listing ===');
132+
await listFiles();
133+
console.log('=== File listing completed ===');
134+
} catch (error) {
135+
console.error('❌ Main execution error:', error);
136+
process.exit(1); // Exit with error code if something fails
137+
}
138+
})();
139+
}

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
{
22
"name": "ai21",
3-
"version": "1.0.3",
3+
"engines": {
4+
"node": ">=18.0.0"
5+
},
6+
"version": "1.1.0-rc.1",
47
"description": "AI21 TypeScript SDK",
58
"main": "./dist/bundle.cjs.js",
69
"types": "./dist/index.d.ts",

src/AI21.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { Chat } from './resources/chat';
55
import { APIClient } from './APIClient';
66
import { Headers } from './types';
77
import * as Runtime from './runtime';
8-
import { ConversationalRag } from './resources/rag/conversationalRag';
8+
import { ConversationalRag } from './resources/rag/conversational-rag';
9+
import { Files } from './resources';
910

1011
export interface ClientOptions {
1112
baseURL?: string | undefined;
@@ -67,6 +68,7 @@ export class AI21 extends APIClient {
6768
// Resources
6869
chat: Chat = new Chat(this);
6970
conversationalRag: ConversationalRag = new ConversationalRag(this);
71+
files: Files = new Files(this);
7072

7173
// eslint-disable-next-line @typescript-eslint/no-unused-vars
7274
protected override authHeaders(_: Types.FinalRequestOptions): Types.Headers {

0 commit comments

Comments
 (0)