Skip to content

Commit 525cd33

Browse files
authored
Chad/0.1.0/final edits (#13)
* add upgrade cta to studio header, fix bug with llm_log failure in SDK, add reasoning token support * bump sdk version from 0.0.5 to 0.0.6 * fix updated_at search path (thanks supabase), consolidated all streaming prompt accumulation, added resoningTokens to streaming response, adjusted sdk reference docs, added manifesto to docs * add reasoning to ExecuteNonStreamingResult reference
1 parent 9530537 commit 525cd33

File tree

22 files changed

+621
-242
lines changed

22 files changed

+621
-242
lines changed

content/docs/core-concepts/manifesto.mdx

Lines changed: 236 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
22
"title": "Core Concepts",
3-
"pages": ["architecture", "self-hosting"]
3+
"pages": ["manifesto", "architecture", "self-hosting"]
44
}

content/docs/index.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Agentsmith is an open-source platform that brings the rigor of software developm
2020
</CardHeader>
2121
</Card>
2222
</a>
23-
<a href="/docs/core-concepts/architecture" className="h-full no-underline group">
23+
<a href="/docs/core-concepts/manifesto" className="h-full no-underline group">
2424
<Card className="h-full transition-shadow duration-200 group-hover:shadow-lg">
2525
<CardHeader>
2626
<Box className="h-8 w-8 mb-2 text-primary" />

content/docs/sdk/advanced-usage.mdx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,29 @@ console.log(''); // Newline at the end
2424
await client.shutdown();
2525
```
2626

27+
You can also access the reasoning tokens and tool calls as they arrive.
28+
29+
```ts
30+
const { reasoningTokens, toolCalls } = await helloWorldPrompt.execute(
31+
{ firstName: 'Jane', lastName: 'Doe' },
32+
{ config: { stream: true } },
33+
);
34+
35+
console.log('Streaming reasoning:');
36+
for await (const reasoningToken of reasoningTokens) {
37+
process.stdout.write(reasoningToken);
38+
}
39+
40+
console.log(''); // Newline at the end
41+
42+
console.log('Streaming tool calls:');
43+
for await (const toolCall of toolCalls) {
44+
console.log(toolCall);
45+
}
46+
47+
await client.shutdown();
48+
```
49+
2750
### Overriding Model Configuration
2851

2952
You can override the default model configuration set in the Studio on a per-call basis. This is useful for A/B testing different models, adjusting parameters for specific users, or dynamically changing behavior.

content/docs/sdk/reference.mdx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ Executes the prompt with the given variables. The `execute` method returns an ob
165165
'The AI response content, equivalent to `completion.choices[0].message.content`.',
166166
type: 'string | null',
167167
},
168+
reasoning: {
169+
description:
170+
'The AI response reasoning, equivalent to `completion.choices[0].message.reasoning`.',
171+
type: 'string | null',
172+
},
168173
completion: {
169174
description: 'The full OpenRouter completion object.',
170175
type: 'OpenrouterNonStreamingResponse',
@@ -196,6 +201,10 @@ Executes the prompt with the given variables. The `execute` method returns an ob
196201
description: 'An async generator that yields content tokens as strings.',
197202
type: 'AsyncGenerator<string | undefined, void, unknown>',
198203
},
204+
reasoningTokens: {
205+
description: 'An async generator that yields reasoning tokens as strings.',
206+
type: 'AsyncGenerator<string | undefined, void, unknown>',
207+
},
199208
toolCalls: {
200209
description: 'An async generator that yields tool calls as they arrive.',
201210
type: 'AsyncGenerator<ToolCall, void, unknown>',

src/app/(api)/api/[apiVersion]/promptVersion/[promptVersionUuid]/execute/route.tsx

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import { CompletionConfig } from '@/lib/openrouter';
77
import { AgentsmithServices } from '@/lib/AgentsmithServices';
88
import { validateGlobalContext, validateVariables } from '@/utils/template-utils';
99
import merge from 'lodash.merge';
10-
import { streamToIterator } from '@/utils/stream-to-iterator';
10+
import { OpenrouterStreamEvent, streamToIterator } from '@/utils/stream-to-iterator';
1111
import { LLMLogsService } from '@/lib/LLMLogsService';
1212
import { mergeIncludedVariables } from '@/utils/merge-included-variables';
13+
import { accumulateStreamToCompletion } from '@/utils/accumulate-stream';
1314

1415
export const maxDuration = 320; // 5m20s minute function timeout
1516

@@ -146,31 +147,10 @@ export async function POST(
146147
const [streamForClient, streamForLogging] = response.stream.tee();
147148

148149
const logStreamedCompletion = async () => {
149-
let fullCompletion: any = {};
150-
let content = '';
151-
152150
try {
153-
const stream = streamToIterator(streamForLogging);
154-
for await (const event of stream) {
155-
if (event.type === 'logUuid') {
156-
response.logUuid = event.data.logUuid;
157-
} else {
158-
const chunk = event.data;
159-
// usage chunk contains null stop values we don't want to merge
160-
if (chunk.usage) {
161-
fullCompletion.usage = merge(fullCompletion.usage, chunk.usage);
162-
} else if (chunk.choices) {
163-
content += chunk.choices[0].delta.content ?? '';
164-
fullCompletion = merge(fullCompletion, chunk);
165-
}
166-
}
167-
}
168-
169-
// rewrite delta to message
170-
if (fullCompletion.choices?.[0]) {
171-
delete fullCompletion.choices[0].delta;
172-
fullCompletion.choices[0].message = { role: 'assistant', content };
173-
}
151+
const fullCompletion = await accumulateStreamToCompletion(
152+
streamToIterator<OpenrouterStreamEvent>(streamForLogging),
153+
);
174154

175155
await agentsmith.services.llmLogs.updateLogWithCompletion(
176156
response.logUuid,
@@ -179,7 +159,7 @@ export async function POST(
179159
} catch (error) {
180160
agentsmith.logger.error(error, 'Error logging streamed completion');
181161
await agentsmith.services.llmLogs.updateLogWithCompletion(response.logUuid, {
182-
error: 'Failed to log stream',
162+
error: `Failed to log stream: ${error instanceof Error ? error.message : 'Unknown error'}`,
183163
});
184164
}
185165
};

src/app/(api)/api/[apiVersion]/sdk-exchange/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ export async function POST(request: Request) {
2626

2727
let jwt: string | null = null;
2828
let expiresAt: number | null = null;
29+
let organizationUuid: string | null = null;
2930

3031
try {
3132
const exchangeResult = await exchangeApiKeyForJwt(apiKey);
3233
jwt = exchangeResult.jwt;
3334
expiresAt = exchangeResult.expiresAt;
35+
organizationUuid = exchangeResult.organizationUuid;
3436
} catch (error) {
3537
logger.error(error, 'Failed to exchange API key for JWT');
3638
return NextResponse.json(
@@ -44,6 +46,7 @@ export async function POST(request: Request) {
4446
const response: SdkExchangeResponse = {
4547
jwt,
4648
expiresAt,
49+
organizationUuid,
4750
};
4851

4952
return NextResponse.json(response);

src/components/modals/test-prompt.tsx

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useState } from 'react';
44
import Link from 'next/link';
55
import { useApp } from '@/providers/app';
6-
import { NonStreamingChoice } from '@/lib/openrouter';
6+
import { NonStreamingChoice, OpenrouterNonStreamingResponse } from '@/lib/openrouter';
77
import { connectOpenrouter } from '@/app/actions/openrouter';
88
import { routes } from '@/utils/routes';
99
import { Button } from '@/components/ui/button';
@@ -17,8 +17,7 @@ import {
1717
} from '@/components/ui/dialog';
1818
import { Alert, AlertDescription } from '@/components/ui/alert';
1919
import { Card, CardContent } from '@/components/ui/card';
20-
import merge from 'lodash.merge';
21-
import { streamToIterator } from '@/utils/stream-to-iterator';
20+
import { OpenrouterStreamEvent, StreamEvent, streamToIterator } from '@/utils/stream-to-iterator';
2221
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
2322
import { MarkdownEditor } from '../editors/markdown-editor';
2423
import { MarkdownRenderer } from '../markdown-renderer';
@@ -29,6 +28,7 @@ import { VariableInput } from '../variable-input';
2928
import { JsonEditor } from '../editors/json-editor';
3029
import { Loader2 } from 'lucide-react';
3130
import { toast } from 'sonner';
31+
import { accumulateStreamToCompletion } from '@/utils/accumulate-stream';
3232

3333
const TABS = {
3434
content: 'content',
@@ -58,8 +58,12 @@ export const PromptTestModal = () => {
5858
} = state;
5959

6060
const [isRunning, setIsRunning] = useState(false);
61-
const [testResult, setTestResult] = useState<string | null>(null);
62-
const [fullResult, setFullResult] = useState<any | null>(null);
61+
const [completionContent, setCompletionContent] = useState<string | null>(null);
62+
const [reasoningContent, setReasoningContent] = useState<string | null>(null);
63+
const [fullResult, setFullResult] = useState<{
64+
completion?: OpenrouterNonStreamingResponse;
65+
logUuid?: string;
66+
} | null>(null);
6367
const [testError, setTestError] = useState<string | null>(null);
6468
const [selectedTab, setSelectedTab] = useState<Tab>(TABS.content);
6569

@@ -70,7 +74,7 @@ export const PromptTestModal = () => {
7074

7175
const handleTestPrompt = async () => {
7276
setIsRunning(true);
73-
setTestResult(null);
77+
setCompletionContent(null);
7478
setFullResult(null);
7579
setTestError(null);
7680

@@ -106,38 +110,34 @@ export const PromptTestModal = () => {
106110
}
107111

108112
if (editorConfig.stream && response.body) {
109-
let fullResult: any = {};
110-
let content = '';
111-
112113
try {
113-
const stream = streamToIterator(response.body);
114-
for await (const event of stream) {
114+
const [streamA, streamB] = response.body.tee();
115+
116+
const iterator = streamToIterator<StreamEvent>(streamA);
117+
118+
for await (const event of iterator) {
115119
if (event.type === 'logUuid') {
116120
if (event.data.logUuid) {
117-
fullResult.logUuid = event.data.logUuid;
121+
setFullResult((prev) => ({ ...prev, logUuid: event.data.logUuid }));
118122
}
119-
} else {
120-
const chunk = event.data;
121-
// usage chunk contains null stop values we don't want to merge
122-
if (chunk.usage) {
123-
fullResult.completion.usage = merge(fullResult.completion.usage, chunk.usage);
124-
} else if (chunk.choices) {
125-
content += chunk.choices[0].delta.content ?? '';
126-
setTestResult(content);
127-
}
128-
fullResult.completion = merge(fullResult.completion, chunk);
123+
}
124+
if (event.type === 'message' && event.data.choices?.[0]?.delta?.content) {
125+
setCompletionContent(
126+
(prev) => (prev ?? '') + (event.data.choices[0].delta.content ?? ''),
127+
);
128+
}
129+
if (event.type === 'message' && event.data.choices?.[0]?.delta?.reasoning) {
130+
setReasoningContent(
131+
(prev) => (prev ?? '') + (event.data.choices[0].delta.reasoning ?? ''),
132+
);
129133
}
130134
}
131135

132-
if (fullResult.completion.choices?.[0]) {
133-
delete fullResult.completion.choices[0].delta;
134-
fullResult.completion.choices[0].message = {
135-
role: 'assistant',
136-
content,
137-
};
138-
}
136+
const completion = await accumulateStreamToCompletion(
137+
streamToIterator<OpenrouterStreamEvent>(streamB),
138+
);
139139

140-
setFullResult(fullResult);
140+
setFullResult((prev) => ({ ...prev, completion }));
141141
} catch (error) {
142142
console.error('Error testing prompt:', error);
143143
setTestError(error instanceof Error ? error.message : 'Unknown error occurred');
@@ -150,10 +150,17 @@ export const PromptTestModal = () => {
150150

151151
const data = await response.json();
152152

153-
const result =
154-
(data.completion.choices[0] as NonStreamingChoice).message.content || 'No response content';
153+
const choice = data.completion.choices[0] as NonStreamingChoice;
154+
155+
const result = choice.message.content || 'No response content';
156+
const reasoning = choice.message.reasoning;
157+
158+
setCompletionContent(result);
159+
160+
if (reasoning) {
161+
setReasoningContent(reasoning);
162+
}
155163

156-
setTestResult(result);
157164
setFullResult(data);
158165
} catch (error) {
159166
console.error('Error testing prompt:', error);
@@ -166,7 +173,8 @@ export const PromptTestModal = () => {
166173

167174
const resetTest = () => {
168175
setInputVariables({});
169-
setTestResult(null);
176+
setCompletionContent(null);
177+
setReasoningContent(null);
170178
setFullResult(null);
171179
setTestError(null);
172180
};
@@ -219,8 +227,8 @@ export const PromptTestModal = () => {
219227

220228
let isContentJson = false;
221229
try {
222-
JSON.parse(testResult as string);
223-
isContentJson = true;
230+
const parsed = JSON.parse(completionContent as string);
231+
if (parsed !== null && parsed !== undefined) isContentJson = true;
224232
} catch (error) {
225233
//
226234
}
@@ -271,7 +279,7 @@ export const PromptTestModal = () => {
271279
>
272280
{isRunning ? 'Running...' : 'Run Test'}
273281
</Button>
274-
{testResult && (
282+
{completionContent && (
275283
<Button onClick={resetTest} variant="outline">
276284
Reset
277285
</Button>
@@ -294,7 +302,7 @@ export const PromptTestModal = () => {
294302
)}
295303
</div>
296304

297-
{testResult ? (
305+
{completionContent || reasoningContent ? (
298306
<Tabs
299307
value={selectedTab}
300308
onValueChange={(value) => setSelectedTab(value as Tab)}
@@ -316,29 +324,53 @@ export const PromptTestModal = () => {
316324
/>
317325
</TabsContent>
318326
<TabsContent value={TABS.content} className="flex-1 overflow-auto">
327+
{reasoningContent && (
328+
<>
329+
<h3 className="font-medium text-lg">Reasoning</h3>
330+
<MarkdownEditor
331+
className="mb-4"
332+
value={reasoningContent}
333+
readOnly
334+
minHeight="100%"
335+
/>
336+
</>
337+
)}
338+
{completionContent && reasoningContent && (
339+
<h3 className="font-medium text-lg">Completion</h3>
340+
)}
319341
{isContentJson ? (
320342
<JsonEditor
321-
value={JSON.parse(testResult as string)}
343+
value={JSON.parse(completionContent as string)}
322344
readOnly
323345
minHeight="100%"
324346
/>
347+
) : completionContent ? (
348+
<MarkdownEditor value={completionContent} readOnly minHeight="100%" />
325349
) : (
326-
<MarkdownEditor value={testResult} readOnly minHeight="100%" />
350+
<div className="flex items-center justify-center h-full">
351+
<Loader2 className="w-4 h-4 animate-spin" />
352+
</div>
327353
)}
328354
</TabsContent>
329355
<TabsContent value={TABS.markdown} className="flex-1 overflow-auto">
330356
<div className="p-4 border rounded-md">
331-
<MarkdownRenderer>{testResult}</MarkdownRenderer>
357+
{completionContent ? (
358+
<MarkdownRenderer>{completionContent}</MarkdownRenderer>
359+
) : (
360+
<div className="flex items-center justify-center h-full">
361+
<Loader2 className="w-4 h-4 animate-spin" />
362+
</div>
363+
)}
332364
</div>
333365
</TabsContent>
334366
<TabsContent value={TABS.response} className="flex-1 overflow-auto">
335367
{isRunning ? (
336368
<div className="flex items-center justify-center h-full">
337369
<Loader2 className="w-4 h-4 animate-spin" />
338370
</div>
339-
) : (
371+
) : fullResult ? (
340372
<JsonEditor value={fullResult} readOnly minHeight="100%" />
341-
)}
373+
) : null}
342374
</TabsContent>
343375
</Tabs>
344376
) : (

src/components/sign-in-form.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,17 @@ export const SignInForm = ({ className, requiredTos = true, ...props }: SignInFo
145145
<Link href={routes.marketing.terms} className="underline">
146146
Terms of Service
147147
</Link>
148+
and the{' '}
149+
<Link href={routes.marketing.privacy} className="underline">
150+
Privacy Policy
151+
</Link>
152+
.
148153
</span>
149154
</Label>
150155
</div>
151156
{mustAgreeTos && (
152157
<p className="text-sm text-destructive mt-2 text-center">
153-
You must agree to the Terms of Service.
158+
You must agree to the Terms of Service and Privacy Policy.
154159
</p>
155160
)}
156161
</div>

0 commit comments

Comments
 (0)