跳到主要内容

Creating an AI Agent Integrated with ICP

Anda is an AI agent framework built with Rust, enabling developers to create intelligent agents that interact with blockchains, Web3, and other complex systems.

This tutorial guides you through developing a simple AI agent using the Anda framework in just over 100 lines of code, capable of interacting with the ICP (Internet Computer Protocol) blockchain.

We'll use the icp_ledger_agent example project for demonstration. The complete code for this tutorial is available here: https://github.com/ldclabs/anda/tree/main/examples/icp_ledger_agent

Note: New to Rust?

This guide assumes basic Rust knowledge and a set-up coding environment. If you're just starting or need to set up your environment, check out these quick guides:

1 Running the Example Project

1.1 Cloning the Project

First, clone the anda repository:

git clone https://github.com/ldclabs/anda.git
cd anda

1.2 Project Structure

The icp_ledger_agent project is located in the examples/icp_ledger_agent directory, structured as follows:

icp_ledger_agent/
├── Cargo.toml # Project configuration file, defining dependencies and metadata
├── README.md # Project documentation, including instructions on how to run it
└── src/
├── agent.rs # Defines the `ICPLedgerAgent` struct and its implementation for ICP blockchain interaction
└── main.rs # Entry point of the project, initializes services and starts the AI agent

1.3 Configuring Environment Variables

Before running the project, configure the environment variables. Create a .env file in the anda directory and modify it:

LOG_LEVEL=debug
ID_SECRET=0000000000000000000000000000000000000000000000000000000000000000
ROOT_SECRET=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
XAI_API_KEY=''
OPENAI_API_KEY=''
DEEPSEEK_API_KEY=''
  • ID_SECRET: A 32-byte hex string or an identity PEM file generated by ICP dfx tool, serving as the AI agent's identity.
  • ROOT_SECRET: A 48-byte hex string for deriving cryptographic operations like encryption, decryption, and signing.
  • XAI_API_KEY: API key for xAI models (if using xAI).
  • OPENAI_API_KEY: API key for OpenAI models (if using OpenAI).
  • DEEPSEEK_API_KEY: API key for Deepseek models (currently unavailable).

This project supports xAI's grok-2 or OpenAI's o3-mini models, but not DeepSeek due to unstable Function Calling.

Generate ID_SECRET and ROOT_SECRET using anda_engine_cli:

cargo build -p anda_engine_cli
./target/debug/anda_engine_cli --help
./target/debug/anda_engine_cli rand-bytes --help

Generate 32-byte ID_SECRET:

./target/debug/anda_engine_cli rand-bytes --len 32

Generate 48-byte ROOT_SECRET:

./target/debug/anda_engine_cli rand-bytes --len 48

1.4 Running the AI Agent Service

After configuring the environment variables, run the project with:

cargo run -p icp_ledger_agent

This starts the AI agent service, listening on the default port 8042.

1.5 Calling the Agent

Use anda_engine_cli to call the AI agent service:

./target/debug/anda_engine_cli agent-run --help
./target/debug/anda_engine_cli agent-run -p 'Please check my PANDA balance'

The AI agent service can include multiple Anda engines, each with multiple agents and tools. The above command calls the default agent in the default Anda engine.

Use a different identity to call the AI agent service if you have dfx installed:

./target/debug/anda_engine_cli agent-run -i ~/.config/dfx/identity/default/identity.pem -p 'Please transfer 0.1 PANDA tokens to me'

You can also directly call tools to query balances:

./target/debug/anda_engine_cli tool-call -n icp_ledger_balance_of -a '{"account":"535yc-uxytb-gfk7h-tny7p-vjkoe-i4krp-3qmcl-uqfgr-cpgej-yqtjq-rqe","symbol":"PANDA"}'

Code Analysis

2.1 Agent Implementation agent.rs

The agent.rs file defines the ICPLedgerAgent struct, implementing the Agent trait. This agent interacts with the ICP blockchain, including querying balances and transferring tokens.

The ICPLedgerAgent struct is defined as:

pub struct ICPLedgerAgent {
ledgers: Arc<ICPLedgers>, // ICP ledgers struct
tools: Vec<&'static str>, // List of tools ICPLedgerAgent depends on
}

ICPLedgerAgent uses the ICPLedgers struct from the anda_icp library, which supports querying balances and transferring tokens for multiple tokens.

The tools method returns tools to register with the Anda engine:

pub fn tools(&self) -> Result<ToolSet<BaseCtx>, BoxError> {
let mut tools = ToolSet::new();
tools.add(BalanceOfTool::new(self.ledgers.clone()))?; // Balance query tool
tools.add(TransferTool::new(self.ledgers.clone()))?; // Transfer tool
Ok(tools)
}

The Agent trait implementation for ICPLedgerAgent:

impl Agent<AgentCtx> for ICPLedgerAgent {
/// Returns the agent's name identifier
fn name(&self) -> String {
Self::NAME.to_string()
}

/// Returns a description of the agent's purpose and capabilities.
fn description(&self) -> String {
"Interacts with ICP blockchain ledgers".to_string()
}

/// Returns a list of tool names that this agent depends on
fn tool_dependencies(&self) -> Vec<String> {
self.tools.iter().map(|v| v.to_string()).collect()
}

/// Main execution method for the agent.
async fn run(
&self,
ctx: AgentCtx,
prompt: String,
_attachment: Option<Vec<u8>>,
) -> Result<AgentOutput, BoxError> {
let caller = ctx.caller();
if caller == ANONYMOUS {
return Err("anonymous caller not allowed".into());
}

let req = CompletionRequest {
system: Some(
"\
You are an AI assistant designed to interact with the ICP blockchain ledger by given tools.\n\
1. Please decline any requests that are not related to the ICP blockchain ledger.\n\
2. For requests that are not supported by the tools available, kindly inform the user \
of your current capabilities."
.to_string(),
),
prompt,
tools: ctx.tool_definitions(Some(&self.tools)),
tool_choice_required: false,
..Default::default()
}
.context("user_address".to_string(), caller.to_string());
let res = ctx.completion(req).await?;
Ok(res)
}
}

Key aspects of the code:

  • ctx.caller() retrieves the caller's identity ID, provided by the Anda engine runtime. If it's not ANONYMOUS, it indicates the caller's request has been signature-verified. The Anda framework uses ICP's identity protocol for AI Agent identification.
  • The system field in CompletionRequest is an optional system prompt that defines the AI Agent's functional purpose.
  • The tools field in CompletionRequest lists the tools available to the AI Agent. The AI model selects the appropriate tool based on user prompts.
  • context("user_address".to_string(), caller.to_string()) injects the requester's identity ID and can also include other context information, such as knowledge documents.
  • ctx.completion(req).await executes the AI model's inferring, returning the AgentOutput struct.

For more on CompletionRequest, see: https://docs.rs/anda_core/latest/anda_core/model/struct.CompletionRequest.html

Besides ctx.caller and ctx.completion, AgentCtx offers many other useful methods for Agent development. Check the anda_engine library docs for more: https://docs.rs/anda_engine/latest/anda_engine/context/struct.AgentCtx.html

2.2 Starting the Service main.rs

main.rs is the project's entry file, initializing the AI Agent. It configures Web3 client, AI model, object storage, and starts the HTTP service to accept external requests.

Key initialization variables:

let global_cancel_token = CancellationToken::new();
let identity = load_identity(&cli.id_secret)?;
let root_secret = const_hex::decode(&cli.root_secret)?;
let root_secret: [u8; 48] = root_secret
.try_into()
.map_err(|_| format!("invalid root_secret: {:?}", cli.root_secret))?;

Key aspects:

Initializing Web3Client:

let web3 = Web3Client::builder()
.with_ic_host(&cli.ic_host)
.with_identity(Arc::new(identity))
.with_root_secret(root_secret)
.build()
.await?;
let my_principal = web3.get_principal();
log::info!(
"start local service, principal: {:?}",
my_principal.to_text()
);

Web3Client is a functional module of anda_web3_client, providing encryption operations, external communication, and ICP smart contract interactions for BaseCtx and AgentCtx. Due to dependencies on unpublished libraries ic-crypto-secp256k1 and ic-crypto-ed25519, it cannot be published to https://crates.io/.

For more on anda_web3_client, see:

In TEE environments, the ic_tee_gateway_sdk library is used. More info: https://docs.rs/ic_tee_gateway_sdk/latest/ic_tee_gateway_sdk/client/index.html

Building the engine:

let engine = EngineBuilder::new()
.with_id(my_principal)
.with_name(APP_NAME.to_string())
.with_cancellation_token(global_cancel_token.clone())
.with_web3_client(Arc::new(Web3SDK::from_web3(Arc::new(web3.clone()))))
.with_model(model)
.with_store(Store::new(object_store))
.register_tools(agent.tools()?)?
.register_agent(agent)?;

let engine = engine.build(ICPLedgerAgent::NAME.to_string())?;

Key aspects:

  • with_id(my_principal) sets the engine's identity ID, i.e., the AI Agent's ID. The engine, as the AI Agent's main body, can include multiple agents and tools.
  • register_tools(agent.tools()?)? registers available tool sets.
  • register_agent(agent)? registers an Agent, or use register_agents for multiple agents.
  • build(ICPLedgerAgent::NAME.to_string())? builds the engine, setting the default agent with ICPLedgerAgent::NAME.

For more on EngineBuilder, see: https://docs.rs/anda_engine/latest/anda_engine/engine/index.html

Starting the Anda engine's HTTP runtime service:

let mut engines = BTreeMap::new();
engines.insert(engine.id(), engine);

ServerBuilder::new()
.with_app_name(APP_NAME.to_string())
.with_app_version(APP_VERSION.to_string())
.with_addr(format!("127.0.0.1:{}", cli.port))
.with_engines(engines, None)
.serve(shutdown_signal(global_cancel_token, Duration::from_secs(3)))
.await?;

Key aspects:

  • with_engines(engines, None): A single HTTP runtime service can host multiple engines.
  • serve(shutdown_signal(global_cancel_token, Duration::from_secs(3))) implements a graceful shutdown mechanism.

ServerBuilder, provided by the anda_engine_server library, implements an HTTP runtime service offering HTTP service interfaces for the Anda engine, including authentication mechanisms for receiving external requests.

Like anda_web3_client, anda_engine_server cannot be published to https://crates.io/.

For more on ServerBuilder, see: https://github.com/ldclabs/anda/tree/main/anda_engine_server

Conclusion

This tutorial has taught you how to develop a simple AI Agent using the Anda framework and interact with the ICP blockchain. You can expand functionalities, such as supporting more blockchain operations or integrating other AI models.

For questions or further assistance, visit https://github.com/ldclabs/anda/discussions.