Skip to content

Commit

Permalink
feat(api): add message batches api
Browse files Browse the repository at this point in the history
  • Loading branch information
RobertCraigie authored and stainless-app[bot] committed Oct 8, 2024
1 parent d225d1d commit 1c404b2
Show file tree
Hide file tree
Showing 19 changed files with 2,187 additions and 30 deletions.
4 changes: 2 additions & 2 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
configured_endpoints: 3
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-286f00929e2a4d28d991e6a7e660fa801dca7ec91d8ecb2fc17654bb8173eb0d.yml
configured_endpoints: 9
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-aedee7570aa925baba404fc5bd3c8c1fffe8845517e492751db9b175c5cae9da.yml
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,51 @@ Streaming with `client.messages.stream(...)` exposes [various helpers for your c

Alternatively, you can use `client.messages.create({ ..., stream: true })` which only returns an async iterable of the events in the stream and thus uses less memory (it does not build up a final message object for you).

## Message Batches

This SDK provides beta support for the [Message Batches API](https://docs.anthropic.com/en/docs/build-with-claude/message-batches) under the `client.beta.messages.batches` namespace.

### Creating a batch

Message Batches take the exact same request params as the standard Messages API:

```ts
await anthropic.beta.messages.batches.create({
requests: [
{
custom_id: 'my-first-request',
params: {
model: 'claude-3-5-sonnet-20240620',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hello, world' }],
},
},
{
custom_id: 'my-second-request',
params: {
model: 'claude-3-5-sonnet-20240620',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Hi again, friend' }],
},
},
],
});
```


### Getting results from a batch

Once a Message Batch has been processed, indicated by `.processing_status === 'ended'`, you can access the results with `.batches.results()`

```ts
const results = await anthropic.beta.messages.batches.results(batch_id);
for await (const entry of results) {
if (entry.result.type === 'succeeded') {
console.log(entry.result.message.content)
}
}
```

## Tool use beta

This SDK provides beta support for tool use, aka function calling. More details can be found in [the documentation](https://docs.anthropic.com/claude/docs/tool-use).
Expand Down Expand Up @@ -224,6 +269,37 @@ On timeout, an `APIConnectionTimeoutError` is thrown.

Note that requests which time out will be [retried twice by default](#retries).

## Auto-pagination

List methods in the Anthropic API are paginated.
You can use `for await … of` syntax to iterate through items across all pages:

```ts
async function fetchAllBetaMessagesBatches(params) {
const allBetaMessagesBatches = [];
// Automatically fetches more pages as needed.
for await (const betaMessageBatch of client.beta.messages.batches.list({ limit: 20 })) {
allBetaMessagesBatches.push(betaMessageBatch);
}
return allBetaMessagesBatches;
}
```

Alternatively, you can make request a single page at a time:

```ts
let page = await client.beta.messages.batches.list({ limit: 20 });
for (const betaMessageBatch of page.data) {
console.log(betaMessageBatch);
}

// Convenience methods are provided for manually paginating:
while (page.hasNextPage()) {
page = page.getNextPage();
// ...
}
```

## Default Headers

We automatically send the `anthropic-version` header set to `2023-06-01`.
Expand Down
71 changes: 71 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,77 @@ Methods:

# Beta

Types:

- <code><a href="./src/resources/beta/beta.ts">AnthropicBeta</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaAPIError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaAuthenticationError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaErrorResponse</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaInvalidRequestError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaNotFoundError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaOverloadedError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaPermissionError</a></code>
- <code><a href="./src/resources/beta/beta.ts">BetaRateLimitError</a></code>

## Messages

Types:

- <code><a href="./src/resources/beta/messages/messages.ts">BetaCacheControlEphemeral</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaContentBlock</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaContentBlockParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaImageBlockParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaInputJSONDelta</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaMessage</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaMessageDeltaUsage</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaMessageParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaMetadata</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawContentBlockDeltaEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawContentBlockStartEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawContentBlockStopEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawMessageDeltaEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawMessageStartEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawMessageStopEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaRawMessageStreamEvent</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaTextBlock</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaTextBlockParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaTextDelta</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaTool</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolChoice</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolChoiceAny</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolChoiceAuto</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolChoiceTool</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolResultBlockParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolUseBlock</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaToolUseBlockParam</a></code>
- <code><a href="./src/resources/beta/messages/messages.ts">BetaUsage</a></code>

Methods:

- <code title="post /v1/messages?beta=true">client.beta.messages.<a href="./src/resources/beta/messages/messages.ts">create</a>({ ...params }) -> BetaMessage</code>

### Batches

Types:

- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatch</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchCanceledResult</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchErroredResult</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchExpiredResult</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchIndividualResponse</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchRequestCounts</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchResult</a></code>
- <code><a href="./src/resources/beta/messages/batches.ts">BetaMessageBatchSucceededResult</a></code>

Methods:

- <code title="post /v1/messages/batches?beta=true">client.beta.messages.batches.<a href="./src/resources/beta/messages/batches.ts">create</a>({ ...params }) -> BetaMessageBatch</code>
- <code title="get /v1/messages/batches/{message_batch_id}?beta=true">client.beta.messages.batches.<a href="./src/resources/beta/messages/batches.ts">retrieve</a>(messageBatchId, { ...params }) -> BetaMessageBatch</code>
- <code title="get /v1/messages/batches?beta=true">client.beta.messages.batches.<a href="./src/resources/beta/messages/batches.ts">list</a>({ ...params }) -> BetaMessageBatchesPage</code>
- <code title="post /v1/messages/batches/{message_batch_id}/cancel?beta=true">client.beta.messages.batches.<a href="./src/resources/beta/messages/batches.ts">cancel</a>(messageBatchId, { ...params }) -> BetaMessageBatch</code>
- <code title="get /v1/messages/batches/{message_batch_id}/results?beta=true">client.beta.messages.batches.<a href="./src/resources/beta/messages/batches.ts">results</a>(messageBatchId, { ...params }) -> Response</code>

## PromptCaching

### Messages
Expand Down
20 changes: 20 additions & 0 deletions examples/batch-results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Anthropic from '@anthropic-ai/sdk/index';

const anthropic = new Anthropic();

async function main() {
const batch_id = process.argv[2];
if (!batch_id) {
throw new Error('must specify a message batch ID, `yarn tsn examples/batch-results.ts msgbatch_123`');
}

console.log(`fetching results for ${batch_id}`);

const results = await anthropic.beta.messages.batches.results(batch_id);

for await (const result of results) {
console.log(result);
}
}

main();
15 changes: 15 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as Errors from './error';
import * as Uploads from './uploads';
import { type Agent } from './_shims/index';
import * as Core from './core';
import * as Pagination from './pagination';
import * as API from './resources/index';

export interface ClientOptions {
Expand Down Expand Up @@ -250,6 +251,10 @@ export import fileFromPath = Uploads.fileFromPath;
export namespace Anthropic {
export import RequestOptions = Core.RequestOptions;

export import Page = Pagination.Page;
export import PageParams = Pagination.PageParams;
export import PageResponse = Pagination.PageResponse;

export import Completions = API.Completions;
export import Completion = API.Completion;
export import CompletionCreateParams = API.CompletionCreateParams;
Expand Down Expand Up @@ -297,6 +302,16 @@ export namespace Anthropic {
export import MessageStreamParams = API.MessageStreamParams;

export import Beta = API.Beta;
export import AnthropicBeta = API.AnthropicBeta;
export import BetaAPIError = API.BetaAPIError;
export import BetaAuthenticationError = API.BetaAuthenticationError;
export import BetaError = API.BetaError;
export import BetaErrorResponse = API.BetaErrorResponse;
export import BetaInvalidRequestError = API.BetaInvalidRequestError;
export import BetaNotFoundError = API.BetaNotFoundError;
export import BetaOverloadedError = API.BetaOverloadedError;
export import BetaPermissionError = API.BetaPermissionError;
export import BetaRateLimitError = API.BetaRateLimitError;
}

export default Anthropic;
41 changes: 41 additions & 0 deletions src/internal/decoders/jsonl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { AnthropicError } from '../../error';
import { readableStreamAsyncIterable } from '../../streaming';
import { type Response } from '../../_shims/index';
import { LineDecoder, type Bytes } from './line';

export class JSONLDecoder<T> {
controller: AbortController;

constructor(
private iterator: AsyncIterableIterator<Bytes>,
controller: AbortController,
) {
this.controller = controller;
}

private async *decoder(): AsyncIterator<T, any, undefined> {
const lineDecoder = new LineDecoder();
for await (const chunk of this.iterator) {
for (const line of lineDecoder.decode(chunk)) {
yield JSON.parse(line);
}
}

for (const line of lineDecoder.flush()) {
yield JSON.parse(line);
}
}

[Symbol.asyncIterator](): AsyncIterator<T> {
return this.decoder();
}

static fromResponse<T>(response: Response, controller: AbortController): JSONLDecoder<T> {
if (!response.body) {
controller.abort();
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}

return new JSONLDecoder(readableStreamAsyncIterable<Bytes>(response.body), controller);
}
}
2 changes: 1 addition & 1 deletion src/internal/decoders/line.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AnthropicError } from '../../error';

type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;
export type Bytes = string | ArrayBuffer | Uint8Array | Buffer | null | undefined;

/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
Expand Down
84 changes: 84 additions & 0 deletions src/pagination.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.

import { AbstractPage, Response, APIClient, FinalRequestOptions, PageInfo } from './core';

export interface PageResponse<Item> {
data: Array<Item>;

has_more: boolean;

first_id: string | null;

last_id: string | null;
}

export interface PageParams {
/**
* Number of items per page.
*/
limit?: number;

before_id?: string;

after_id?: string;
}

export class Page<Item> extends AbstractPage<Item> implements PageResponse<Item> {
data: Array<Item>;

has_more: boolean;

first_id: string | null;

last_id: string | null;

constructor(client: APIClient, response: Response, body: PageResponse<Item>, options: FinalRequestOptions) {
super(client, response, body, options);

this.data = body.data || [];
this.has_more = body.has_more || false;
this.first_id = body.first_id || null;
this.last_id = body.last_id || null;
}

getPaginatedItems(): Item[] {
return this.data ?? [];
}

// @deprecated Please use `nextPageInfo()` instead
nextPageParams(): Partial<PageParams> | null {
const info = this.nextPageInfo();
if (!info) return null;
if ('params' in info) return info.params;
const params = Object.fromEntries(info.url.searchParams);
if (!Object.keys(params).length) return null;
return params;
}

nextPageInfo(): PageInfo | null {
if ((this.options.query as Record<string, unknown>)?.['before_id']) {
// in reverse
const firstId = this.first_id;
if (!firstId) {
return null;
}

return {
params: {
before_id: firstId,
},
};
}

const cursor = this.last_id;
if (!cursor) {
return null;
}

return {
params: {
after_id: cursor,
},
};
}
}
Loading

0 comments on commit 1c404b2

Please sign in to comment.