chrome ai model
This commit is contained in:
parent
a5de9705d4
commit
86296c96b6
187
src/models/ChatChromeAi.ts
Normal file
187
src/models/ChatChromeAi.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import {
|
||||
SimpleChatModel,
|
||||
type BaseChatModelParams
|
||||
} from "@langchain/core/language_models/chat_models"
|
||||
import type { BaseLanguageModelCallOptions } from "@langchain/core/language_models/base"
|
||||
import {
|
||||
CallbackManagerForLLMRun,
|
||||
Callbacks
|
||||
} from "@langchain/core/callbacks/manager"
|
||||
import { BaseMessage, AIMessageChunk } from "@langchain/core/messages"
|
||||
import { ChatGenerationChunk } from "@langchain/core/outputs"
|
||||
import { IterableReadableStream } from "@langchain/core/utils/stream"
|
||||
|
||||
export interface AI {
|
||||
canCreateTextSession(): Promise<AIModelAvailability>
|
||||
createTextSession(options?: AITextSessionOptions): Promise<AITextSession>
|
||||
defaultTextSessionOptions(): Promise<AITextSessionOptions>
|
||||
}
|
||||
|
||||
export interface AITextSession {
|
||||
prompt(input: string): Promise<string>
|
||||
promptStreaming(input: string): ReadableStream
|
||||
destroy(): void
|
||||
clone(): AITextSession
|
||||
}
|
||||
|
||||
export interface AITextSessionOptions {
|
||||
topK: number
|
||||
temperature: number
|
||||
}
|
||||
|
||||
export const enum AIModelAvailability {
|
||||
Readily = "readily",
|
||||
AfterDownload = "after-download",
|
||||
No = "no"
|
||||
}
|
||||
|
||||
export interface ChromeAIInputs extends BaseChatModelParams {
|
||||
topK?: number
|
||||
temperature?: number
|
||||
/**
|
||||
* An optional function to format the prompt before sending it to the model.
|
||||
*/
|
||||
promptFormatter?: (messages: BaseMessage[]) => string
|
||||
}
|
||||
|
||||
export interface ChromeAICallOptions extends BaseLanguageModelCallOptions {}
|
||||
|
||||
function formatPrompt(messages: BaseMessage[]): string {
|
||||
return messages
|
||||
.map((message) => {
|
||||
if (typeof message.content !== "string") {
|
||||
throw new Error(
|
||||
"ChatChromeAI does not support non-string message content."
|
||||
)
|
||||
}
|
||||
return `${message._getType()}: ${message.content}`
|
||||
})
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
/**
|
||||
* To use this model you need to have the `Built-in AI Early Preview Program`
|
||||
* for Chrome. You can find more information about the program here:
|
||||
* @link https://developer.chrome.com/docs/ai/built-in
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Initialize the ChatChromeAI model.
|
||||
* const model = new ChatChromeAI({
|
||||
* temperature: 0.5, // Optional. Default is 0.5.
|
||||
* topK: 40, // Optional. Default is 40.
|
||||
* });
|
||||
*
|
||||
* // Call the model with a message and await the response.
|
||||
* const response = await model.invoke([
|
||||
* new HumanMessage({ content: "My name is John." }),
|
||||
* ]);
|
||||
* ```
|
||||
*/
|
||||
export class ChatChromeAI extends SimpleChatModel<ChromeAICallOptions> {
|
||||
session?: AITextSession
|
||||
|
||||
temperature = 0.5
|
||||
|
||||
topK = 40
|
||||
|
||||
promptFormatter: (messages: BaseMessage[]) => string
|
||||
|
||||
static lc_name() {
|
||||
return "ChatChromeAI"
|
||||
}
|
||||
|
||||
constructor(inputs?: ChromeAIInputs) {
|
||||
super({
|
||||
callbacks: {} as Callbacks,
|
||||
...inputs
|
||||
})
|
||||
this.temperature = inputs?.temperature ?? this.temperature
|
||||
this.topK = inputs?.topK ?? this.topK
|
||||
this.promptFormatter = inputs?.promptFormatter ?? formatPrompt
|
||||
}
|
||||
|
||||
_llmType() {
|
||||
return "chrome-ai"
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the model. This method must be called before calling `.invoke()`.
|
||||
*/
|
||||
async initialize() {
|
||||
if (typeof window === "undefined") {
|
||||
throw new Error("ChatChromeAI can only be used in the browser.")
|
||||
}
|
||||
|
||||
const { ai } = window as any
|
||||
const canCreateTextSession = await ai.canCreateTextSession()
|
||||
if (canCreateTextSession === AIModelAvailability.No) {
|
||||
throw new Error("The AI model is not available.")
|
||||
} else if (canCreateTextSession === AIModelAvailability.AfterDownload) {
|
||||
throw new Error("The AI model is not yet downloaded.")
|
||||
}
|
||||
|
||||
this.session = await ai.createTextSession({
|
||||
topK: this.topK,
|
||||
temperature: this.temperature
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Call `.destroy()` to free resources if you no longer need a session.
|
||||
* When a session is destroyed, it can no longer be used, and any ongoing
|
||||
* execution will be aborted. You may want to keep the session around if
|
||||
* you intend to prompt the model often since creating a session can take
|
||||
* some time.
|
||||
*/
|
||||
destroy() {
|
||||
if (!this.session) {
|
||||
return console.log("No session found. Returning.")
|
||||
}
|
||||
this.session.destroy()
|
||||
}
|
||||
|
||||
async *_streamResponseChunks(
|
||||
messages: BaseMessage[],
|
||||
_options: this["ParsedCallOptions"],
|
||||
runManager?: CallbackManagerForLLMRun
|
||||
): AsyncGenerator<ChatGenerationChunk> {
|
||||
if (!this.session) {
|
||||
throw new Error("Session not found. Please call `.initialize()` first.")
|
||||
}
|
||||
const textPrompt = this.promptFormatter(messages)
|
||||
|
||||
const stream = this.session.promptStreaming(textPrompt)
|
||||
const iterableStream = IterableReadableStream.fromReadableStream(stream)
|
||||
|
||||
let previousContent = ""
|
||||
for await (const chunk of iterableStream) {
|
||||
const newContent = chunk.slice(previousContent.length)
|
||||
previousContent += newContent
|
||||
yield new ChatGenerationChunk({
|
||||
text: newContent,
|
||||
message: new AIMessageChunk({
|
||||
content: newContent,
|
||||
additional_kwargs: {}
|
||||
})
|
||||
})
|
||||
await runManager?.handleLLMNewToken(newContent)
|
||||
}
|
||||
}
|
||||
|
||||
async _call(
|
||||
messages: BaseMessage[],
|
||||
options: this["ParsedCallOptions"],
|
||||
runManager?: CallbackManagerForLLMRun
|
||||
): Promise<string> {
|
||||
const chunks = []
|
||||
for await (const chunk of this._streamResponseChunks(
|
||||
messages,
|
||||
options,
|
||||
runManager
|
||||
)) {
|
||||
chunks.push(chunk.text)
|
||||
}
|
||||
return chunks.join("")
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user