Deep Chat OpenAI
### 7h360df47h3r commented Nov 27, 2023
Feature Request: Support Passing Assistant Details in Custom Requests
Current Behavior:
I am utilising your deep-chat-nextjs server as a proxy for OpenAI and the request
### 7h360df47h3r commented Nov 27, 2023
Current Behavior:
I am utilising your deep-chat-nextjs server as a proxy for OpenAI and the request
DeepChat
request
prop.
Additional InformationOvidijusParsiunas self-assigned this Nov 27, 2023 OvidijusParsiunas added the advice Information how to use/implement the component label Nov 27, 2023 Owner ### OvidijusParsiunas commented Nov 27, 2023 Hi. When connecting Deep Chat to your own backend (like a NextJs function), the specific code for calling another service such as OpenAI Assistants API should be written in the backend and not in Deep Chat. We try to keep the backend examples as simple as possible so that developers can tailor them to their use-cases, hence we would prefer not to expand our examples for this reason. In regards to your specific problem, connecting to OpenAI Assistants API is a quite complex task as it requires the use of the Assistants, Threads, Messages and Runs APIs. If you want to do this manually in your own backend, you can use the code that I will paste below.
2 7h360df47h3r and myfypersonal reacted with thumbs up emoji
index.tsx
file:
import {RequestDetails} from 'deep-chat/dist/types/interceptors';
import {Response} from 'deep-chat/dist/types/response';
import styles from '../styles/Index.module.css';
import dynamic from 'next/dynamic';
import React from 'react';
export default function IndexPage() {
// Need to import the component dynamically as it uses the 'window' property.
// If you have found a better way of adding the component in next, please create a new issue ticket so we can update the example!
const DeepChat = dynamic(() => import('deep-chat-react').then((mod) => mod.DeepChat), {
ssr: false,
});
// quick way to store state and not re-render the chat
const threadId = React.useRef<string | null>('');
return (
<>
<main className={styles.main}>
<h1 className={styles.serverTitle}>Server for OpenAI</h1>
<a href="https://openai.com/blog/openai-api" target="_blank" rel="noreferrer">
<img
className={styles.serverTitleIcon}
src="https://raw.githubusercontent.com/OvidijusParsiunas/deep-chat/HEAD/website/static/img/openAILogo.png"
style=
alt={'Title icon'}
/>
</a>
<h3>Make sure to set the OPENAI_API_KEY environment variable in your server</h3>
<div className={styles.components}>
<div className={styles.diagonalLine} style=></div>
{/* by setting maxMessages requestBodyLimits to 0 or lower - each request will send full chat history:
https://deepchat.dev/docs/connect/#requestBodyLimits */}
<DeepChat
style=
introMessage=
request=
requestBodyLimits=
errorMessages=
requestInterceptor={(details: RequestDetails) => {
if (details.body instanceof FormData) {
if (threadId.current) details.body.append('thread_id', JSON.stringify(threadId.current));
} else if (threadId.current) {
details.body.thread_id = threadId.current;
}
return details;
}}
responseInterceptor={(response: Response & {thread_id: string}) => {
threadId.current = response.thread_id;
return response;
}}
/>
</div>
</main>
</>
);
}
1 7h360df47h3r reacted with thumbs up emoji
API_KEY
and ASSISTANT_ID
variables:
import {DeepChatOpenAITextRequestBody} from '../../../types/deepChatTextRequestBody';
import {MessageContent} from 'deep-chat/dist/types/messages';
import errorHandler from '../../../utils/errorHandler';
import {NextRequest, NextResponse} from 'next/server';
import {
OpenAIAssistantMessagesResult,
OpenAIAssistantInitReqResult,
OpenAIRunResult,
} from 'deep-chat/dist/types/openAIResult';
export const config = {
runtime: 'edge',
};
const API_KEY = '';
const ASSISTANT_ID = '';
async function handler(req: NextRequest) {
// Text messages are stored inside request body using the Deep Chat JSON format:
// https://deepchat.dev/docs/connect
const textRequestBody = (await req.json()) as {messages: MessageContent[]; thread_id?: string};
console.log(textRequestBody);
let thread_id: string = textRequestBody.thread_id;
let run_id: string;
if (thread_id) {
const messageBody = createMessageBody(textRequestBody);
// Create a new thread and automatically run it
// https://platform.openai.com/docs/api-reference/messages/createMessage
await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify(messageBody),
});
// https://platform.openai.com/docs/api-reference/runs/createRun
const createAndRunResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs`, {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify({assistant_id: ASSISTANT_ID}),
});
const createAndRunResult = (await createAndRunResponse.json()) as OpenAIAssistantInitReqResult;
run_id = createAndRunResult.id;
} else {
const threadBody = createThreadBody(textRequestBody);
// Create a new thread and automatically run it
// https://platform.openai.com/docs/api-reference/runs/createThreadAndRun
const createMessageAndRunResponse = await fetch('https://api.openai.com/v1/threads/runs', {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify(threadBody),
});
const createMessageAndRunResult = (await createMessageAndRunResponse.json()) as OpenAIAssistantInitReqResult;
thread_id = createMessageAndRunResult.thread_id;
run_id = createMessageAndRunResult.id;
}
// Get the result
const resultText = await pollForResult(thread_id, run_id);
// Sends response back to Deep Chat using the Response format:
// https://deepchat.dev/docs/connect/#Response
return NextResponse.json({text: resultText, thread_id: thread_id});
}
export function createThreadBody(body: DeepChatOpenAITextRequestBody) {
// Text messages are stored inside request body using the Deep Chat JSON format:
// https://deepchat.dev/docs/connect
return {
assistant_id: ASSISTANT_ID,
thread: {
messages: [
{
role: 'user',
content: body.messages[body.messages.length - 1].text,
},
],
},
};
}
export function createMessageBody(body: DeepChatOpenAITextRequestBody) {
// Text messages are stored inside request body using the Deep Chat JSON format:
// https://deepchat.dev/docs/connect
return {
role: 'user',
content: body.messages[body.messages.length - 1].text,
};
}
export function createHeaders() {
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
'OpenAI-Beta': 'assistants=v1',
};
}
async function pollForResult(thread_id: string, run_id: string) {
// Get the run status
// https://platform.openai.com/docs/api-reference/runs/listRuns
const runStatusResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs/${run_id}`, {
headers: createHeaders(),
method: 'GET',
});
const {status} = (await runStatusResponse.json()) as OpenAIRunResult;
if (status === 'queued' || status === 'in_progress') {
return await pollForResult(thread_id, run_id);
} else if (status === 'completed') {
// https://platform.openai.com/docs/api-reference/messages/listMessages
const messagesResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, {
headers: createHeaders(),
method: 'GET',
});
const messagesResult = (await messagesResponse.json()) as OpenAIAssistantMessagesResult;
return messagesResult.data[0].content[0].text.value;
}
throw runStatusResponse.status;
}
export default errorHandler(handler);
1 7h360df47h3r reacted with thumbs up emoji
thread_id
and then re-use it for future conversations.
These examples should work right out of the box and you can tailor them to your use-case. If you have any questions specific to the OpenAI API, I recommend to instead check their documentation or use the Developer Forum.
Let me know if you have any specific questions to the above examples. Thanks!1 7h360df47h3r reacted with heart emoji
Owner ### OvidijusParsiunas commented Nov 28, 2023 • edited Edited 2 times
stream
property to simulate the stream.stream=
.2 7h360df47h3r and techpeace reacted with thumbs up emoji
Owner ### OvidijusParsiunas commented Nov 28, 2023 • edited Edited 6 times
oparsiunas@googlemail.com
for my PayPal. To note, I am motivated to work on this component by knowing that people are using it and every new GitHub Star keeps me going 🌟 . I am also just finishing up my career break and am looking for a job, so financially I will be ok. Thanks again for your kind thoughts!
When it comes to integrating functions/tools for Assistants API, majority of the setup really needs to be done when setting up your assistant. I recommend using the Assistant Playground to first add your function:
(This Asisstant is now removed as I used it for a demo)
After clicking the Add button, you will see a template for an example function setup code. This is where things get a little complicated as they require the understanding on what functions/tools are and how they are used. If you are new to them I recommend reading this document or watching this video which really helped me (to note that vide is using the old API, but it helps understanding the concept of OpenAI functions).
When adding your function, you can just use the template, and for my case I have used a get_weather function which contains the following template:
{
"name": "get_weather",
"description": "Determine weather in my location",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state e.g. San Francisco, CA"
},
"unit": {
"type": "string",
"enum": [
"c",
"f"
]
}
},
"required": [
"location"
]
}
}
Given the function above, you can change the pollForResult
function and add a new functions to the chat.ts example from above:
async function pollForResult(thread_id: string, run_id: string) {
// Get the run status
// https://platform.openai.com/docs/api-reference/runs/listRuns
const runStatusResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs/${run_id}`, {
headers: createHeaders(),
method: 'GET',
});
const {status, required_action} = (await runStatusResponse.json()) as OpenAIRunResult;
if (status === 'queued' || status === 'in_progress') {
return await pollForResult(thread_id, run_id);
}
if (status === 'completed') {
// https://platform.openai.com/docs/api-reference/messages/listMessages
const messagesResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, {
headers: createHeaders(),
method: 'GET',
});
const messagesResult = (await messagesResponse.json()) as OpenAIAssistantMessagesResult;
return messagesResult.data[0].content[0].text.value;
}
const toolCalls = required_action?.submit_tool_outputs?.tool_calls;
if (status === 'requires_action' && toolCalls) {
console.log('hello');
return await handleTools(toolCalls, thread_id, run_id);
}
throw runStatusResponse.status;
}
async function handleTools(toolCalls: ToolCalls, thread_id: string, run_id: string) {
const functions = toolCalls.map((call) => {
return {name: call.function.name, arguments: call.function.arguments};
});
const handlerResponse = functions.map((functionDetails) => getCurrentWeather(functionDetails.arguments));
const tool_outputs = handlerResponse.map((resp, index) => {
return {tool_call_id: toolCalls[index].id, output: resp};
});
// https://platform.openai.com/docs/api-reference/runs/submitToolOutputs
await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs/${run_id}/submit_tool_outputs`, {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify({tool_outputs}),
});
await new Promise((resolve) => setTimeout(resolve, 1000)); // wait for OpenAI to read the output
return await pollForResult(thread_id, run_id);
}
function getCurrentWeather(location: string) {
location = location.toLowerCase();
if (location.includes('tokyo')) {
return 'Good';
}
if (location.includes('san francisco')) {
return 'Mild';
}
return 'Very Hot';
}
In this example the getCurrentWeather
function is what handles the actual output.
Once you have all the code that is setup, this should work as follows:
To note, the Assistants tools API does not always work and sometimes it is unable to read the function response, but the code in this example is correct
Hopefully this helps you!
2 7h360df47h3r and techpeace reacted with heart emoji
OvidijusParsiunas changed the title Support Passing Assistant Details in the Custom Requests OpenAI Assistant in a proxy server Dec 29, 2023 OvidijusParsiunas mentioned this issue Dec 29, 2023 OpenAI Assistant through a proxy #82 Closed ### AhmeedBen commented Jan 13, 2024 thanks, this works fine with text messages, what about files upload ?
Owner ### OvidijusParsiunas commented Jan 13, 2024 • edited Edited 3 times
mixedFiles
(or any other that suits your use-case better) property on the Deep Chat component.
When you send files from Deep Chat, you must remember that the request format is encapsulated inside FormData as referenced in the Request message section.
Here is the new chat.ts code:
import {DeepChatOpenAITextRequestBody} from '../../../types/deepChatTextRequestBody';
import errorHandler from '../../../utils/errorHandler';
import {NextRequest, NextResponse} from 'next/server';
import {
OpenAIAssistantMessagesResult,
OpenAIAssistantInitReqResult,
OpenAIRunResult,
} from 'deep-chat/dist/types/openAIResult';
export const config = {
runtime: 'edge',
};
const API_KEY = '';
const ASSISTANT_ID = '';
async function handler(req: NextRequest) {
const formData = await req.formData();
// aggregate data
const files: File[] = [];
const textRequestBody: DeepChatOpenAITextRequestBody = {messages: []};
let thread_id: string | undefined;
formData.forEach((data) => {
if (typeof data === 'object') {
files.push(data);
} else {
const parsedData = JSON.parse(data);
if (typeof parsedData === 'string') {
thread_id = parsedData;
} else {
textRequestBody.messages.push(parsedData);
}
}
});
// store files
const file_ids = files ? await storeFiles(files) : undefined;
let run_id: string;
if (thread_id) {
const messageBody = createMessageBody(textRequestBody, file_ids);
// Create a new message
// https://platform.openai.com/docs/api-reference/messages/createMessage
await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify(messageBody),
});
// https://platform.openai.com/docs/api-reference/runs/createRun
const createAndRunResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs`, {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify({assistant_id: ASSISTANT_ID}),
});
const createAndRunResult = (await createAndRunResponse.json()) as OpenAIAssistantInitReqResult;
run_id = createAndRunResult.id;
} else {
const threadBody = createThreadBody(textRequestBody, file_ids);
// Create a new thread and automatically run it
// https://platform.openai.com/docs/api-reference/runs/createThreadAndRun
const createMessageAndRunResponse = await fetch('https://api.openai.com/v1/threads/runs', {
headers: createHeaders(),
method: 'POST',
body: JSON.stringify(threadBody),
});
const createMessageAndRunResult = (await createMessageAndRunResponse.json()) as OpenAIAssistantInitReqResult;
thread_id = createMessageAndRunResult.thread_id;
run_id = createMessageAndRunResult.id;
}
// Get the result
const resultText = await pollForResult(thread_id, run_id);
// Sends response back to Deep Chat using the Response format:
// https://deepchat.dev/docs/connect/#Response
return NextResponse.json({text: resultText, thread_id: thread_id});
}
async function storeFiles(files: File[]) {
const headers = createHeaders();
delete headers['Content-Type']; // don't need when sending files
const requests = files.map(async (file) => {
const formData = new FormData();
formData.append('purpose', 'assistants');
formData.append('file', file);
return new Promise<{id: string}>(async (resolve) => {
const response = await fetch('https://api.openai.com/v1/files', {headers, method: 'POST', body: formData});
const content = await response.json();
resolve(content);
});
});
try {
return (await Promise.all(requests)).map((result) => result.id);
} catch (err) {
console.log('error');
console.log(err);
}
}
export function createThreadBody(body: DeepChatOpenAITextRequestBody, file_ids: string[]) {
// Text messages are stored inside request body using the Deep Chat JSON format:
// https://deepchat.dev/docs/connect
return {
assistant_id: ASSISTANT_ID,
thread: {
messages: [
{
role: 'user',
content: body.messages[body.messages.length - 1].text,
file_ids,
},
],
},
};
}
export function createMessageBody(body: DeepChatOpenAITextRequestBody, file_ids: string[]) {
// Text messages are stored inside request body using the Deep Chat JSON format:
// https://deepchat.dev/docs/connect
return {
role: 'user',
content: body.messages[body.messages.length - 1].text,
file_ids,
};
}
export function createHeaders() {
return {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_KEY}`,
'OpenAI-Beta': 'assistants=v1',
};
}
async function pollForResult(thread_id: string, run_id: string) {
// Get the run status
// https://platform.openai.com/docs/api-reference/runs/listRuns
const runStatusResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/runs/${run_id}`, {
headers: createHeaders(),
method: 'GET',
});
const {status} = (await runStatusResponse.json()) as OpenAIRunResult;
if (status === 'queued' || status === 'in_progress') {
return await pollForResult(thread_id, run_id);
} else if (status === 'completed') {
// https://platform.openai.com/docs/api-reference/messages/listMessages
const messagesResponse = await fetch(`https://api.openai.com/v1/threads/${thread_id}/messages`, {
headers: createHeaders(),
method: 'GET',
});
const messagesResult = (await messagesResponse.json()) as OpenAIAssistantMessagesResult;
return messagesResult.data[0].content[0].text.value;
}
throw runStatusResponse.status;
}
export default errorHandler(handler);
###
AhmeedBen commented Jan 13, 2024
I tried the code but still get the error:
API Error: [SyntaxError: Unexpected token ‘o’, “[object File]” is not valid JSON]
text messages works fine
Owner ### OvidijusParsiunas commented Jan 13, 2024 It could be that your assistant is not configured to work with files, does uploading files work for you in OpenAI Playground?
### AhmeedBen commented Jan 14, 2024 yes the assistant works with files in the playground.
Owner
###
OvidijusParsiunas commented Jan 14, 2024
I just tested the code I commented above and it works fine for me.
It is hard say what is causing your issue, but if you have copied the code exactly as described then it is definitely something else. I would recommend to make sure that the API_KEY
and the ASSISTANT_ID
have been set correctly.
Could you perhaps share the prompt you are using, what kind of files you are uploading and what kind of response you expect to get?
Other than this, the amount of support I can provide is limited, however if you try to debug your app and find the line that is causing the error that would be helpful.
Thanks.
###
AhmeedBen commented Jan 14, 2024
the execution stops here:
formData.forEach((data) => {
if (data instanceof File) {
files.push(data);
} else {
textRequestBody.messages.push(JSON.parse(data) as MessageContent);
}
});
the first condition is not fulfiled, despite of uploading file
Owner
###
OvidijusParsiunas commented Jan 14, 2024
That is very strange, for some reason the data
is not recognised as a file.
Is your index.tsx
file different than the one in this comment. Ofcourse with the addition of mixedFiles.
###
AhmeedBen commented Jan 14, 2024
I changed the condition to typeof data === ‘object’ , and it works fine,
I don’t know this will affect the other types of uploads.
Owner ### OvidijusParsiunas commented Jan 14, 2024 Happy to hear it works for you, I have also updated my example to reflect this. Thankyou!
###
AhmeedBen commented Jan 15, 2024
Thank you very much,
Another thing, a new thread is created everytime we add a new file.
I tried to use
const textRequestBody = (await req.json()) as {messages: []; thread_id?: string};
but it doesn’t work.
Owner
###
OvidijusParsiunas commented Jan 15, 2024
I have updated the chat.ts
and index.tsx
examples to help handle the same thread_id (session) form FormData.
### AhmeedBen commented Jan 16, 2024 Thank you very much, everything works fine now.