-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat (ai/svelte): add useAssistant (#1593)
Co-authored-by: Jake Hall <[email protected]>
- Loading branch information
1 parent
7588999
commit 18a9655
Showing
11 changed files
with
547 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'ai': patch | ||
--- | ||
|
||
feat (ai/svelte): add useAssistant |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
examples/sveltekit-openai/src/routes/api/assistant/+server.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import type { RequestHandler } from './$types'; | ||
|
||
import { env } from '$env/dynamic/private'; | ||
|
||
import { AssistantResponse } from 'ai'; | ||
import OpenAI from 'openai'; | ||
|
||
const openai = new OpenAI({ | ||
apiKey: env.OPENAI_API_KEY || '', | ||
}); | ||
|
||
const homeTemperatures = { | ||
bedroom: 20, | ||
'home office': 21, | ||
'living room': 21, | ||
kitchen: 22, | ||
bathroom: 23, | ||
}; | ||
|
||
export const POST = (async ({ request }) => { | ||
// Parse the request body | ||
const input: { | ||
threadId: string | null; | ||
message: string; | ||
} = await request.json(); | ||
|
||
// Create a thread if needed | ||
const threadId = input.threadId ?? (await openai.beta.threads.create({})).id; | ||
|
||
// Add a message to the thread | ||
const createdMessage = await openai.beta.threads.messages.create(threadId, { | ||
role: 'user', | ||
content: input.message, | ||
}); | ||
|
||
return AssistantResponse( | ||
{ threadId, messageId: createdMessage.id }, | ||
async ({ forwardStream, sendDataMessage }) => { | ||
// Run the assistant on the thread | ||
const runStream = openai.beta.threads.runs.stream(threadId, { | ||
assistant_id: | ||
env.ASSISTANT_ID ?? | ||
(() => { | ||
throw new Error('ASSISTANT_ID is not set'); | ||
})(), | ||
}); | ||
|
||
// forward run status would stream message deltas | ||
let runResult = await forwardStream(runStream); | ||
|
||
// status can be: queued, in_progress, requires_action, cancelling, cancelled, failed, completed, or expired | ||
while ( | ||
runResult?.status === 'requires_action' && | ||
runResult.required_action?.type === 'submit_tool_outputs' | ||
) { | ||
const tool_outputs = | ||
runResult.required_action.submit_tool_outputs.tool_calls.map( | ||
(toolCall: any) => { | ||
const parameters = JSON.parse(toolCall.function.arguments); | ||
|
||
switch (toolCall.function.name) { | ||
case 'getRoomTemperature': { | ||
const temperature = | ||
homeTemperatures[ | ||
parameters.room as keyof typeof homeTemperatures | ||
]; | ||
|
||
return { | ||
tool_call_id: toolCall.id, | ||
output: temperature.toString(), | ||
}; | ||
} | ||
|
||
case 'setRoomTemperature': { | ||
const oldTemperature = | ||
homeTemperatures[ | ||
parameters.room as keyof typeof homeTemperatures | ||
]; | ||
|
||
homeTemperatures[ | ||
parameters.room as keyof typeof homeTemperatures | ||
] = parameters.temperature; | ||
|
||
sendDataMessage({ | ||
role: 'data', | ||
data: { | ||
oldTemperature, | ||
newTemperature: parameters.temperature, | ||
description: `Temperature in ${parameters.room} changed from ${oldTemperature} to ${parameters.temperature}`, | ||
}, | ||
}); | ||
|
||
return { | ||
tool_call_id: toolCall.id, | ||
output: `temperature set successfully`, | ||
}; | ||
} | ||
|
||
default: | ||
throw new Error( | ||
`Unknown tool call function: ${toolCall.function.name}`, | ||
); | ||
} | ||
}, | ||
); | ||
|
||
runResult = await forwardStream( | ||
openai.beta.threads.runs.submitToolOutputsStream( | ||
threadId, | ||
runResult.id, | ||
{ tool_outputs }, | ||
), | ||
); | ||
} | ||
}, | ||
); | ||
}) satisfies RequestHandler; |
63 changes: 63 additions & 0 deletions
63
examples/sveltekit-openai/src/routes/api/assistant/assistant-setup.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Home Automation Assistant Example | ||
|
||
## Setup | ||
|
||
### Create OpenAI Assistant | ||
|
||
[OpenAI Assistant Website](https://platform.openai.com/assistants) | ||
|
||
Create a new assistant. Enable Code interpreter. Add the following functions and instructions to the assistant. | ||
|
||
Then add the assistant id to the `.env` file as `ASSISTANT_ID=your-assistant-id`. | ||
|
||
### Instructions | ||
|
||
``` | ||
You are an assistant with access to a home automation system. You can get and set the temperature in the bedroom, home office, living room, kitchen and bathroom. | ||
The system uses temperature in Celsius. If the user requests Fahrenheit, you should convert the temperature to Fahrenheit. | ||
``` | ||
|
||
### getRoomTemperature function | ||
|
||
```json | ||
{ | ||
"name": "getRoomTemperature", | ||
"description": "Get the temperature in a room", | ||
"parameters": { | ||
"type": "object", | ||
"properties": { | ||
"room": { | ||
"type": "string", | ||
"enum": ["bedroom", "home office", "living room", "kitchen", "bathroom"] | ||
} | ||
}, | ||
"required": ["room"] | ||
} | ||
} | ||
``` | ||
|
||
### setRoomTemperature function | ||
|
||
```json | ||
{ | ||
"name": "setRoomTemperature", | ||
"description": "Set the temperature in a room", | ||
"parameters": { | ||
"type": "object", | ||
"properties": { | ||
"room": { | ||
"type": "string", | ||
"enum": ["bedroom", "home office", "living room", "kitchen", "bathroom"] | ||
}, | ||
"temperature": { "type": "number" } | ||
}, | ||
"required": ["room", "temperature"] | ||
} | ||
} | ||
``` | ||
|
||
## Run | ||
|
||
1. Run `pnpm dev` in `examples/sveltekit-openai` | ||
2. Go to http://localhost:5173/assistant |
46 changes: 46 additions & 0 deletions
46
examples/sveltekit-openai/src/routes/assistant/+page.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<script> | ||
import { useAssistant } from 'ai/svelte' | ||
const { messages, input, submitMessage } = useAssistant({ | ||
api: '/api/assistant', | ||
}); | ||
</script> | ||
|
||
<svelte:head> | ||
<title>Home</title> | ||
<meta name="description" content="Svelte demo app" /> | ||
</svelte:head> | ||
|
||
<section> | ||
<h1>useAssistant</h1> | ||
<ul> | ||
{#each $messages as m} | ||
<strong>{m.role}</strong> | ||
{#if m.role !== 'data'} | ||
{m.content} | ||
{/if} | ||
{#if m.role === 'data'} | ||
<pre>{JSON.stringify(m.data, null, 2)}}</pre> | ||
{/if} | ||
<br/> | ||
<br/> | ||
{/each} | ||
</ul> | ||
<form on:submit={submitMessage}> | ||
<input bind:value={$input} /> | ||
<button type="submit">Send</button> | ||
</form> | ||
</section> | ||
|
||
<style> | ||
section { | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: center; | ||
align-items: center; | ||
flex: 0.6; | ||
} | ||
h1 { | ||
width: 100%; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
// Define a type for the assistant status | ||
export type AssistantStatus = 'in_progress' | 'awaiting_message'; | ||
|
||
export type UseAssistantOptions = { | ||
/** | ||
* The API endpoint that accepts a `{ threadId: string | null; message: string; }` object and returns an `AssistantResponse` stream. | ||
* The threadId refers to an existing thread with messages (or is `null` to create a new thread). | ||
* The message is the next message that should be appended to the thread and sent to the assistant. | ||
*/ | ||
api: string; | ||
|
||
/** | ||
* An optional string that represents the ID of an existing thread. | ||
* If not provided, a new thread will be created. | ||
*/ | ||
threadId?: string; | ||
|
||
/** | ||
* An optional literal that sets the mode of credentials to be used on the request. | ||
* Defaults to "same-origin". | ||
*/ | ||
credentials?: RequestCredentials; | ||
|
||
/** | ||
* An optional object of headers to be passed to the API endpoint. | ||
*/ | ||
headers?: Record<string, string> | Headers; | ||
|
||
/** | ||
* An optional, additional body object to be passed to the API endpoint. | ||
*/ | ||
body?: object; | ||
|
||
/** | ||
* An optional callback that will be called when the assistant encounters an error. | ||
*/ | ||
onError?: (error: Error) => void; | ||
}; |
Oops, something went wrong.