Skip to content

Commit

Permalink
feat: 1. improve attention; 2. add tools to anda_bot
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Jan 24, 2025
1 parent 67d9daf commit c7fd427
Show file tree
Hide file tree
Showing 26 changed files with 228 additions and 90 deletions.
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions agents/anda_bot/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ license.workspace = true
anda_core = { path = "../../anda_core", version = "0.3" }
anda_engine = { path = "../../anda_engine", version = "0.3" }
anda_lancedb = { path = "../../anda_lancedb", version = "0.1" }
anda_icp = { path = "../../tools/anda_icp", version = "0.2" }
axum = { workspace = true }
axum-server = { workspace = true }
candid = { workspace = true }
Expand Down
6 changes: 4 additions & 2 deletions agents/anda_bot/Character.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ name = "Anda ICP"
username = "AndaICP"

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

# Character's backstory and historical background
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. 🐼✨"
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. 🐼✨"

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

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

# List of meme phrases or internet slang the character uses
Expand Down
5 changes: 5 additions & 0 deletions agents/anda_bot/Config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ api_host = "http://localhost:4943"
cose_namespace = "_"
cose_canister = "53cyg-yyaaa-aaaap-ahpua-cai"
object_store_canister = "6at64-oyaaa-aaaap-anvza-cai"
token_ledgers = []

[llm]
deepseek_api_key = ""
Expand All @@ -33,3 +34,7 @@ password = ""
email = ""
two_factor_auth = ""
cookie_string = ""

[google]
api_key = ""
search_engine_id = ""
6 changes: 4 additions & 2 deletions agents/anda_bot/nitro_enclave/Character.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ name = "Anda ICP"
username = "AndaICP"

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

# Character's backstory and historical background
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. 🐼✨"
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. 🐼✨"

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

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

# List of meme phrases or internet slang the character uses
Expand Down
5 changes: 5 additions & 0 deletions agents/anda_bot/nitro_enclave/Config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ api_host = "https://icp-api.io"
cose_namespace = "anda"
cose_canister = "53cyg-yyaaa-aaaap-ahpua-cai"
object_store_canister = "6at64-oyaaa-aaaap-anvza-cai"
token_ledgers = []

[llm]
deepseek_api_key = ""
Expand All @@ -22,3 +23,7 @@ password = ""
email = ""
two_factor_auth = ""
cookie_string = ""

[google]
api_key = ""
search_engine_id = ""
2 changes: 1 addition & 1 deletion agents/anda_bot/nitro_enclave/amd64.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ RUN mv linux-amd64/dnsproxy ./ && chmod +x dnsproxy
RUN wget -O ic_tee_nitro_gateway https://github.com/ldclabs/ic-tee/releases/download/v0.2.11/ic_tee_nitro_gateway
RUN chmod +x ic_tee_nitro_gateway

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

FROM --platform=linux/amd64 debian:bookworm-slim AS runtime
Expand Down
9 changes: 9 additions & 0 deletions agents/anda_bot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub struct Icp {
pub cose_namespace: String,
pub cose_canister: String,
pub object_store_canister: String,
pub token_ledgers: Vec<String>,
}

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

/// Configuration for the Google search should be encrypted and stored in the ICP COSE canister.
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Google {
pub api_key: String,
pub search_engine_id: String,
}

#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Conf {
pub llm: Llm,
pub tee: Tee,
pub icp: Icp,
pub x: X,
pub google: Google,
}

impl Conf {
Expand Down
31 changes: 28 additions & 3 deletions agents/anda_bot/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ use anda_engine::{
extension::{
attention::Attention,
character::{Character, CharacterAgent},
google::GoogleSearchTool,
segmenter::DocumentSegmenter,
},
model::{cohere, deepseek, openai, Model},
store::Store,
};
use anda_icp::ledger::{BalanceOfTool, ICPLedgers};
use anda_lancedb::{knowledge::KnowledgeStore, lancedb::LanceVectorStore};
use axum::{routing, Router};
use candid::Principal;
Expand All @@ -32,6 +34,7 @@ use ic_object_store::{
};
use ic_tee_agent::setting::decrypt_payload;
use ic_tee_cdk::TEEAppInformation;
use std::collections::BTreeSet;
use std::{net::SocketAddr, sync::Arc, time::Duration};
use structured_logger::{async_json::new_writer, get_env_level, unix_ms, Builder};
use tokio::{net::TcpStream, signal, sync::RwLock, time::sleep};
Expand Down Expand Up @@ -205,13 +208,35 @@ async fn bootstrap(cli: Cli) -> Result<(), BoxError> {
knowledge_store,
);

let engine = EngineBuilder::new()
let mut engine = EngineBuilder::new()
.with_id(tee_info.id)
.with_name(engine_name.clone())
.with_cancellation_token(global_cancel_token.clone())
.with_tee_client(tee.clone())
.with_model(model)
.with_store(Store::new(object_store))
.register_agent(agent.clone())?;
.with_store(Store::new(object_store));

if !encrypted_cfg.google.api_key.is_empty() {
engine = engine.register_tool(GoogleSearchTool::new(
encrypted_cfg.google.api_key.clone(),
encrypted_cfg.google.search_engine_id.clone(),
None,
))?;
}
if !cfg.icp.token_ledgers.is_empty() {
let token_ledgers: BTreeSet<Principal> = cfg
.icp
.token_ledgers
.iter()
.flat_map(|t| Principal::from_text(t).map_err(|_| format!("invalid token: {}", t)))
.collect();

let ledgers = ICPLedgers::load(&tee, token_ledgers, false).await?;
let ledgers = Arc::new(ledgers);
engine = engine.register_tool(BalanceOfTool::new(ledgers.clone()))?;
}

engine = engine.register_agent(agent.clone())?;

let agent = Arc::new(agent);
let engine = Arc::new(engine.build(default_agent.clone())?);
Expand Down
60 changes: 36 additions & 24 deletions agents/anda_bot/src/twitter.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
use agent_twitter_client::{models::Tweet, scraper::Scraper, search::SearchMode};
use anda_core::{
Agent, BoxError, CacheFeatures, CompletionFeatures, Path, PutMode, StateFeatures,
StoreFeatures,
Agent, BoxError, CacheFeatures, CompletionFeatures, Path, PutMode, StateFeatures, StoreFeatures,
};
use anda_engine::{
context::AgentCtx,
engine::Engine,
extension::character::CharacterAgent,
rand_number,
context::AgentCtx, engine::Engine, extension::character::CharacterAgent, rand_number,
};
use anda_lancedb::knowledge::KnowledgeStore;
use ciborium::from_reader;
Expand Down Expand Up @@ -48,7 +44,7 @@ impl TwitterDaemon {
}
}

async fn init_seen_tweet_ids<F>(&self, ctx: &F)
async fn init_seen_tweet_ids<F>(&self, ctx: &F) -> usize
where
F: CacheFeatures + StoreFeatures,
{
Expand All @@ -58,9 +54,10 @@ impl TwitterDaemon {
.await
.map(|(v, _)| from_reader(&v[..]).unwrap_or_default())
.unwrap_or_default();

let count = seen_tweet_ids.len();
ctx.cache_set("seen_tweet_ids", (seen_tweet_ids, None))
.await;
count
}

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

pub async fn run(&self, cancel_token: CancellationToken) -> Result<(), BoxError> {
let ctx = self.engine.ctx_with(self.agent.as_ref(), None, None)?;

// load seen_tweet_ids from store
self.init_seen_tweet_ids(&ctx).await;
{
let ctx = self.engine.ctx_with(self.agent.as_ref(), None, None)?;
// load seen_tweet_ids from store
let count = self.init_seen_tweet_ids(&ctx).await;

log::info!(target: LOG_TARGET, "starting Twitter bot");
log::info!(target: LOG_TARGET, "starting Twitter bot with {} seen tweets", count);
}

loop {
{
Expand All @@ -115,13 +113,14 @@ impl TwitterDaemon {
.scraper
.search_tweets(
&format!("@{}", self.agent.character.username.clone()),
5,
20,
SearchMode::Latest,
None,
)
.await
{
Ok(mentions) => {
log::info!(target: LOG_TARGET, "fetch mentions: {} tweets", mentions.tweets.len());
for tweet in mentions.tweets {
if let Err(err) = self.handle_mention(tweet).await {
log::error!(target: LOG_TARGET, "handle mention error: {err:?}");
Expand All @@ -140,15 +139,25 @@ impl TwitterDaemon {
}
}

if rand_number(0..=5) == 0 {
if let Err(err) = self.handle_home_timeline().await {
log::error!(target: LOG_TARGET, "handle_home_timeline error: {err:?}");
match rand_number(0..=5) {
0 => {
if let Err(err) = self.handle_home_timeline().await {
log::error!(target: LOG_TARGET, "handle_home_timeline error: {err:?}");
}
}
n => {
log::info!(target: LOG_TARGET, "skip home timeline task by random {n}");
}
}

if rand_number(0..=9) == 0 {
if let Err(err) = self.post_new_tweet().await {
log::error!(target: LOG_TARGET, "post_new_tweet error: {err:?}");
match rand_number(0..=9) {
0 => {
if let Err(err) = self.post_new_tweet().await {
log::error!(target: LOG_TARGET, "post_new_tweet error: {err:?}");
}
}
n => {
log::info!(target: LOG_TARGET, "skip post new tweet task by random {n}");
}
}

Expand Down Expand Up @@ -181,7 +190,6 @@ impl TwitterDaemon {
"\
Share a single brief thought or observation in one short sentence.\
Be direct and concise. No questions, hashtags, or emojis.\
Keep responses concise and under 280 characters.\n\
"
.to_string(),
ctx.user(),
Expand Down Expand Up @@ -365,7 +373,12 @@ impl TwitterDaemon {
tweet_content: &str,
tweet_id: &str,
) -> Result<bool, BoxError> {
if self.agent.attention.should_like(ctx, tweet_content).await {
if self
.agent
.attention
.should_like(ctx, &self.agent.character.style.interests, tweet_content)
.await
{
let _ = self.scraper.like_tweet(tweet_id).await?;
return Ok(true);
}
Expand Down Expand Up @@ -403,8 +416,7 @@ impl TwitterDaemon {
.to_request(
"\
Reply with a single clear, natural sentence.\
If the tweet contains ASCII art or stylized text formatting, respond with similar creative formatting.\n\
Keep responses concise and under 280 characters.\
If the tweet contains ASCII art or stylized text formatting, respond with similar creative formatting.\
"
.to_string(),
ctx.user(),
Expand Down
2 changes: 1 addition & 1 deletion anda_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "anda_core"
description = "Core types and traits for Anda -- a framework for AI agent development."
repository = "https://github.com/ldclabs/anda/tree/main/anda_core"
publish = true
version = "0.3.3"
version = "0.3.4"
edition.workspace = true
keywords.workspace = true
categories.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions anda_core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ pub trait BaseContext:

/// StateFeatures is one of the context feature sets available when calling Agent or Tool.
pub trait StateFeatures: Sized {
/// Gets the engine ID, which comes from the TEE host
fn id(&self) -> Principal;
/// Gets the username from request context.
/// Note: This is not verified and should not be used as a trusted identifier.
/// For example, if triggered by a bot of X platform, this might be the username
Expand Down
Loading

0 comments on commit c7fd427

Please sign in to comment.