Skip to content

Commit

Permalink
feat(api): add tool_choice param, image block params inside `tool_r…
Browse files Browse the repository at this point in the history
…esult.content`, and streaming for `tool_use` blocks (#418)
  • Loading branch information
stainless-bot authored and stainless-app[bot] committed May 16, 2024
1 parent fe3c2ba commit 2c7cc70
Show file tree
Hide file tree
Showing 12 changed files with 1,323 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .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-4742de59ec06077403336bc26e26390e57888e5eef313bf27eab241dbb905f06.yml
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-0017013a270564e5cdfb7b8ffe474c962f4b806c862cbcc33c905504897fabbe.yml
5 changes: 5 additions & 0 deletions api.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,19 @@ Methods:

Types:

- <code><a href="./src/resources/beta/tools/messages.ts">InputJsonDelta</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">Tool</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolResultBlockParam</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolUseBlock</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolUseBlockParam</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlock</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlockDeltaEvent</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlockStartEvent</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessage</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessageParam</a></code>
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessageStreamEvent</a></code>

Methods:

- <code title="post /v1/messages?beta=tools">client.beta.tools.messages.<a href="./src/resources/beta/tools/messages.ts">create</a>({ ...params }) -> ToolsBetaMessage</code>
- <code>client.beta.tools.messages.<a href="./src/resources/beta/tools/messages.ts">stream</a>(body, options?) -> ToolsBetaMessageStream</code>
50 changes: 50 additions & 0 deletions examples/tools-streaming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env -S npm run tsn -T

import Anthropic from '@anthropic-ai/sdk';
import { inspect } from 'util';

// gets API Key from environment variable ANTHROPIC_API_KEY
const client = new Anthropic();

async function main() {
const stream = client.beta.tools.messages
.stream({
messages: [
{
role: 'user',
content: `What is the weather in SF?`,
},
],
tools: [
{
name: 'get_weather',
description: 'Get the weather at a specific location',
input_schema: {
type: 'object',
properties: {
location: { type: 'string', description: 'The city and state, e.g. San Francisco, CA' },
unit: {
type: 'string',
enum: ['celsius', 'fahrenheit'],
description: 'Unit for the output',
},
},
required: ['location'],
},
},
],
model: 'claude-3-haiku-20240307',
max_tokens: 1024,
})
// When a JSON content block delta is encountered this
// event will be fired with the delta and the currently accumulated object
.on('inputJson', (delta, snapshot) => {
console.log(`delta: ${delta}`);
console.log(`snapshot: ${inspect(snapshot)}`);
console.log();
});

await stream.done();
}

main();
3 changes: 3 additions & 0 deletions src/_vendor/partial-json-parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Partial JSON Parser

Vendored from https://www.npmjs.com/package/partial-json-parser and updated to use TypeScript.
262 changes: 262 additions & 0 deletions src/_vendor/partial-json-parser/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
type Token = {
type: string;
value: string;
};

const tokenize = (input: string) => {
let current = 0;
let tokens = [];

while (current < input.length) {
let char = input[current];

if (char === '\\') {
current++;
continue;
}

if (char === '{') {
tokens.push({
type: 'brace',
value: '{',
});

current++;
continue;
}

if (char === '}') {
tokens.push({
type: 'brace',
value: '}',
});

current++;
continue;
}

if (char === '[') {
tokens.push({
type: 'paren',
value: '[',
});

current++;
continue;
}

if (char === ']') {
tokens.push({
type: 'paren',
value: ']',
});

current++;
continue;
}

if (char === ':') {
tokens.push({
type: 'separator',
value: ':',
});

current++;
continue;
}

if (char === ',') {
tokens.push({
type: 'delimiter',
value: ',',
});

current++;
continue;
}

if (char === '"') {
let value = '';
let danglingQuote = false;

char = input[++current];

while (char !== '"') {
if (current === input.length) {
danglingQuote = true;
break;
}

if (char === '\\') {
current++;
if (current === input.length) {
danglingQuote = true;
break;
}
value += char + input[current];
char = input[++current];
} else {
value += char;
char = input[++current];
}
}

char = input[++current];

if (!danglingQuote) {
tokens.push({
type: 'string',
value,
});
}
continue;
}

let WHITESPACE = /\s/;
if (char && WHITESPACE.test(char)) {
current++;
continue;
}

let NUMBERS = /[0-9]/;
if ((char && NUMBERS.test(char)) || char === '-' || char === '.') {
let value = '';

if (char === '-') {
value += char;
char = input[++current];
}

while ((char && NUMBERS.test(char)) || char === '.') {
value += char;
char = input[++current];
}

tokens.push({
type: 'number',
value,
});
continue;
}

let LETTERS = /[a-z]/i;
if (char && LETTERS.test(char)) {
let value = '';

while (char && LETTERS.test(char)) {
if (current === input.length) {
break;
}
value += char;
char = input[++current];
}

if (value == 'true' || value == 'false') {
tokens.push({
type: 'name',
value,
});
} else {
throw new Error(`Invalid token: ${value} is not a valid token!`);
}
continue;
}

current++;
}

return tokens;
},
strip = (tokens: Token[]): Token[] => {
if (tokens.length === 0) {
return tokens;
}

let lastToken = tokens[tokens.length - 1]!;

switch (lastToken.type) {
case 'separator':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
case 'number':
let lastCharacterOfLastToken = lastToken.value[lastToken.value.length - 1];
if (lastCharacterOfLastToken === '.' || lastCharacterOfLastToken === '-') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
case 'string':
let tokenBeforeTheLastToken = tokens[tokens.length - 2];
if (tokenBeforeTheLastToken?.type === 'delimiter') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
} else if (tokenBeforeTheLastToken?.type === 'brace' && tokenBeforeTheLastToken.value === '{') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
break;
case 'delimiter':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
}

return tokens;
},
unstrip = (tokens: Token[]): Token[] => {
let tail: string[] = [];

tokens.map((token) => {
if (token.type === 'brace') {
if (token.value === '{') {
tail.push('}');
} else {
tail.splice(tail.lastIndexOf('}'), 1);
}
}
if (token.type === 'paren') {
if (token.value === '[') {
tail.push(']');
} else {
tail.splice(tail.lastIndexOf(']'), 1);
}
}
});

if (tail.length > 0) {
tail.reverse().map((item) => {
if (item === '}') {
tokens.push({
type: 'brace',
value: '}',
});
} else if (item === ']') {
tokens.push({
type: 'paren',
value: ']',
});
}
});
}

return tokens;
},
generate = (tokens: Token[]): string => {
let output = '';

tokens.map((token) => {
switch (token.type) {
case 'string':
output += '"' + token.value + '"';
break;
default:
output += token.value;
break;
}
});

return output;
},
partialParse = (input: string): unknown => JSON.parse(generate(unstrip(strip(tokenize(input)))));

export { partialParse };
Loading

0 comments on commit 2c7cc70

Please sign in to comment.