@copilotkit/runtime
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
89 lines (79 loc) • 3.24 kB
text/typescript
/**
* Copilot Runtime adapter for Google Generative AI (e.g. Gemini).
*
* ## Example
*
* ```ts
* import { CopilotRuntime, GoogleGenerativeAIAdapter } from "@copilotkit/runtime";
* const { GoogleGenerativeAI } = require("@google/generative-ai");
*
* const genAI = new GoogleGenerativeAI(process.env["GOOGLE_API_KEY"]);
*
* const copilotKit = new CopilotRuntime();
*
* return new GoogleGenerativeAIAdapter({ model: "gemini-2.5-flash", apiVersion: "v1" });
* ```
*/
import { LangChainAdapter } from "../langchain/langchain-adapter";
interface GoogleGenerativeAIAdapterOptions {
/**
* A custom Google Generative AI model to use.
*/
model?: string;
/**
* The API version to use (e.g. "v1" or "v1beta"). Defaults to "v1".
*/
apiVersion?: "v1" | "v1beta";
/**
* The API key to use.
*/
apiKey?: string;
}
const DEFAULT_MODEL = "gemini-2.5-flash";
const DEFAULT_API_VERSION: GoogleGenerativeAIAdapterOptions["apiVersion"] = "v1";
let hasWarnedDefaultGoogleModel = false;
export class GoogleGenerativeAIAdapter extends LangChainAdapter {
public provider = "google";
public model: string = DEFAULT_MODEL;
constructor(options?: GoogleGenerativeAIAdapterOptions) {
if (!hasWarnedDefaultGoogleModel && !options?.model && !options?.apiVersion) {
console.warn(
`You are using the GoogleGenerativeAIAdapter without explicitly setting a model or apiVersion. ` +
`CopilotKit will default to apiVersion="v1" and model="${DEFAULT_MODEL}". ` +
`To silence this warning, pass model and apiVersion when constructing the adapter.`,
);
hasWarnedDefaultGoogleModel = true;
}
super({
chainFn: async ({ messages, tools, threadId }) => {
// Lazy require for optional peer dependencies
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { ChatGoogle } = require("@langchain/google-gauth");
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { AIMessage } = require("@langchain/core/messages");
// Filter out empty assistant messages to prevent Gemini validation errors
// Gemini specifically rejects conversations containing AIMessages with empty content
const filteredMessages = messages.filter((message) => {
// Keep all non-AI messages (HumanMessage, SystemMessage, ToolMessage, etc.)
if (!(message instanceof AIMessage)) {
return true;
}
// For AIMessages, only keep those with non-empty content
// Also keep AIMessages with tool_calls even if content is empty
const aiMsg = message as any;
return (
(aiMsg.content && String(aiMsg.content).trim().length > 0) ||
(aiMsg.tool_calls && aiMsg.tool_calls.length > 0)
);
});
this.model = options?.model ?? DEFAULT_MODEL;
const model = new ChatGoogle({
apiKey: options?.apiKey ?? process.env.GOOGLE_API_KEY,
modelName: this.model,
apiVersion: options?.apiVersion ?? DEFAULT_API_VERSION,
}).bindTools(tools);
return model.stream(filteredMessages, { metadata: { conversation_id: threadId } });
},
});
}
}