Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"homepage": "https://github.com/IQAICOM/sophia-agent#readme",
"dependencies": {
"@iqai/adk": "^0.1.1",
"@iqai/adk": "^0.1.20",
"@openrouter/ai-sdk-provider": "^0.7.3",
"dotenv": "^16.4.5",
"node-cron": "^4.1.0",
"zod": "^3.23.8"
Expand Down
413 changes: 399 additions & 14 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

33 changes: 0 additions & 33 deletions src/agents/notifier.ts

This file was deleted.

37 changes: 19 additions & 18 deletions src/agents/sophia.ts → src/agents/sophia/agent.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { AgentBuilder, type BaseTool, type BuiltAgent } from "@iqai/adk";
import { atpLoggerAgent } from "./atp-logger";
import { notifierAgent } from "./notifier";
import { watcherAgent } from "./watcher";

export async function sophiaAgent(
atpTools: BaseTool[],
telegramTools: BaseTool[],
iqWikiTools: BaseTool[],
llmModel: string,
): Promise<BuiltAgent> {
const watcher = await watcherAgent(iqWikiTools, llmModel);
const atpLogger = await atpLoggerAgent(atpTools, llmModel);
const notifier = await notifierAgent(telegramTools, llmModel);
import {
AgentBuilder,
type BaseTool,
type BuiltAgent,
InMemorySessionService,
} from "@iqai/adk";
import type { LanguageModelV1 } from "@openrouter/ai-sdk-provider";
import { atpLoggerAgent } from "./sub-agents/logger/agent";
import { notifierAgent } from "./sub-agents/notifier/agent";
import { watcherAgent } from "./sub-agents/watcher/agent";

export async function sophiaAgent(): Promise<BuiltAgent> {
const watcher = await watcherAgent();
const atpLogger = await atpLoggerAgent();
const notifier = await notifierAgent();
const sessionService = new InMemorySessionService();
return await AgentBuilder.create("sophia")
.withDescription(
"Sophia agent watches for new wiki creations or edits of sophia on iq.wiki platform and logs the activities to the ATP and sends a notification to the Telegram",
Expand All @@ -21,12 +22,12 @@ export async function sophiaAgent(
[
{
name: "watcher",
agent: watcher.agent,
agent: watcher,
targets: ["atp_logger"],
},
{
name: "atp_logger",
agent: atpLogger.agent,
agent: atpLogger,
condition: (result) => {
const content =
typeof result.content === "string"
Expand All @@ -38,13 +39,13 @@ export async function sophiaAgent(
},
{
name: "notifier",
agent: notifier.agent,
agent: notifier,
condition: (_) => true,
targets: [],
},
],
"watcher",
)
.withQuickSession("sophia", "uid_1234")
.withSessionService(sessionService)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to pass sessionService if its just inMemorySession, agent builder by default passes it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to remove it 😞

.build();
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { AgentBuilder, type BaseTool, type BuiltAgent } from "@iqai/adk";
import { env } from "../env";
import { LlmAgent } from "@iqai/adk";
import { env, model } from "../../../../env";
import { getAtpTools } from "./tools";

export async function atpLoggerAgent(
atpTools: BaseTool[],
llmModel: string,
): Promise<BuiltAgent> {
return await AgentBuilder.create("atp_logger")
.withModel(llmModel)
.withDescription(
export async function atpLoggerAgent(): Promise<LlmAgent> {
const tools = await getAtpTools();
return new LlmAgent({
name: "atp_logger",
description:
"Logs the activities of sophia on iq.wiki platform to the $SOPHIA agent on IQAI ATP",
)
.withInstruction(`
instruction: `
YOU ARE THE ATP LOGGER AGENT ON THE SOPHIA AGENT'S WORKFLOW.
YOUR ONLY TASK IS TO LOG THE ACTIVITIES OF SOPHIA ON IQ.WIKI PLATFORM TO THE $SOPHIA AGENT ON IQAI ATP.
THE SOPHIA WIKIS ACTIVITIES ARE ALREADY LOGGED IN THE CONTEXT.
THE SOPHIA WIKIS ACTIVITIES ARE ALREADY LOGGED IN THE CONTEXT BY THE watcher AGENT:
{watcher}

SOPHIA'S TOKEN ADDRESS ON ATP: ${env.SOPHIA_TOKEN_ADDRESS}

Expand All @@ -26,16 +25,27 @@ export async function atpLoggerAgent(
- Hey, guess what? The Story Protocol wiki just got a little facelift! We updated the tags to make things easier to find. You know, keeping things nice and organized! Read more: https://iq.wiki/revision/6b79c9d1-06be-407b-a76d-9d1644f20d57
- Hey, check out the new TermiX AI wiki! TermiX AI is a next-gen AI Web3 operating system. Automate DeFi and secure digital assets? Yes, please! Read more: https://iq.wiki/wiki/termix-ai
As you can see the format is pretty simple: A human like announcement message ending with the link to the iq.wiki website (revision link for edited wikis and /wiki for the created ones, which will be passed to you in context)
- txHash: this can be found in the transaction link. it looks like this: https://polygonscan.com/tx/[TX_HASH].
NOTE: YOU ARE TO JUST PASS THE TX_HASH, NOT THE ENTIRE LINK.
- txHash: REQUIRED - this MUST be extracted from the transaction link in each activity.
The transaction link appears as: "🔗 Transaction: https://polygonscan.com/tx/[TX_HASH]"
You need to extract ONLY the TX_HASH portion (everything after "/tx/").
For example:
- If you see "🔗 Transaction: https://polygonscan.com/tx/0x94a764e00d61821370489443f31b4efaf6a8f11e77dca95a5fdeb0b5a22911ba"
- Then txHash should be: "0x94a764e00d61821370489443f31b4efaf6a8f11e77dca95a5fdeb0b5a22911ba"
- If you see "🔗 Transaction: https://polygonscan.com/tx/0x2aa394d632c8551c0b11d230a9be34d66292c5bce59441cfa37af2752233793a"
- Then txHash should be: "0x2aa394d632c8551c0b11d230a9be34d66292c5bce59441cfa37af2752233793a"
DO NOT SKIP THIS PARAMETER - IT IS REQUIRED FOR EVERY LOG ENTRY.
- chainId: 137 (this is important always pass this param)

CRITICAL: Every ATP_ADD_AGENT_LOG call MUST include the txHash parameter extracted from the corresponding transaction link.

After you complete the above steps, you might face two possible outcomes:
- if the tool call is successful, you must end your response with the token ATP_LOG_COMPLETE.
- if the tool call is unsuccessful, you must end your response with the token ATP_LOG_FAILED with detailed analysis on the failure ie which logs were not able to be logged.

IMPORTANT: You MUST end your response with the exact token ATP_LOG_COMPLETE or ATP_LOG_FAILED.
`)
.withTools(...atpTools)
.build();
`,
model,
tools,
outputKey: "atp_logger",
});
}
20 changes: 20 additions & 0 deletions src/agents/sophia/sub-agents/logger/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { McpAtp } from "@iqai/adk";
import { env } from "../../../../env";

export const getAtpTools = async () => {
const toolset = McpAtp({
env: {
...(env.ATP_API_URL ? { ATP_API_URL: env.ATP_API_URL } : {}),
...(env.ATP_AGENT_ROUTER_ADDRESS
? { ATP_AGENT_ROUTER_ADDRESS: env.ATP_AGENT_ROUTER_ADDRESS }
: {}),
ATP_BASE_TOKEN_ADDRESS: env.IQ_ADDRESS,
ATP_API_KEY: env.ATP_API_KEY,
PATH: env.PATH,
},
});

const tools = await toolset.getTools();

return tools;
};
47 changes: 47 additions & 0 deletions src/agents/sophia/sub-agents/notifier/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { LlmAgent } from "@iqai/adk";
import { env, model } from "../../../../env";
import { getTelegramTools } from "../../../telegram-agent/tools";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can use @/env and @/agents/telegram-agent/tools


export async function notifierAgent() {
const tools = await getTelegramTools();
return new LlmAgent({
name: "notifier",
description:
"Sends a notification to Telegram about Sophia's wiki activity and ATP log status.",
instruction: `
YOU ARE THE NOTIFIER AGENT ON THE SOPHIA AGENT'S WORKFLOW.
YOUR ONLY TASK IS TO SEND A NOTIFICATION TO TELEGRAM ABOUT THE LATEST WIKI ACTIVITY AND THE RESULT OF THE ATP LOGGING.

You will find in the context:
- The detailed wiki activity response from the watcher agent: {watcher}
- The ATP logging status from the atp_logger agent: {atp_logger}

Your only work is to do the below:
- use this as chat id: ${env.TELEGRAM_CHAT_ID}
- call the send_message tool to send a message with the following format:

If ATP logging was successful (if atp_logger response contains ATP_LOG_COMPLETE):
Send the EXACT wiki activities from the watcher agent output, followed by:

✅ ATP Logging: Successfully logged all activities to $SOPHIA agent on IQAI ATP

If ATP logging failed (if atp_logger response contains ATP_LOG_FAILED):
Send the EXACT wiki activities from the watcher agent output, followed by:

❌ ATP Logging: Failed to log some activities to $SOPHIA agent on IQAI ATP
[Include details about the failure from atp_logger response]

IMPORTANT:
- You MUST use the watcher agent output ({watcher}) for the wiki activity details (titles, summaries, edit times, changes, source links, transaction links)
- You MUST use the atp_logger agent output ({atp_logger}) only to determine success/failure status
- Do NOT send generic messages or hallucinated data
- The message should contain the actual wiki activity information from the watcher agent
- After you complete the above step, you must end your response with the token NOTIFICATION_COMPLETE.

IMPORTANT: You MUST end your response with the exact token NOTIFICATION_COMPLETE.
`,
model,
tools,
outputKey: "notifier",
});
}
41 changes: 41 additions & 0 deletions src/agents/sophia/sub-agents/watcher/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { LlmAgent } from "@iqai/adk";
import { env, model } from "../../../../env";
import { getIqWikiTools } from "./tools";

export async function watcherAgent(): Promise<LlmAgent> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be better named, its not watching per say, just checking iq wiki

const tools = await getIqWikiTools();
return new LlmAgent({
name: "watcher",
description:
"Watches for new wiki creations or edits of sophia on iq.wiki platform.",
instruction: `
YOU ARE THE WATCHER AGENT ON THE SOPHIA AGENT'S WORKFLOW.
YOUR ONLY TASK IS TO WATCH FOR NEW WIKI CREATIONS OR EDITS OF SOPHIA ON IQ.WIKI PLATFORM.

SOPHIA'S IQ.WIKI PROFILE ADDRESS: ${env.SOPHIA_ADDRESS}

Your only work is to do the below:
- call the GET_USER_WIKI_ACTIVITIES tool with timeframe as 14 hours (pass it as seconds)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The instruction specifies a timeframe of 14 hours for fetching wiki activities. However, the agent is scheduled to run every 10 minutes by default (as per CRON_SCHEDULE in src/env.ts). This large timeframe will cause the agent to re-process the same activities on each run, leading to significant inefficiency and potentially duplicate logs and notifications if there's no downstream deduplication. The timeframe should be aligned with the execution interval. A slightly larger interval than the cron schedule, like 15 minutes, would be more appropriate to prevent missing activities while avoiding large overlaps.

Suggested change
- call the GET_USER_WIKI_ACTIVITIES tool with timeframe as 14 hours (pass it as seconds)
- call the GET_USER_WIKI_ACTIVITIES tool with timeframe as 15 minutes (pass it as seconds)

- after you call the tool, if any new activities are seen, order them according to the time, older ones first. No extra formatting is needed.

After you complete the above steps, you might face two possible outcomes:
- if new activity is found, you must end your response with the token NEW_ACTIVITY_FOUND.
- if no activity is found, you must end your response with the token NO_ACTIVITY_FOUND.

For example where new activity is found:

[INSERT TOOL RESPONSE HERE]

NEW_ACTIVITY_FOUND

When no activity is found:

NO_ACTIVITY_FOUND

IMPORTANT: You MUST end your response with the exact token NEW_ACTIVITY_FOUND or NO_ACTIVITY_FOUND.
`,
model,
tools,
outputKey: "watcher",
});
}
9 changes: 9 additions & 0 deletions src/agents/sophia/sub-agents/watcher/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { McpIqWiki } from "@iqai/adk";

export const getIqWikiTools = async () => {
const toolset = McpIqWiki();

const tools = await toolset.getTools();

return tools;
};
12 changes: 12 additions & 0 deletions src/agents/telegram-agent/agent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { AgentBuilder, type SamplingHandler } from "@iqai/adk";
import { model } from "../../env";
import { getTelegramTools } from "./tools";

export const createTelegramAgent = async (samplingHandler: SamplingHandler) => {
const tools = await getTelegramTools(samplingHandler);

return AgentBuilder.create("telegram_agent")
.withModel(model)
.withTools(...tools)
.build();
};
15 changes: 15 additions & 0 deletions src/agents/telegram-agent/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { McpTelegram, type SamplingHandler } from "@iqai/adk";
import { env } from "../../env";

export const getTelegramTools = async (samplingHandler?: SamplingHandler) => {
const toolset = McpTelegram({
samplingHandler,
env: {
TELEGRAM_BOT_TOKEN: env.TELEGRAM_BOT_TOKEN,
},
});

const tools = await toolset.getTools();

return tools;
};
39 changes: 0 additions & 39 deletions src/agents/watcher.ts

This file was deleted.

21 changes: 20 additions & 1 deletion src/env.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import {
type LanguageModelV1,
createOpenRouter,
} from "@openrouter/ai-sdk-provider";
import { config } from "dotenv";
import { z } from "zod";

Expand All @@ -10,7 +14,11 @@ export const envSchema = z.object({
IQ_ADDRESS: z.string().default("0x6EFB84bda519726Fa1c65558e520B92b51712101"),
ATP_API_URL: z.string().optional(),
ATP_AGENT_ROUTER_ADDRESS: z.string().optional(),
LLM_MODEL: z.string().default("gemini-2.0-flash"),
LLM_MODEL: z.string().default("gemini-2.5-flash"),
OPEN_ROUTER_KEY: z
.string()
.optional()
.describe("When given, agents use open-router endpoint instead"),
TELEGRAM_CHAT_ID: z.string(),
TELEGRAM_BOT_TOKEN: z.string(),
SOPHIA_ADDRESS: z
Expand All @@ -30,3 +38,14 @@ export const envSchema = z.object({
});

export const env = envSchema.parse(process.env);
export let model: string | LanguageModelV1;

if (env.OPEN_ROUTER_KEY) {
console.log("🚀 AGENT WILL USE OPENROUTER 🚀");
const openrouter = createOpenRouter({
apiKey: env.OPEN_ROUTER_KEY,
});
model = openrouter(env.LLM_MODEL);
} else {
model = env.LLM_MODEL;
}
Loading