219 lines
6.6 KiB
TypeScript
219 lines
6.6 KiB
TypeScript
import { openapiToFunctions } from "@/lib/openapi-conversion"
|
|
import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers"
|
|
import { Tables } from "@/supabase/types"
|
|
import { ChatSettings } from "@/types"
|
|
import { OpenAIStream, StreamingTextResponse } from "ai"
|
|
import OpenAI from "openai"
|
|
import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs"
|
|
|
|
export async function POST(request: Request) {
|
|
const json = await request.json()
|
|
const { chatSettings, messages, selectedTools } = json as {
|
|
chatSettings: ChatSettings
|
|
messages: any[]
|
|
selectedTools: Tables<"tools">[]
|
|
}
|
|
|
|
try {
|
|
const profile = await getServerProfile()
|
|
|
|
checkApiKey(profile.openai_api_key, "OpenAI")
|
|
|
|
const openai = new OpenAI({
|
|
apiKey: profile.openai_api_key || "",
|
|
organization: profile.openai_organization_id
|
|
})
|
|
|
|
let allTools: OpenAI.Chat.Completions.ChatCompletionTool[] = []
|
|
let allRouteMaps = {}
|
|
let schemaDetails = []
|
|
|
|
for (const selectedTool of selectedTools) {
|
|
try {
|
|
const convertedSchema = await openapiToFunctions(
|
|
JSON.parse(selectedTool.schema as string)
|
|
)
|
|
const tools = convertedSchema.functions || []
|
|
allTools = allTools.concat(tools)
|
|
|
|
const routeMap = convertedSchema.routes.reduce(
|
|
(map: Record<string, string>, route) => {
|
|
map[route.path.replace(/{(\w+)}/g, ":$1")] = route.operationId
|
|
return map
|
|
},
|
|
{}
|
|
)
|
|
|
|
allRouteMaps = { ...allRouteMaps, ...routeMap }
|
|
|
|
schemaDetails.push({
|
|
title: convertedSchema.info.title,
|
|
description: convertedSchema.info.description,
|
|
url: convertedSchema.info.server,
|
|
headers: selectedTool.custom_headers,
|
|
routeMap,
|
|
requestInBody: convertedSchema.routes[0].requestInBody
|
|
})
|
|
} catch (error: any) {
|
|
console.error("Error converting schema", error)
|
|
}
|
|
}
|
|
|
|
const firstResponse = await openai.chat.completions.create({
|
|
model: chatSettings.model as ChatCompletionCreateParamsBase["model"],
|
|
messages,
|
|
tools: allTools.length > 0 ? allTools : undefined
|
|
})
|
|
|
|
const message = firstResponse.choices[0].message
|
|
messages.push(message)
|
|
const toolCalls = message.tool_calls || []
|
|
|
|
if (toolCalls.length === 0) {
|
|
return new Response(message.content, {
|
|
headers: {
|
|
"Content-Type": "application/json"
|
|
}
|
|
})
|
|
}
|
|
|
|
if (toolCalls.length > 0) {
|
|
for (const toolCall of toolCalls) {
|
|
const functionCall = toolCall.function
|
|
const functionName = functionCall.name
|
|
const argumentsString = toolCall.function.arguments.trim()
|
|
const parsedArgs = JSON.parse(argumentsString)
|
|
|
|
// Find the schema detail that contains the function name
|
|
const schemaDetail = schemaDetails.find(detail =>
|
|
Object.values(detail.routeMap).includes(functionName)
|
|
)
|
|
|
|
if (!schemaDetail) {
|
|
throw new Error(`Function ${functionName} not found in any schema`)
|
|
}
|
|
|
|
const pathTemplate = Object.keys(schemaDetail.routeMap).find(
|
|
key => schemaDetail.routeMap[key] === functionName
|
|
)
|
|
|
|
if (!pathTemplate) {
|
|
throw new Error(`Path for function ${functionName} not found`)
|
|
}
|
|
|
|
const path = pathTemplate.replace(/:(\w+)/g, (_, paramName) => {
|
|
const value = parsedArgs.parameters[paramName]
|
|
if (!value) {
|
|
throw new Error(
|
|
`Parameter ${paramName} not found for function ${functionName}`
|
|
)
|
|
}
|
|
return encodeURIComponent(value)
|
|
})
|
|
|
|
if (!path) {
|
|
throw new Error(`Path for function ${functionName} not found`)
|
|
}
|
|
|
|
// Determine if the request should be in the body or as a query
|
|
const isRequestInBody = schemaDetail.requestInBody
|
|
let data = {}
|
|
|
|
if (isRequestInBody) {
|
|
// If the type is set to body
|
|
let headers = {
|
|
"Content-Type": "application/json"
|
|
}
|
|
|
|
// Check if custom headers are set
|
|
const customHeaders = schemaDetail.headers // Moved this line up to the loop
|
|
// Check if custom headers are set and are of type string
|
|
if (customHeaders && typeof customHeaders === "string") {
|
|
let parsedCustomHeaders = JSON.parse(customHeaders) as Record<
|
|
string,
|
|
string
|
|
>
|
|
|
|
headers = {
|
|
...headers,
|
|
...parsedCustomHeaders
|
|
}
|
|
}
|
|
|
|
const fullUrl = schemaDetail.url + path
|
|
|
|
const bodyContent = parsedArgs.requestBody || parsedArgs
|
|
|
|
const requestInit = {
|
|
method: "POST",
|
|
headers,
|
|
body: JSON.stringify(bodyContent) // Use the extracted requestBody or the entire parsedArgs
|
|
}
|
|
|
|
const response = await fetch(fullUrl, requestInit)
|
|
|
|
if (!response.ok) {
|
|
data = {
|
|
error: response.statusText
|
|
}
|
|
} else {
|
|
data = await response.json()
|
|
}
|
|
} else {
|
|
// If the type is set to query
|
|
const queryParams = new URLSearchParams(
|
|
parsedArgs.parameters
|
|
).toString()
|
|
const fullUrl =
|
|
schemaDetail.url + path + (queryParams ? "?" + queryParams : "")
|
|
|
|
let headers = {}
|
|
|
|
// Check if custom headers are set
|
|
const customHeaders = schemaDetail.headers
|
|
if (customHeaders && typeof customHeaders === "string") {
|
|
headers = JSON.parse(customHeaders)
|
|
}
|
|
|
|
const response = await fetch(fullUrl, {
|
|
method: "GET",
|
|
headers: headers
|
|
})
|
|
|
|
if (!response.ok) {
|
|
data = {
|
|
error: response.statusText
|
|
}
|
|
} else {
|
|
data = await response.json()
|
|
}
|
|
}
|
|
|
|
messages.push({
|
|
tool_call_id: toolCall.id,
|
|
role: "tool",
|
|
name: functionName,
|
|
content: JSON.stringify(data)
|
|
})
|
|
}
|
|
}
|
|
|
|
const secondResponse = await openai.chat.completions.create({
|
|
model: chatSettings.model as ChatCompletionCreateParamsBase["model"],
|
|
messages,
|
|
stream: true
|
|
})
|
|
|
|
const stream = OpenAIStream(secondResponse)
|
|
|
|
return new StreamingTextResponse(stream)
|
|
} catch (error: any) {
|
|
console.error(error)
|
|
const errorMessage = error.error?.message || "An unexpected error occurred"
|
|
const errorCode = error.status || 500
|
|
return new Response(JSON.stringify({ message: errorMessage }), {
|
|
status: errorCode
|
|
})
|
|
}
|
|
}
|