-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Solid.js: Add complex response parsing and StreamData support to useC…
…hat (#738) Co-authored-by: Max Leiter <max.leiter@vercel.com>
- Loading branch information
Showing
12 changed files
with
546 additions
and
302 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 | ||
--- | ||
|
||
ai/solid: add experimental_StreamData support to useChat |
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
97 changes: 97 additions & 0 deletions
97
examples/solidstart-openai/src/routes/api/chat-with-functions/index.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,97 @@ | ||
import { | ||
OpenAIStream, | ||
StreamingTextResponse, | ||
experimental_StreamData, | ||
} from 'ai'; | ||
import OpenAI from 'openai'; | ||
import type { ChatCompletionCreateParams } from 'openai/resources/chat'; | ||
|
||
import { APIEvent } from 'solid-start/api'; | ||
|
||
// Create an OpenAI API client | ||
const openai = new OpenAI({ | ||
apiKey: process.env['OPENAI_API_KEY'] || '', | ||
}); | ||
|
||
const functions: ChatCompletionCreateParams.Function[] = [ | ||
{ | ||
name: 'get_current_weather', | ||
description: 'Get the current weather.', | ||
parameters: { | ||
type: 'object', | ||
properties: { | ||
format: { | ||
type: 'string', | ||
enum: ['celsius', 'fahrenheit'], | ||
description: 'The temperature unit to use.', | ||
}, | ||
}, | ||
required: ['format'], | ||
}, | ||
}, | ||
{ | ||
name: 'eval_code_in_browser', | ||
description: 'Execute javascript code in the browser with eval().', | ||
parameters: { | ||
type: 'object', | ||
properties: { | ||
code: { | ||
type: 'string', | ||
description: `Javascript code that will be directly executed via eval(). Do not use backticks in your response. | ||
DO NOT include any newlines in your response, and be sure to provide only valid JSON when providing the arguments object. | ||
The output of the eval() will be returned directly by the function.`, | ||
}, | ||
}, | ||
required: ['code'], | ||
}, | ||
}, | ||
]; | ||
|
||
export const POST = async (event: APIEvent) => { | ||
const { messages } = await event.request.json(); | ||
|
||
const response = await openai.chat.completions.create({ | ||
model: 'gpt-3.5-turbo-0613', | ||
stream: true, | ||
messages, | ||
functions, | ||
}); | ||
|
||
const data = new experimental_StreamData(); | ||
const stream = OpenAIStream(response, { | ||
experimental_onFunctionCall: async ( | ||
{ name, arguments: args }, | ||
createFunctionCallMessages, | ||
) => { | ||
if (name === 'get_current_weather') { | ||
// Call a weather API here | ||
const weatherData = { | ||
temperature: 20, | ||
unit: args.format === 'celsius' ? 'C' : 'F', | ||
}; | ||
|
||
data.append({ | ||
text: 'Some custom data', | ||
}); | ||
|
||
const newMessages = createFunctionCallMessages(weatherData); | ||
return openai.chat.completions.create({ | ||
messages: [...messages, ...newMessages], | ||
stream: true, | ||
model: 'gpt-3.5-turbo-0613', | ||
}); | ||
} | ||
}, | ||
onFinal() { | ||
data.close(); | ||
}, | ||
experimental_streamData: true, | ||
}); | ||
|
||
data.append({ | ||
text: 'Hello, how are you?', | ||
}); | ||
|
||
// Respond with the stream | ||
return new StreamingTextResponse(stream, {}, data); | ||
}; |
88 changes: 88 additions & 0 deletions
88
examples/solidstart-openai/src/routes/function-calling/index.tsx
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,88 @@ | ||
import { FunctionCallHandler, Message, nanoid } from 'ai'; | ||
import { useChat } from 'ai/solid'; | ||
import { For, JSX } from 'solid-js'; | ||
|
||
export default function Chat() { | ||
const functionCallHandler: FunctionCallHandler = async ( | ||
chatMessages, | ||
functionCall, | ||
) => { | ||
if (functionCall.name === 'eval_code_in_browser') { | ||
if (functionCall.arguments) { | ||
// Parsing here does not always work since it seems that some characters in generated code aren't escaped properly. | ||
const parsedFunctionCallArguments: { code: string } = JSON.parse( | ||
functionCall.arguments, | ||
); | ||
// WARNING: Do NOT do this in real-world applications! | ||
eval(parsedFunctionCallArguments.code); | ||
const functionResponse = { | ||
messages: [ | ||
...chatMessages, | ||
{ | ||
id: nanoid(), | ||
name: 'eval_code_in_browser', | ||
role: 'function' as const, | ||
content: parsedFunctionCallArguments.code, | ||
}, | ||
], | ||
}; | ||
return functionResponse; | ||
} | ||
} | ||
}; | ||
|
||
const { messages, input, setInput, handleSubmit, data } = useChat({ | ||
api: '/api/chat-with-functions', | ||
experimental_onFunctionCall: functionCallHandler, | ||
}); | ||
|
||
// Generate a map of message role to text color | ||
const roleToColorMap: Record<Message['role'], string> = { | ||
system: 'red', | ||
user: 'black', | ||
function: 'blue', | ||
assistant: 'green', | ||
}; | ||
|
||
const handleInputChange: JSX.ChangeEventHandlerUnion< | ||
HTMLInputElement, | ||
Event | ||
> = e => { | ||
setInput(e.target.value); | ||
}; | ||
|
||
return ( | ||
<div class="flex flex-col w-full max-w-md py-24 mx-auto stretch"> | ||
<div class="bg-gray-200 mb-8"> | ||
<For each={data()}> | ||
{item => ( | ||
<pre class="whitespace-pre-wrap">{JSON.stringify(item)}</pre> | ||
)} | ||
</For> | ||
</div> | ||
|
||
<For each={messages()}> | ||
{m => ( | ||
<div | ||
class="whitespace-pre-wrap" | ||
style={{ color: roleToColorMap[m.role] }} | ||
> | ||
<strong>{`${m.role}: `}</strong> | ||
{m.content || JSON.stringify(m.function_call)} | ||
<br /> | ||
<br /> | ||
</div> | ||
)} | ||
</For> | ||
|
||
<form onSubmit={handleSubmit}> | ||
<input | ||
class="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl" | ||
value={input()} | ||
placeholder="Say something..." | ||
onChange={handleInputChange} | ||
/> | ||
</form> | ||
</div> | ||
); | ||
} |
Oops, something went wrong.