forked from alexis779/lambda-openai-proxy
-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
Objective: Extend the existing Lambda proxy to support Google Gemini API alongside the existing OpenAI functionality, using the provided reference implementation as the gold standard.
Constraints:
- Do not remove or break existing OpenAI functionality.
- Use
@google/generative-ai
library for Gemini interaction. - The URL endpoint is NOT needed, as the client lib handles that.
- GOLD STANDARD: Use the following code as the exact way to interact with the Gemini API. Mimic this code precisely when implementing the Gemini functionality.
- DO NOT MODIFY OpenAiSettings{}. It is SPECIFICALLY named "OpenAiSettings" for a reason, and should only be used for settings related to OpenAI.
import { GoogleGenerativeAI } from "@google/genai";
const ai = new GoogleGenAI({ apiKey: "GEMINI_API_KEY" });
async function main() {
const chat = ai.chats.create({
model: "gemini-2.0-flash",
history: [
{
role: "user",
parts: [{ text: "Hello" }],
},
{
role: "model",
parts: [{ text: "Great to meet you. What would you like to know?" }],
},
],
});
const stream1 = await chat.sendMessageStream({
message: "I have 2 dogs in my house.",
});
for await (const chunk of stream1) {
console.log(chunk.text);
console.log("_".repeat(80));
}
const stream2 = await chat.sendMessageStream({
message: "How many paws are in my house?",
});
for await (const chunk of stream2) {
console.log(chunk.text);
console.log("_".repeat(80));
}
}
await main();
Steps (with STRONG reference to the example code):
-
Install
@google/generative-ai
:npm install @google/generative-ai
-
Update
openai_servers.yaml
: Add agemini
entry. Key elements to mimic from the example:token
: Your Gemini API keymodel
: The Gemini model name (e.g., "gemini-1.5-pro-latest" or "gemini-2.0-flash" from the example). The example explicitly uses a model parameter inai.chats.create
, so include it.- Omit the
url
. - DO NOT INCLUDE provider: gemini. The name "gemini" in the yaml is the implicit provider.
gemini: token: YOUR_GEMINI_API_KEY model: gemini-2.0-flash # Use the example's model for consistency
-
Create
src/gemini_settings.ts
:- Create a new interface named
GeminiSettings
withtoken
andmodel
properties. The keys should match theopenai_servers.yaml
.
export interface GeminiSettings { token: string; model: string; }
- Create a new interface named
-
Modify
src/llm_client.ts
:- Import
OpenAi
fromopenai
andGoogleGenerativeAI
from@google/generative-ai
. - Import
GeminiSettings
from./gemini_settings
. - Modify constructor:
- Take in
settings: OpenAiSettings | GeminiSettings
as a parameter. - Have two class properties,
openaiSettings: OpenAiSettings
andgeminiSettings: GeminiSettings | null
- If
settings
includes aurl
property then the object is of typeOpenAiSettings
, otherwise it's aGeminiSettings
object. - Initialize
openai
client ONLY ifsettings
isOpenAiSettings
. - Initialize
gemini
client ONLY ifsettings
isGeminiSettings
. - Store the
model
:this.model = settings.model;
(as done in the reference).
- Take in
chatCompletionStreaming(content: string)
: This is the critical part.- Create a
GenerativeModel
instance:const model = this.gemini.getGenerativeModel({ model: this.model });
(exactly as in the example, passing the model from the config). - Create a
ChatSession
instance:const chat = model.startChat({ history: [] });
. Note: The example useshistory: []
. For now, keep it simple and do the same. IMPORTANT: Future Enhancement: This is where you would need to integrate the previous messages in the conversation, if you want to maintain a conversation history. - Call
sendMessageStream
:const result = await chat.sendMessageStream({ message: content });
. Note: Precisely mimic the example's use of{ message: content }
. This is key.content
is the user's message (the prompt). - Return the stream:
return result.stream;
- Create a
createCompletionStreaming(params: any)
: Remove this. The streaming logic is now withinchatCompletionStreaming
which is correct according to the example.chatCompletionNonStreaming(content: string)
: Implement if needed, but focus on streaming first.- Other methods: Remove/adjust unused functions that are no longer valid.
- Import
-
Modify
src/llm_proxy.ts
:- Adapt
transformGenerator
function: This will depend on the exact structure of the stream coming back fromgemini.sendMessageStream
. You will need to carefully examine thechunk
objects to extract the text.
export const transformGenerator = async function*<F, T>(iterator: AsyncIterator<F>, transform: (f: F) => T) { while (true) { const next = await iterator.next(); if (next.done) { return; } // Check what the shape of 'next.value' is, and adapt the transform function accordingly yield transform(next.value); } }
- Also needs to import
GeminiSettings
- Also needs to change signature of
getLlmClient
method togetLlmClient(server: string): LlmClient {
- Adapt
-
Modify
src/app_settings.ts
:- Also needs to check if
url
key is included to determine if the setting should be of typeOpenAiSettings
orGeminiSettings
. - Also needs to import
GeminiSettings
- Also needs to check if
-
Update Test file (
src/tests/index.test.ts
):- Add integration and unit tests for gemini server. Test streaming thoroughly.
-
Build and Deploy:
npm run build
followed bysam deploy --guided
Revised src/llm_client.ts
(Illustrative - Focus on Streaming):
import OpenAi from 'openai';
import { OpenAiSettings } from './openai_settings';
import { GeminiSettings } from './gemini_settings';
import { GoogleGenerativeAI } from "@google/genai";
export class LlmClient {
private openai: OpenAi | null;
private gemini: GoogleGenerativeAI | null;
private model: string;
private openaiSettings: OpenAiSettings | null;
private geminiSettings: GeminiSettings | null;
constructor(settings: OpenAiSettings | GeminiSettings) {
if ('url' in settings) {
// It's OpenAiSettings
this.openaiSettings = settings;
this.geminiSettings = null;
this.openai = new OpenAi({
baseURL: settings.url,
apiKey: settings.token,
});
this.gemini = null;
} else {
// It's GeminiSettings
this.geminiSettings = settings;
this.openaiSettings = null;
this.gemini = new GoogleGenerativeAI(settings.token);
this.openai = null;
}
this.model = settings.model;
}
async chatCompletionStreaming(content: string): Promise<any> {
if (this.openai) {
const params = {
model: this.model,
messages: [{ role: 'user', content: content }],
stream: true
};
return await this.openai.chat.completions.create(params);
} else if (this.gemini) {
const model = this.gemini.getGenerativeModel({ model: this.model });
const chat = model.startChat({
history: [], // IMPORTANT: Add history here later
});
const result = await chat.sendMessageStream({ message: content }); // Mimic example
return result.stream;
} else {
throw new Error(`Unsupported provider: ${this.provider}`);
}
}
}
Metadata
Metadata
Assignees
Labels
No labels