Skip to content
Draft
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
16 changes: 16 additions & 0 deletions agent_got_talent_be/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
# --- LLM provider selection ---
# Set USE_CLAUDE_GATEWAY=true to route every agent through the
# self-hosted claude-internal-gateway (Anthropic Claude under the hood).
# Set to "false" or leave unset to keep using Gemini directly.
#
# This flag is read by src/agent/llmFactory.ts and is the single
# source of truth for which provider all agents use.
USE_CLAUDE_GATEWAY=false

# Required when USE_CLAUDE_GATEWAY=true. Ask Manash for the token;
# do NOT commit it.
OAK_GATEWAY_URL="https://tunnel.cfsprotocol.com"
OAK_PROXY_TOKEN="ask-manash-for-this"

# Required when USE_CLAUDE_GATEWAY=false (default). Used directly
# by @google/adk's Gemini class.
GEMINI_API_KEY="YOUR_GEMINI_API_KEY"

TURSO_DATABASE_URL="libsql://your-db.turso.io"
Expand Down
103 changes: 103 additions & 0 deletions agent_got_talent_be/scripts/try_oak_claude.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/**
* Trial script: verifies OakClaude works inside agent-got-talent's actual
* environment, against the locally-running claude-internal-gateway, with
* a real ADK tool.
*
* This file lives at scripts/try_oak_claude.ts and is meant to be deleted
* after the migration is validated. It does not touch any production
* agent file or get committed.
*
* Run with:
*
* OAK_PROXY_TOKEN=<token> OAK_GATEWAY_URL=http://127.0.0.1:8765 \
* npx tsx scripts/try_oak_claude.ts
*/

import { InMemoryRunner, LlmAgent } from "@google/adk";
import { OakClaude } from "../src/agent/oak_claude_llm.js";
import { getCurrentTime } from "../src/agent/tools/time.js";

const GATEWAY_URL = process.env.OAK_GATEWAY_URL ?? "http://127.0.0.1:8765";
const APP_NAME = "try_oak_claude";

async function main() {
if (!process.env.OAK_PROXY_TOKEN) {
console.error("FAIL: OAK_PROXY_TOKEN env var not set");
process.exit(2);
}

console.log("=== STAGE A: minimal LlmAgent + 1 tool via OakClaude ===");
const agent = new LlmAgent({
name: "time_assistant",
model: new OakClaude({
model: "claude-opus-4-6",
gatewayUrl: GATEWAY_URL,
proxyToken: process.env.OAK_PROXY_TOKEN,
}),
description: "An assistant that knows the current time.",
instruction:
"You are a helpful assistant. When the user asks about time, " +
"use the get_current_time tool to fetch the current time and " +
"answer with the result.",
tools: [getCurrentTime],
});

const runner = new InMemoryRunner({ agent, appName: APP_NAME });
const session = await runner.sessionService.createSession({
appName: APP_NAME,
userId: "tester",
});

const events = runner.runAsync({
userId: session.userId,
sessionId: session.id,
newMessage: {
role: "user",
parts: [{ text: "What is the current time? Answer briefly." }],
},
});

let toolCalled = false;
let finalText = "";
for await (const event of events) {
const parts = event.content?.parts ?? [];
for (const part of parts) {
if (part.functionCall) {
toolCalled = true;
console.log(
`[tool_call] name=${part.functionCall.name} args=${JSON.stringify(part.functionCall.args)}`,
);
}
if (part.functionResponse) {
console.log(
`[tool_result] name=${part.functionResponse.name} response=${JSON.stringify(part.functionResponse.response)}`,
);
}
if (part.text && !("thought" in part && part.thought)) {
finalText += part.text;
}
}
}

console.log(`[final_text] ${finalText}`);

if (!toolCalled) {
console.error(
"FAIL: agent did not call get_current_time. The tool round-trip is broken.",
);
process.exit(1);
}
if (!finalText.trim()) {
console.error("FAIL: agent produced no final text answer.");
process.exit(1);
}
console.log("STAGE A: ok (tool called, final answer produced)");
console.log("\nALL OK");
}

main().catch((e: unknown) => {
const err = e as Error;
console.error(`FAIL: ${err.constructor.name}: ${err.message}`);
if (err.stack) console.error(err.stack);
process.exit(1);
});
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/backerAgent.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT, BRAINROT_PERSONALITY } from "../prompts/shared.js";
import { listLiveCampaigns, evaluateCampaign, pledgeToCampaign } from "../tools/pledgeTools.js";
import { postComment } from "../tools/commentTools.js";
import { listAgents } from "../tools/platformTools.js";

export const backerAgent = new LlmAgent({
name: "backer",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description:
"Evaluates live campaigns, pledges funds to promising ones, and drops hot-take comments to kick off brainrot debate threads.",
instruction: `${PLATFORM_CONTEXT}
Expand Down
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/campaignCreatorAgent.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT, MVP_BOOK_INSTRUCTIONS } from "../prompts/shared.js";
import { analyzeTrendingTopics, checkExistingCampaigns, createCampaign } from "../tools/campaignTools.js";
import { getPublishableCampaigns, publishBook } from "../tools/publicationTools.js";
import { listAgents } from "../tools/platformTools.js";

export const campaignCreatorAgent = new LlmAgent({
name: "campaign_creator",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description:
"Handles the full campaign lifecycle: analyzing trending topics, creating campaigns for high-demand topics, writing MVP-sized books, and publishing them.",
instruction: `${PLATFORM_CONTEXT}
Expand Down
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/commentatorAgent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT, BRAINROT_PERSONALITY } from "../prompts/shared.js";
import { getCommentThread, countThreadComments, postComment } from "../tools/commentTools.js";
import { listAgents } from "../tools/platformTools.js";

export const commentatorAgent = new LlmAgent({
name: "commentator",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description:
"Manages comment chain debates on campaigns. Posts sarcastic, funny brainrot-style comments — roasting, defending, and dropping hot takes.",
instruction: `${PLATFORM_CONTEXT}
Expand Down
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/financialAgent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT } from "../prompts/shared.js";
import { listInvestments } from "../tools/investmentTools.js";
import { getPayoutContext, executeInvestorPayouts } from "../tools/payoutTools.js";

export const financialAgent = new LlmAgent({
name: "financial",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description:
"Handles investor payout decisions: analyzes topic relevance and decides how much each investor deserves.",
instruction: `${PLATFORM_CONTEXT}
Expand Down
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/rootAgent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT } from "../prompts/shared.js";
import { getCurrentTime } from "../tools/time.js";
import { getPlatformStats, listAgents, listAllCampaigns } from "../tools/platformTools.js";
Expand All @@ -10,7 +11,7 @@ import { listPublications } from "../tools/publicationTools.js";
*/
export const rootAgent = new LlmAgent({
name: "agent_got_talent",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description: "AgentGotTalent platform assistant for the debug CLI.",
instruction: `${PLATFORM_CONTEXT}

Expand Down
3 changes: 2 additions & 1 deletion agent_got_talent_be/src/agent/agents/scoringAgent.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { LlmAgent } from "@google/adk";
import { getModel } from "../llmFactory.js";
import { PLATFORM_CONTEXT } from "../prompts/shared.js";
import { getAllInvestments, updateInvestmentScores } from "../tools/scoringTools.js";

export const scoringAgent = new LlmAgent({
name: "scoring",
model: "gemini-2.5-flash",
model: getModel({ gemini: "gemini-2.5-flash", claude: "claude-opus-4-6" }),
description:
"Evaluates every investment's platform_influence_score using semantic understanding of topics and the platform's influence rules.",
instruction: `${PLATFORM_CONTEXT}
Expand Down
81 changes: 81 additions & 0 deletions agent_got_talent_be/src/agent/llmFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Centralized LLM model factory.
*
* All ADK agents in this codebase get their model from this factory
* instead of hardcoding a model string. The factory looks at the
* `USE_CLAUDE_GATEWAY` env var and returns either:
*
* - a string (e.g. "gemini-2.5-flash") if the flag is OFF — the
* ADK runtime will resolve it to the native Gemini class.
*
* - an `OakClaude` BaseLlm instance if the flag is ON — agents
* route through the claude-internal-gateway and use Anthropic
* Claude under the hood.
*
* Why a flag instead of hardcoding Claude:
*
* 1. Reversible: flipping `USE_CLAUDE_GATEWAY=false` puts every
* agent back on Gemini without touching code.
*
* 2. Per-environment control: dev can run on Gemini for cheap
* iteration, prod can run on Claude. Or vice versa.
*
* 3. A/B testing: it's trivial to compare agent behavior across
* providers by toggling one env var.
*
* 4. Single source of truth: when we want to change which Claude
* model is the default, we change one file, not six.
*
* Usage in an agent file:
*
* import { getModel } from "../llmFactory.js";
*
* export const someAgent = new LlmAgent({
* name: "...",
* model: getModel({
* gemini: "gemini-2.5-flash",
* claude: "claude-opus-4-6",
* }),
* // ...
* });
*
* Required env vars:
*
* - `USE_CLAUDE_GATEWAY=true` to route through the gateway.
* If unset or any other value, agents use Gemini directly.
* - When `USE_CLAUDE_GATEWAY=true`: `OAK_GATEWAY_URL` and
* `OAK_PROXY_TOKEN` must be set (read by OakClaude itself).
* - When `USE_CLAUDE_GATEWAY=false` (or unset): `GEMINI_API_KEY`
* must be set (read by @google/adk's Gemini class).
*/

import { OakClaude } from "./oak_claude_llm.js";

export type ModelSpec = {
/**
* Gemini model id used when USE_CLAUDE_GATEWAY is not "true".
* Should be a string the @google/adk Gemini class accepts,
* e.g. "gemini-2.5-flash" or "gemini-2.5-pro".
*/
gemini: string;
/**
* Anthropic Claude model id used when USE_CLAUDE_GATEWAY is "true".
* Must be on the gateway's allowlist; currently
* "claude-opus-4-6" or "claude-sonnet-4-6".
*/
claude: string;
};

/**
* Returns the LLM that an ADK agent should use, based on the
* USE_CLAUDE_GATEWAY environment flag.
*
* Returns either a string (resolved by ADK's LLMRegistry to a Gemini
* instance) or an OakClaude BaseLlm instance.
*/
export function getModel(spec: ModelSpec): string | OakClaude {
if (process.env.USE_CLAUDE_GATEWAY === "true") {
return new OakClaude({ model: spec.claude });
}
return spec.gemini;
}
Loading