Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions backend/MCP_Execution_Server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ export const config = {
apiKey: process.env.NEYNAR_API_KEY ?? "0x",
signerUuid: process.env.NEYNAR_SIGNER_UUID ?? "0x",
},
gemini:{
apiKey: process.env.GEMINI_API_KEY ?? "0x",
gemini: {
apiKey: process.env.GEMINI_API_KEY ?? "",
},
openai: {
apiKey: process.env.OPENAI_API_KEY,
apiKey: process.env.OPENAI_API_KEY ?? "",
},
supabase: {
SUPABASE_URL: process.env.SUPABASE_URL ?? "",
Expand Down
6 changes: 3 additions & 3 deletions backend/MCP_Execution_Server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ async function main() {
config.reclaim.appId,
config.reclaim.appSecret,
);
const geminiai = new GeminiAiService(config.gemini.apiKey as string);
const ai = new AIService(config.openai.apiKey as string);
const geminiai = new GeminiAiService(config.gemini.apiKey);
const ai = new AIService(config.openai.apiKey);
const avs = new AVSService(
config.network.rpcBaseAddress,
config.network.privateKey,
Expand All @@ -41,7 +41,7 @@ async function main() {
avs,
ipfs,
);
const user = new UserService(neynar, ai, db);
const user = new UserService(neynar, ai, db, geminiai);

// Initialize services
const services = {
Expand Down
44 changes: 28 additions & 16 deletions backend/MCP_Execution_Server/src/services/gemini_ai.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GoogleGenAI } from "@google/genai";
import { GoogleGenAI, Type } from "@google/genai";

export class GeminiAiService {
private ai: GoogleGenAI;
Expand All @@ -9,7 +9,7 @@ export class GeminiAiService {
this.ai = new GoogleGenAI({
apiKey: this.geminiApiKey,
});
this.geminiModel = "gemini-2.5-flash";
this.geminiModel = "gemini-2.5-flash-preview-04-17";
this.embeddingModel = "embedding-001";
}

Expand Down Expand Up @@ -42,10 +42,17 @@ Bad output:
`;

try {
/**
* TODO: Fix this
*
* Received error:
* ClientError: got status: 404 Not Found. {"error":{"code":404,"message":"models/gemini-2.5-flash is not found for API version v1beta, or is not supported for generateContent. Call ListModels to see the list of available models and their supported methods.","status":"NOT_FOUND"}}
*/
const result = await this.ai.models.generateContent({
model: this.geminiModel,
contents: prompt,
});

return result.text;
} catch (err: any) {
console.error("summarizeUserContext error", err);
Expand Down Expand Up @@ -151,8 +158,7 @@ ${formattedTrendingFeeds}
7. AVOID talking about airdrops and giveaways.
</instructions>

<output_format>
You MUST respond with ONLY a valid JSON object containing exactly these two fields:
<output_guidelines>
- replyText: A string with the message "You should connect with [author_username], who said: '[cast_text]'" - Max 60-70 words, 320 characters.
- link: A string with the URL "https://warpcast.com/[author_username]/[cast_hash]" - this should MATCH the cast you selected and not a fabricated/random one.

Expand All @@ -169,30 +175,36 @@ If and only if you find no relevant casts, respond with this exact format:
"replyText": "No relevant trending casts found in the provided data.",
"link": ""
}
</output_format>

<response_requirements>
Your response should be a valid JSON object with no additional text or explanation.
</response_requirements>
</output_guidelines>
`;

try {
const result = await this.ai.models.generateContent({
model: this.geminiModel,
contents: prompt,
config: {
responseMimeType: "application/json",
responseSchema: {
type: Type.OBJECT,
properties: {
replyText: { type: Type.STRING },
link: { type: Type.STRING },
},
required: ["replyText", "link"],
},
},
});
const content = result.text?.trim();

const content = result.text;
try {
return content
? JSON.parse(content)
: {
replyText:
"No relevant trending casts found in the provided data.",
link: "",
};
replyText:
"No relevant trending casts found in the provided data.",
link: "",
};
} catch (jsonErr) {
console.warn("Response was not valid JSON:", content);
console.warn("Response was not valid JSON Gemini:", content);
return { error: "Invalid JSON format in AI response", raw: content };
}
} catch (err: any) {
Expand Down
22 changes: 6 additions & 16 deletions backend/MCP_Execution_Server/src/services/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NeynarService } from "./neynar.service.js";
import type { AIService } from "./ai.service.js";
import type { DBService } from "./db.service.js";
import { GeminiAiService } from "./gemini_ai.service.js";
import type { GeminiAiService } from "./gemini_ai.service.js";

export enum FID_STATUS {
NOT_EXIST = "NOT_EXIST",
Expand All @@ -23,28 +23,17 @@ export class UserService {
try {
if (this.geminiService) {
const summary = await this.geminiService.summarizeUserContext(userData);
console.log("Summary generated by Gemini");
if (summary) return summary;
}
console.log("Summary generated by AIService");
return await this.aiService.summarizeUserContext(userData);
} catch (e) {
console.warn("Fallback to AIService for summarizeUserContext");
return await this.aiService.summarizeUserContext(userData);
}
}

private async generateEmbeddingsSafe(text: string): Promise<any | null> {
try {
if (this.geminiService) {
const embeddings = await this.geminiService.generateEmbeddings(text);
if (embeddings) return embeddings;
}
return await this.aiService.generateEmbeddings(text);
} catch (e) {
console.warn("Fallback to AIService for generateEmbeddings");
return await this.aiService.generateEmbeddings(text);
}
}

private async generateReplyForCastSafe(input: {
userCast: string;
castSummary: string;
Expand Down Expand Up @@ -158,7 +147,7 @@ export class UserService {
const userData = await this.neynarService.aggregateUserData(fid);
const summary = await this.summarizeUserContextSafe(userData);
if (!summary) throw new Error("Summary generation failed");
const embeddings = await this.generateEmbeddingsSafe(summary);
const embeddings = await this.aiService.generateEmbeddings(summary);
if (!embeddings) throw new Error("Embedding generation failed");

const { success } = await this.db.registerAndSubscribeFID(
Expand Down Expand Up @@ -259,7 +248,8 @@ export class UserService {

// Step 2: Generate embeddings for the received cast
const castSummary = await this.findMeaningFromTextSafe(cast.text);
const castEmbeddings = await this.generateEmbeddingsSafe(castSummary);
const castEmbeddings =
await this.aiService.generateEmbeddings(castSummary);
if (!castEmbeddings) {
throw new Error("Embedding generation failed for the cast text");
}
Expand Down