Skip to content

Commit fbf307e

Browse files
committed
feat(api): add tool use param & image block params (#418)
1 parent 7a042d2 commit fbf307e

File tree

12 files changed

+1323
-63
lines changed

12 files changed

+1323
-63
lines changed

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
configured_endpoints: 3
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-4742de59ec06077403336bc26e26390e57888e5eef313bf27eab241dbb905f06.yml
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/anthropic-0017013a270564e5cdfb7b8ffe474c962f4b806c862cbcc33c905504897fabbe.yml

api.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,19 @@ Methods:
3434

3535
Types:
3636

37+
- <code><a href="./src/resources/beta/tools/messages.ts">InputJsonDelta</a></code>
3738
- <code><a href="./src/resources/beta/tools/messages.ts">Tool</a></code>
3839
- <code><a href="./src/resources/beta/tools/messages.ts">ToolResultBlockParam</a></code>
3940
- <code><a href="./src/resources/beta/tools/messages.ts">ToolUseBlock</a></code>
4041
- <code><a href="./src/resources/beta/tools/messages.ts">ToolUseBlockParam</a></code>
4142
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlock</a></code>
43+
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlockDeltaEvent</a></code>
44+
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaContentBlockStartEvent</a></code>
4245
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessage</a></code>
4346
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessageParam</a></code>
47+
- <code><a href="./src/resources/beta/tools/messages.ts">ToolsBetaMessageStreamEvent</a></code>
4448

4549
Methods:
4650

4751
- <code title="post /v1/messages?beta=tools">client.beta.tools.messages.<a href="./src/resources/beta/tools/messages.ts">create</a>({ ...params }) -> ToolsBetaMessage</code>
52+
- <code>client.beta.tools.messages.<a href="./src/resources/beta/tools/messages.ts">stream</a>(body, options?) -> ToolsBetaMessageStream</code>

examples/tools-streaming.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env -S npm run tsn -T
2+
3+
import Anthropic from '@anthropic-ai/sdk';
4+
import { inspect } from 'util';
5+
6+
// gets API Key from environment variable ANTHROPIC_API_KEY
7+
const client = new Anthropic();
8+
9+
async function main() {
10+
const stream = client.beta.tools.messages
11+
.stream({
12+
messages: [
13+
{
14+
role: 'user',
15+
content: `What is the weather in SF?`,
16+
},
17+
],
18+
tools: [
19+
{
20+
name: 'get_weather',
21+
description: 'Get the weather at a specific location',
22+
input_schema: {
23+
type: 'object',
24+
properties: {
25+
location: { type: 'string', description: 'The city and state, e.g. San Francisco, CA' },
26+
unit: {
27+
type: 'string',
28+
enum: ['celsius', 'fahrenheit'],
29+
description: 'Unit for the output',
30+
},
31+
},
32+
required: ['location'],
33+
},
34+
},
35+
],
36+
model: 'claude-3-haiku-20240307',
37+
max_tokens: 1024,
38+
})
39+
// When a JSON content block delta is encountered this
40+
// event will be fired with the delta and the currently accumulated object
41+
.on('inputJson', (delta, snapshot) => {
42+
console.log(`delta: ${delta}`);
43+
console.log(`snapshot: ${inspect(snapshot)}`);
44+
console.log();
45+
});
46+
47+
await stream.done();
48+
}
49+
50+
main();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Partial JSON Parser
2+
3+
Vendored from https://www.npmjs.com/package/partial-json-parser and updated to use TypeScript.
Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,262 @@
1+
type Token = {
2+
type: string;
3+
value: string;
4+
};
5+
6+
const tokenize = (input: string) => {
7+
let current = 0;
8+
let tokens = [];
9+
10+
while (current < input.length) {
11+
let char = input[current];
12+
13+
if (char === '\\') {
14+
current++;
15+
continue;
16+
}
17+
18+
if (char === '{') {
19+
tokens.push({
20+
type: 'brace',
21+
value: '{',
22+
});
23+
24+
current++;
25+
continue;
26+
}
27+
28+
if (char === '}') {
29+
tokens.push({
30+
type: 'brace',
31+
value: '}',
32+
});
33+
34+
current++;
35+
continue;
36+
}
37+
38+
if (char === '[') {
39+
tokens.push({
40+
type: 'paren',
41+
value: '[',
42+
});
43+
44+
current++;
45+
continue;
46+
}
47+
48+
if (char === ']') {
49+
tokens.push({
50+
type: 'paren',
51+
value: ']',
52+
});
53+
54+
current++;
55+
continue;
56+
}
57+
58+
if (char === ':') {
59+
tokens.push({
60+
type: 'separator',
61+
value: ':',
62+
});
63+
64+
current++;
65+
continue;
66+
}
67+
68+
if (char === ',') {
69+
tokens.push({
70+
type: 'delimiter',
71+
value: ',',
72+
});
73+
74+
current++;
75+
continue;
76+
}
77+
78+
if (char === '"') {
79+
let value = '';
80+
let danglingQuote = false;
81+
82+
char = input[++current];
83+
84+
while (char !== '"') {
85+
if (current === input.length) {
86+
danglingQuote = true;
87+
break;
88+
}
89+
90+
if (char === '\\') {
91+
current++;
92+
if (current === input.length) {
93+
danglingQuote = true;
94+
break;
95+
}
96+
value += char + input[current];
97+
char = input[++current];
98+
} else {
99+
value += char;
100+
char = input[++current];
101+
}
102+
}
103+
104+
char = input[++current];
105+
106+
if (!danglingQuote) {
107+
tokens.push({
108+
type: 'string',
109+
value,
110+
});
111+
}
112+
continue;
113+
}
114+
115+
let WHITESPACE = /\s/;
116+
if (char && WHITESPACE.test(char)) {
117+
current++;
118+
continue;
119+
}
120+
121+
let NUMBERS = /[0-9]/;
122+
if ((char && NUMBERS.test(char)) || char === '-' || char === '.') {
123+
let value = '';
124+
125+
if (char === '-') {
126+
value += char;
127+
char = input[++current];
128+
}
129+
130+
while ((char && NUMBERS.test(char)) || char === '.') {
131+
value += char;
132+
char = input[++current];
133+
}
134+
135+
tokens.push({
136+
type: 'number',
137+
value,
138+
});
139+
continue;
140+
}
141+
142+
let LETTERS = /[a-z]/i;
143+
if (char && LETTERS.test(char)) {
144+
let value = '';
145+
146+
while (char && LETTERS.test(char)) {
147+
if (current === input.length) {
148+
break;
149+
}
150+
value += char;
151+
char = input[++current];
152+
}
153+
154+
if (value == 'true' || value == 'false') {
155+
tokens.push({
156+
type: 'name',
157+
value,
158+
});
159+
} else {
160+
throw new Error(`Invalid token: ${value} is not a valid token!`);
161+
}
162+
continue;
163+
}
164+
165+
current++;
166+
}
167+
168+
return tokens;
169+
},
170+
strip = (tokens: Token[]): Token[] => {
171+
if (tokens.length === 0) {
172+
return tokens;
173+
}
174+
175+
let lastToken = tokens[tokens.length - 1]!;
176+
177+
switch (lastToken.type) {
178+
case 'separator':
179+
tokens = tokens.slice(0, tokens.length - 1);
180+
return strip(tokens);
181+
break;
182+
case 'number':
183+
let lastCharacterOfLastToken = lastToken.value[lastToken.value.length - 1];
184+
if (lastCharacterOfLastToken === '.' || lastCharacterOfLastToken === '-') {
185+
tokens = tokens.slice(0, tokens.length - 1);
186+
return strip(tokens);
187+
}
188+
case 'string':
189+
let tokenBeforeTheLastToken = tokens[tokens.length - 2];
190+
if (tokenBeforeTheLastToken?.type === 'delimiter') {
191+
tokens = tokens.slice(0, tokens.length - 1);
192+
return strip(tokens);
193+
} else if (tokenBeforeTheLastToken?.type === 'brace' && tokenBeforeTheLastToken.value === '{') {
194+
tokens = tokens.slice(0, tokens.length - 1);
195+
return strip(tokens);
196+
}
197+
break;
198+
case 'delimiter':
199+
tokens = tokens.slice(0, tokens.length - 1);
200+
return strip(tokens);
201+
break;
202+
}
203+
204+
return tokens;
205+
},
206+
unstrip = (tokens: Token[]): Token[] => {
207+
let tail: string[] = [];
208+
209+
tokens.map((token) => {
210+
if (token.type === 'brace') {
211+
if (token.value === '{') {
212+
tail.push('}');
213+
} else {
214+
tail.splice(tail.lastIndexOf('}'), 1);
215+
}
216+
}
217+
if (token.type === 'paren') {
218+
if (token.value === '[') {
219+
tail.push(']');
220+
} else {
221+
tail.splice(tail.lastIndexOf(']'), 1);
222+
}
223+
}
224+
});
225+
226+
if (tail.length > 0) {
227+
tail.reverse().map((item) => {
228+
if (item === '}') {
229+
tokens.push({
230+
type: 'brace',
231+
value: '}',
232+
});
233+
} else if (item === ']') {
234+
tokens.push({
235+
type: 'paren',
236+
value: ']',
237+
});
238+
}
239+
});
240+
}
241+
242+
return tokens;
243+
},
244+
generate = (tokens: Token[]): string => {
245+
let output = '';
246+
247+
tokens.map((token) => {
248+
switch (token.type) {
249+
case 'string':
250+
output += '"' + token.value + '"';
251+
break;
252+
default:
253+
output += token.value;
254+
break;
255+
}
256+
});
257+
258+
return output;
259+
},
260+
partialParse = (input: string): unknown => JSON.parse(generate(unstrip(strip(tokenize(input)))));
261+
262+
export { partialParse };

0 commit comments

Comments
 (0)