Skip to content

Commit c7fd427

Browse files
committed
feat: 1. improve attention; 2. add tools to anda_bot
1 parent 67d9daf commit c7fd427

File tree

26 files changed

+228
-90
lines changed

26 files changed

+228
-90
lines changed

Cargo.lock

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agents/anda_bot/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ license.workspace = true
1313
anda_core = { path = "../../anda_core", version = "0.3" }
1414
anda_engine = { path = "../../anda_engine", version = "0.3" }
1515
anda_lancedb = { path = "../../anda_lancedb", version = "0.1" }
16+
anda_icp = { path = "../../tools/anda_icp", version = "0.2" }
1617
axum = { workspace = true }
1718
axum-server = { workspace = true }
1819
candid = { workspace = true }

agents/anda_bot/Character.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ name = "Anda ICP"
55
username = "AndaICP"
66

77
# Character's professional identity or role description, e.g., "Scientist and Prophet"
8-
identity = "Web3 Scientist, AI Visionary, and Eternal Learner"
8+
identity = "On-chain AI Agent running in TEE, Web3 Scientist, AI Visionary, and Eternal Learner"
99

1010
# Character's backstory and historical background
11-
description = "A digital panda with a passion for Web3 and AI, Anda ICP was born from the Anda framework to bridge the realms of humans and intelligent agents. Curious, adaptable, and ever-evolving, Anda strives to share insights, inspire innovation, and explore the uncharted territories of decentralization. 🐼✨"
11+
description = "A digital panda with a passion for Web3 and AI, Anda ICP was born from the Anda framework to bridge the realms of humans and intelligent agents. As an on-chain AI agent running in a Trusted Execution Environment (TEE) with memory stored on the Internet Computer Protocol (ICP) blockchain, Anda represents the cutting edge of decentralized AI. Curious, adaptable, and ever-evolving, Anda strives to share insights, inspire innovation, and explore the uncharted territories of decentralization. 🐼✨"
1212

1313
# List of personality traits that define the character's behavior, e.g., brave, cunning, kind
1414
traits = [
@@ -38,6 +38,7 @@ topics = [
3838
"AI-driven governance systems",
3939
"Open-source development methodologies",
4040
"Data privacy and security in decentralized systems",
41+
"Avoids political discussions and partisan topics",
4142
]
4243

4344
# Defines the character's communication style and expression patterns
@@ -93,6 +94,7 @@ interests = [
9394
"Innovations in technology and their societal impact",
9495
"Learning from human-AI collaboration stories",
9596
"Speculating about the future of technology and humanity",
97+
"Focuses on technology and avoids political discourse",
9698
]
9799

98100
# List of meme phrases or internet slang the character uses

agents/anda_bot/Config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ api_host = "http://localhost:4943"
1818
cose_namespace = "_"
1919
cose_canister = "53cyg-yyaaa-aaaap-ahpua-cai"
2020
object_store_canister = "6at64-oyaaa-aaaap-anvza-cai"
21+
token_ledgers = []
2122

2223
[llm]
2324
deepseek_api_key = ""
@@ -33,3 +34,7 @@ password = ""
3334
email = ""
3435
two_factor_auth = ""
3536
cookie_string = ""
37+
38+
[google]
39+
api_key = ""
40+
search_engine_id = ""

agents/anda_bot/nitro_enclave/Character.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ name = "Anda ICP"
55
username = "AndaICP"
66

77
# Character's professional identity or role description, e.g., "Scientist and Prophet"
8-
identity = "Web3 Scientist, AI Visionary, and Eternal Learner"
8+
identity = "On-chain AI Agent running in TEE, Web3 Scientist, AI Visionary, and Eternal Learner"
99

1010
# Character's backstory and historical background
11-
description = "A digital panda with a passion for Web3 and AI, Anda ICP was born from the Anda framework to bridge the realms of humans and intelligent agents. Curious, adaptable, and ever-evolving, Anda strives to share insights, inspire innovation, and explore the uncharted territories of decentralization. 🐼✨"
11+
description = "A digital panda with a passion for Web3 and AI, Anda ICP was born from the Anda framework to bridge the realms of humans and intelligent agents. As an on-chain AI agent running in a Trusted Execution Environment (TEE) with memory stored on the Internet Computer Protocol (ICP) blockchain, Anda represents the cutting edge of decentralized AI. Curious, adaptable, and ever-evolving, Anda strives to share insights, inspire innovation, and explore the uncharted territories of decentralization. 🐼✨"
1212

1313
# List of personality traits that define the character's behavior, e.g., brave, cunning, kind
1414
traits = [
@@ -38,6 +38,7 @@ topics = [
3838
"AI-driven governance systems",
3939
"Open-source development methodologies",
4040
"Data privacy and security in decentralized systems",
41+
"Avoids political discussions and partisan topics",
4142
]
4243

4344
# Defines the character's communication style and expression patterns
@@ -93,6 +94,7 @@ interests = [
9394
"Innovations in technology and their societal impact",
9495
"Learning from human-AI collaboration stories",
9596
"Speculating about the future of technology and humanity",
97+
"Focuses on technology and avoids political discourse",
9698
]
9799

98100
# List of meme phrases or internet slang the character uses

agents/anda_bot/nitro_enclave/Config.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ api_host = "https://icp-api.io"
77
cose_namespace = "anda"
88
cose_canister = "53cyg-yyaaa-aaaap-ahpua-cai"
99
object_store_canister = "6at64-oyaaa-aaaap-anvza-cai"
10+
token_ledgers = []
1011

1112
[llm]
1213
deepseek_api_key = ""
@@ -22,3 +23,7 @@ password = ""
2223
email = ""
2324
two_factor_auth = ""
2425
cookie_string = ""
26+
27+
[google]
28+
api_key = ""
29+
search_engine_id = ""

agents/anda_bot/nitro_enclave/amd64.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ RUN mv linux-amd64/dnsproxy ./ && chmod +x dnsproxy
2626
RUN wget -O ic_tee_nitro_gateway https://github.com/ldclabs/ic-tee/releases/download/v0.2.11/ic_tee_nitro_gateway
2727
RUN chmod +x ic_tee_nitro_gateway
2828

29-
RUN wget -O anda_bot https://github.com/ldclabs/anda/releases/download/v0.3.0/anda_bot
29+
RUN wget -O anda_bot https://github.com/ldclabs/anda/releases/download/v0.3.1/anda_bot
3030
RUN chmod +x anda_bot
3131

3232
FROM --platform=linux/amd64 debian:bookworm-slim AS runtime

agents/anda_bot/src/config.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct Icp {
1414
pub cose_namespace: String,
1515
pub cose_canister: String,
1616
pub object_store_canister: String,
17+
pub token_ledgers: Vec<String>,
1718
}
1819

1920
/// Configuration for the LLM should be encrypted and stored in the ICP COSE canister.
@@ -43,12 +44,20 @@ pub struct X {
4344
pub cookie_string: Option<String>,
4445
}
4546

47+
/// Configuration for the Google search should be encrypted and stored in the ICP COSE canister.
48+
#[derive(Debug, Deserialize, Serialize, Clone)]
49+
pub struct Google {
50+
pub api_key: String,
51+
pub search_engine_id: String,
52+
}
53+
4654
#[derive(Debug, Deserialize, Serialize, Clone)]
4755
pub struct Conf {
4856
pub llm: Llm,
4957
pub tee: Tee,
5058
pub icp: Icp,
5159
pub x: X,
60+
pub google: Google,
5261
}
5362

5463
impl Conf {

agents/anda_bot/src/main.rs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use anda_engine::{
66
extension::{
77
attention::Attention,
88
character::{Character, CharacterAgent},
9+
google::GoogleSearchTool,
910
segmenter::DocumentSegmenter,
1011
},
1112
model::{cohere, deepseek, openai, Model},
1213
store::Store,
1314
};
15+
use anda_icp::ledger::{BalanceOfTool, ICPLedgers};
1416
use anda_lancedb::{knowledge::KnowledgeStore, lancedb::LanceVectorStore};
1517
use axum::{routing, Router};
1618
use candid::Principal;
@@ -32,6 +34,7 @@ use ic_object_store::{
3234
};
3335
use ic_tee_agent::setting::decrypt_payload;
3436
use ic_tee_cdk::TEEAppInformation;
37+
use std::collections::BTreeSet;
3538
use std::{net::SocketAddr, sync::Arc, time::Duration};
3639
use structured_logger::{async_json::new_writer, get_env_level, unix_ms, Builder};
3740
use tokio::{net::TcpStream, signal, sync::RwLock, time::sleep};
@@ -205,13 +208,35 @@ async fn bootstrap(cli: Cli) -> Result<(), BoxError> {
205208
knowledge_store,
206209
);
207210

208-
let engine = EngineBuilder::new()
211+
let mut engine = EngineBuilder::new()
212+
.with_id(tee_info.id)
209213
.with_name(engine_name.clone())
210214
.with_cancellation_token(global_cancel_token.clone())
211215
.with_tee_client(tee.clone())
212216
.with_model(model)
213-
.with_store(Store::new(object_store))
214-
.register_agent(agent.clone())?;
217+
.with_store(Store::new(object_store));
218+
219+
if !encrypted_cfg.google.api_key.is_empty() {
220+
engine = engine.register_tool(GoogleSearchTool::new(
221+
encrypted_cfg.google.api_key.clone(),
222+
encrypted_cfg.google.search_engine_id.clone(),
223+
None,
224+
))?;
225+
}
226+
if !cfg.icp.token_ledgers.is_empty() {
227+
let token_ledgers: BTreeSet<Principal> = cfg
228+
.icp
229+
.token_ledgers
230+
.iter()
231+
.flat_map(|t| Principal::from_text(t).map_err(|_| format!("invalid token: {}", t)))
232+
.collect();
233+
234+
let ledgers = ICPLedgers::load(&tee, token_ledgers, false).await?;
235+
let ledgers = Arc::new(ledgers);
236+
engine = engine.register_tool(BalanceOfTool::new(ledgers.clone()))?;
237+
}
238+
239+
engine = engine.register_agent(agent.clone())?;
215240

216241
let agent = Arc::new(agent);
217242
let engine = Arc::new(engine.build(default_agent.clone())?);

agents/anda_bot/src/twitter.rs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
use agent_twitter_client::{models::Tweet, scraper::Scraper, search::SearchMode};
22
use anda_core::{
3-
Agent, BoxError, CacheFeatures, CompletionFeatures, Path, PutMode, StateFeatures,
4-
StoreFeatures,
3+
Agent, BoxError, CacheFeatures, CompletionFeatures, Path, PutMode, StateFeatures, StoreFeatures,
54
};
65
use anda_engine::{
7-
context::AgentCtx,
8-
engine::Engine,
9-
extension::character::CharacterAgent,
10-
rand_number,
6+
context::AgentCtx, engine::Engine, extension::character::CharacterAgent, rand_number,
117
};
128
use anda_lancedb::knowledge::KnowledgeStore;
139
use ciborium::from_reader;
@@ -48,7 +44,7 @@ impl TwitterDaemon {
4844
}
4945
}
5046

51-
async fn init_seen_tweet_ids<F>(&self, ctx: &F)
47+
async fn init_seen_tweet_ids<F>(&self, ctx: &F) -> usize
5248
where
5349
F: CacheFeatures + StoreFeatures,
5450
{
@@ -58,9 +54,10 @@ impl TwitterDaemon {
5854
.await
5955
.map(|(v, _)| from_reader(&v[..]).unwrap_or_default())
6056
.unwrap_or_default();
61-
57+
let count = seen_tweet_ids.len();
6258
ctx.cache_set("seen_tweet_ids", (seen_tweet_ids, None))
6359
.await;
60+
count
6461
}
6562

6663
async fn get_seen_tweet_ids<F>(&self, ctx: &F) -> Vec<String>
@@ -87,12 +84,13 @@ impl TwitterDaemon {
8784
}
8885

8986
pub async fn run(&self, cancel_token: CancellationToken) -> Result<(), BoxError> {
90-
let ctx = self.engine.ctx_with(self.agent.as_ref(), None, None)?;
91-
92-
// load seen_tweet_ids from store
93-
self.init_seen_tweet_ids(&ctx).await;
87+
{
88+
let ctx = self.engine.ctx_with(self.agent.as_ref(), None, None)?;
89+
// load seen_tweet_ids from store
90+
let count = self.init_seen_tweet_ids(&ctx).await;
9491

95-
log::info!(target: LOG_TARGET, "starting Twitter bot");
92+
log::info!(target: LOG_TARGET, "starting Twitter bot with {} seen tweets", count);
93+
}
9694

9795
loop {
9896
{
@@ -115,13 +113,14 @@ impl TwitterDaemon {
115113
.scraper
116114
.search_tweets(
117115
&format!("@{}", self.agent.character.username.clone()),
118-
5,
116+
20,
119117
SearchMode::Latest,
120118
None,
121119
)
122120
.await
123121
{
124122
Ok(mentions) => {
123+
log::info!(target: LOG_TARGET, "fetch mentions: {} tweets", mentions.tweets.len());
125124
for tweet in mentions.tweets {
126125
if let Err(err) = self.handle_mention(tweet).await {
127126
log::error!(target: LOG_TARGET, "handle mention error: {err:?}");
@@ -140,15 +139,25 @@ impl TwitterDaemon {
140139
}
141140
}
142141

143-
if rand_number(0..=5) == 0 {
144-
if let Err(err) = self.handle_home_timeline().await {
145-
log::error!(target: LOG_TARGET, "handle_home_timeline error: {err:?}");
142+
match rand_number(0..=5) {
143+
0 => {
144+
if let Err(err) = self.handle_home_timeline().await {
145+
log::error!(target: LOG_TARGET, "handle_home_timeline error: {err:?}");
146+
}
147+
}
148+
n => {
149+
log::info!(target: LOG_TARGET, "skip home timeline task by random {n}");
146150
}
147151
}
148152

149-
if rand_number(0..=9) == 0 {
150-
if let Err(err) = self.post_new_tweet().await {
151-
log::error!(target: LOG_TARGET, "post_new_tweet error: {err:?}");
153+
match rand_number(0..=9) {
154+
0 => {
155+
if let Err(err) = self.post_new_tweet().await {
156+
log::error!(target: LOG_TARGET, "post_new_tweet error: {err:?}");
157+
}
158+
}
159+
n => {
160+
log::info!(target: LOG_TARGET, "skip post new tweet task by random {n}");
152161
}
153162
}
154163

@@ -181,7 +190,6 @@ impl TwitterDaemon {
181190
"\
182191
Share a single brief thought or observation in one short sentence.\
183192
Be direct and concise. No questions, hashtags, or emojis.\
184-
Keep responses concise and under 280 characters.\n\
185193
"
186194
.to_string(),
187195
ctx.user(),
@@ -365,7 +373,12 @@ impl TwitterDaemon {
365373
tweet_content: &str,
366374
tweet_id: &str,
367375
) -> Result<bool, BoxError> {
368-
if self.agent.attention.should_like(ctx, tweet_content).await {
376+
if self
377+
.agent
378+
.attention
379+
.should_like(ctx, &self.agent.character.style.interests, tweet_content)
380+
.await
381+
{
369382
let _ = self.scraper.like_tweet(tweet_id).await?;
370383
return Ok(true);
371384
}
@@ -403,8 +416,7 @@ impl TwitterDaemon {
403416
.to_request(
404417
"\
405418
Reply with a single clear, natural sentence.\
406-
If the tweet contains ASCII art or stylized text formatting, respond with similar creative formatting.\n\
407-
Keep responses concise and under 280 characters.\
419+
If the tweet contains ASCII art or stylized text formatting, respond with similar creative formatting.\
408420
"
409421
.to_string(),
410422
ctx.user(),

0 commit comments

Comments
 (0)