-
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.
- Loading branch information
1 parent
22cda3a
commit b717dad
Showing
24 changed files
with
1,238 additions
and
5 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 | ||
--- | ||
|
||
Adding Inkeep as a stream provider |
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,186 @@ | ||
# InkeepStream | ||
|
||
## `InkeepStream(res: Response, cb?: AIStreamCallbacks): ReadableStream` [#InkeepStream] | ||
|
||
The `InkeepStream` function is a utility that transforms the output from [Inkeep's](https://www.inkeep.com) API into a [ReadableStream](https://developer.mozilla.org/docs/Web/API/ReadableStream). It uses [AIStream](/docs/api-reference/ai-stream) under the hood, applying a specific parser for the Inkeep's response data structure. | ||
|
||
This works with the official Inkeep API, and it's supported in both Node.js, the [Edge Runtime](https://edge-runtime.vercel.app), and browser environments. | ||
|
||
## Parameters | ||
|
||
### `res: Response` | ||
|
||
The `Response` object returned by the request to the Inkeep API. | ||
|
||
### `cb?: InkeepAIStreamCallbacksAndOptions` | ||
|
||
This optional parameter can be an object containing the callback functions to handle the start, each token, completion, and other events of the AI response. In the absence of this parameter, default behavior is implemented. | ||
|
||
The `InkeepAIStreamCallbacksAndOptions` extends the standard [AIStreamCallbacks](/docs/api-reference/ai-stream#AIStreamCallbacks) by (1) including additional `metadata` in `onFinal` and (2) adding an `onRecordsCited` callback. | ||
|
||
<OptionTable | ||
options={[ | ||
[ | ||
'onRecordsCited', | ||
'(records_cited: any) => void', | ||
'An optional function that is called once for every request, after the main content of a message has completed. It includes the information about the records (sources) cited in the AI chat response. It's the payload of the `records_cited` event from the Inkeep API.', | ||
], | ||
[ | ||
'onFinal', | ||
'(completion: string, metadata: InkeepOnFinalMetadata) => Promise<void>', | ||
"An optional function that is called once for every request. It's always the final callback invoked. It's passed the content of the chat response as a string and metadata as an object of type `InkeepOnFinalMetadata`.", | ||
], | ||
]} | ||
/> | ||
|
||
Check the [@inkeep/ai-api](https://github.com/inkeep/ai-api-ts) SDK for the latest typings of the Inkeep APIs. For example, the `records_cited` data payload can be obtained as: | ||
|
||
``` | ||
import type { RecordsCited$ } from '@inkeep/ai-api/models/components'; | ||
// RecordsCited$.Inbound | ||
``` | ||
|
||
#### InkeepOnFinalMetadata | ||
|
||
Information included in `metadata` of InkeepStream's `onFinal` callback. | ||
|
||
<OptionTable | ||
options={[ | ||
[ | ||
'chat_session_id', | ||
'string', | ||
'The Inkeep chat_session_id. This should be included in follow-up chat requests that are part of a chat session. Used for analytics and threading chat conversations.', | ||
], | ||
[ | ||
'records_cited', | ||
'any', | ||
'Contains information about the citations used in the chat response body.', | ||
], | ||
]} | ||
/> | ||
|
||
## Example | ||
|
||
The Inkeep API provides two routes: | ||
|
||
1. `POST chat_sessions/chat_results` - To **create** a chat session | ||
2. `POST chat_sessions/${chat_session_id}/chat_results` - To **continue** a chat session | ||
|
||
The example below shows how to create a `chat` API endpoint compatible with Vercel AI SDK client-side utilities like `useChat`: | ||
|
||
```tsx filename="app/api/chat/route.ts" showLineNumbers | ||
import { | ||
InkeepStream, | ||
InkeepOnFinalMetadata, | ||
StreamingTextResponse, | ||
experimental_StreamData, | ||
} from 'ai'; | ||
import { InkeepAI } from '@inkeep/ai-api'; | ||
import type { RecordsCited$ } from '@inkeep/ai-api/models/components'; | ||
|
||
interface ChatRequestBody { | ||
messages: Array<{ | ||
role: 'user' | 'assistant'; | ||
content: string; | ||
}>; | ||
chat_session_id?: string; | ||
} | ||
|
||
const inkeepIntegrationId = process.env.INKEEP_INTEGRATION_ID; | ||
|
||
export async function POST(req: Request) { | ||
const chatRequestBody: ChatRequestBody = await req.json(); | ||
const chat_session_id = chatRequestBody.chat_session_id; | ||
|
||
const ikpClient = new InkeepAI({ | ||
apiKey: process.env.INKEEP_API_KEY, | ||
}); | ||
|
||
let response; | ||
|
||
if (!chat_session_id) { | ||
const createRes = await ikpClient.chatSession.create({ | ||
integrationId: inkeepIntegrationId, | ||
chatSession: { | ||
messages: chatRequestBody.messages, | ||
}, | ||
stream: true, | ||
}); | ||
|
||
response = createRes.rawResponse; | ||
} else { | ||
const continueRes = await ikpClient.chatSession.continue(chat_session_id, { | ||
integrationId: inkeepIntegrationId, | ||
message: chatRequestBody.messages[chatRequestBody.messages.length - 1], | ||
stream: true, | ||
}); | ||
|
||
response = continueRes.rawResponse; | ||
} | ||
|
||
// used to pass custom metadata to the client | ||
const data = new experimental_StreamData(); | ||
|
||
if (!response?.body) { | ||
throw new Error('Response body is null'); | ||
} | ||
|
||
const stream = InkeepStream(response, { | ||
onRecordsCited: async (records_cited: RecordsCited$.Inbound) => { | ||
// append the citations to the message annotations | ||
data.appendMessageAnnotation({ | ||
records_cited, | ||
}); | ||
}, | ||
onFinal: async (complete: string, metadata?: InkeepOnFinalMetadata) => { | ||
// return the chat_session_id to the client | ||
if (metadata) { | ||
data.append({ onFinalMetadata: metadata }); | ||
} | ||
data.close(); | ||
}, | ||
experimental_streamData: true, | ||
}); | ||
|
||
return new StreamingTextResponse(stream, {}, data); | ||
} | ||
``` | ||
|
||
This example uses the [experimental_StreamData](/docs/api-reference/stream-data) and the callback methods of `InkeepStream` to attach metadata to the response. | ||
|
||
### Client | ||
|
||
From [`useChat`](/docs/api-reference/use-chat), this is available as: | ||
|
||
```tsx filename="app/chat/page.tsx" showLineNumbers | ||
import { InkeepOnFinalMetadata } from 'ai/streams'; | ||
import type { RecordsCited$ } from '@inkeep/ai-api/models/components'; | ||
|
||
// ... your chat component | ||
|
||
const { messages, data } = useChat(); | ||
|
||
/* ==For chat_session_id== */ | ||
// get the onFinalMetadata item from the global chat data | ||
const onFinalMetadataItem = data?.find( | ||
item => | ||
typeof item === 'object' && item !== null && 'onFinalMetadata' in item, | ||
) as { onFinalMetadata: InkeepOnFinalMetadata } | undefined; | ||
|
||
// get the chat_session_id from the onFinalMetadata item | ||
const chatSessionId = onFinalMetadataItem?.onFinalMetadata?.chat_session_id; | ||
|
||
/* For messages[n].annotations, available independently for each message */ | ||
|
||
const recordsCitedAnnotation = | ||
messages && | ||
messages.length > 0 && | ||
(messages[0].annotations?.find( | ||
item => | ||
typeof item === 'object' && item !== null && 'records_cited' in item, | ||
) as { records_cited: RecordsCited$.Inbound } | undefined); | ||
|
||
// get the citations from the records_cited annotation | ||
const citations = recordsCitedAnnotation?.records_cited?.citations; | ||
``` |
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
Oops, something went wrong.