|
| 1 | +--- |
| 2 | +sidebar_position: 1 |
| 3 | +--- |
| 4 | + |
| 5 | +# Creating an AI Agent Integrated with ICP |
| 6 | + |
| 7 | +Anda is an AI agent framework built with Rust, enabling developers to create intelligent agents that interact with blockchains, Web3, and other complex systems. |
| 8 | + |
| 9 | +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. |
| 10 | + |
| 11 | +We'll use the `icp_ledger_agent` example project for demonstration. |
| 12 | +The complete code for this tutorial is available here: https://github.com/ldclabs/anda/tree/main/examples/icp_ledger_agent |
| 13 | + |
| 14 | +> Note: New to Rust? |
| 15 | +> |
| 16 | +> 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: |
| 17 | +> |
| 18 | +> - Getting Started with Rust: https://www.rust-lang.org/learn |
| 19 | +> - Setting Up Rust with VS Code: https://users.rust-lang.org/t/setting-up-rust-with-vs-code/76907 |
| 20 | +> These resources will help you get up to speed quickly! |
| 21 | +
|
| 22 | +## 1 Running the Example Project |
| 23 | + |
| 24 | +### 1.1 Cloning the Project |
| 25 | + |
| 26 | +First, clone the `anda` repository: |
| 27 | + |
| 28 | +```bash |
| 29 | +git clone https://github.com/ldclabs/anda.git |
| 30 | +cd anda |
| 31 | +``` |
| 32 | + |
| 33 | +### 1.2 Project Structure |
| 34 | + |
| 35 | +The `icp_ledger_agent` project is located in the `examples/icp_ledger_agent` directory, structured as follows: |
| 36 | + |
| 37 | +```sh |
| 38 | +icp_ledger_agent/ |
| 39 | +├── Cargo.toml # Project configuration file, defining dependencies and metadata |
| 40 | +├── README.md # Project documentation, including instructions on how to run it |
| 41 | +└── src/ |
| 42 | + ├── agent.rs # Defines the `ICPLedgerAgent` struct and its implementation for ICP blockchain interaction |
| 43 | + └── main.rs # Entry point of the project, initializes services and starts the AI agent |
| 44 | +``` |
| 45 | + |
| 46 | +### 1.3 Configuring Environment Variables |
| 47 | + |
| 48 | +Before running the project, configure the environment variables. Create a `.env` file in the `anda` directory and modify it: |
| 49 | + |
| 50 | +```bash |
| 51 | +LOG_LEVEL=debug |
| 52 | +ID_SECRET=0000000000000000000000000000000000000000000000000000000000000000 |
| 53 | +ROOT_SECRET=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
| 54 | +XAI_API_KEY='' |
| 55 | +OPENAI_API_KEY='' |
| 56 | +DEEPSEEK_API_KEY='' |
| 57 | +``` |
| 58 | + |
| 59 | +- `ID_SECRET`: A 32-byte hex string or an identity PEM file generated by ICP `dfx` tool, serving as the AI agent's identity. |
| 60 | +- `ROOT_SECRET`: A 48-byte hex string for deriving cryptographic operations like encryption, decryption, and signing. |
| 61 | +- `XAI_API_KEY`: API key for xAI models (if using xAI). |
| 62 | +- `OPENAI_API_KEY`: API key for OpenAI models (if using OpenAI). |
| 63 | +- `DEEPSEEK_API_KEY`: API key for Deepseek models (currently unavailable). |
| 64 | + |
| 65 | +This project supports xAI's grok-2 or OpenAI's o3-mini models, but not DeepSeek due to unstable Function Calling. |
| 66 | + |
| 67 | +Generate `ID_SECRET` and `ROOT_SECRET` using `anda_engine_cli`: |
| 68 | + |
| 69 | +```bash |
| 70 | +cargo build -p anda_engine_cli |
| 71 | +./target/debug/anda_engine_cli --help |
| 72 | +./target/debug/anda_engine_cli rand-bytes --help |
| 73 | +``` |
| 74 | + |
| 75 | +Generate 32-byte `ID_SECRET`: |
| 76 | +```bash |
| 77 | +./target/debug/anda_engine_cli rand-bytes --len 32 |
| 78 | +``` |
| 79 | + |
| 80 | +Generate 48-byte `ROOT_SECRET`: |
| 81 | +```bash |
| 82 | +./target/debug/anda_engine_cli rand-bytes --len 48 |
| 83 | +``` |
| 84 | + |
| 85 | +### 1.4 Running the AI Agent Service |
| 86 | + |
| 87 | +After configuring the environment variables, run the project with: |
| 88 | + |
| 89 | +```bash |
| 90 | +cargo run -p icp_ledger_agent |
| 91 | +``` |
| 92 | + |
| 93 | +This starts the AI agent service, listening on the default port `8042`. |
| 94 | + |
| 95 | +### 1.5 Calling the Agent |
| 96 | + |
| 97 | +Use `anda_engine_cli` to call the AI agent service: |
| 98 | + |
| 99 | +```bash |
| 100 | +./target/debug/anda_engine_cli agent-run --help |
| 101 | +./target/debug/anda_engine_cli agent-run -p 'Please check my PANDA balance' |
| 102 | +``` |
| 103 | + |
| 104 | +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. |
| 105 | + |
| 106 | +Use a different identity to call the AI agent service if you have `dfx` installed: |
| 107 | + |
| 108 | +```bash |
| 109 | +./target/debug/anda_engine_cli agent-run -i ~/.config/dfx/identity/default/identity.pem -p 'Please transfer 0.1 PANDA tokens to me' |
| 110 | +``` |
| 111 | + |
| 112 | +You can also directly call tools to query balances: |
| 113 | + |
| 114 | +```bash |
| 115 | +./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"}' |
| 116 | +``` |
| 117 | + |
| 118 | +## Code Analysis |
| 119 | + |
| 120 | +### 2.1 Agent Implementation `agent.rs` |
| 121 | + |
| 122 | +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. |
| 123 | + |
| 124 | +The `ICPLedgerAgent` struct is defined as: |
| 125 | +```rust |
| 126 | +pub struct ICPLedgerAgent { |
| 127 | + ledgers: Arc<ICPLedgers>, // ICP ledgers struct |
| 128 | + tools: Vec<&'static str>, // List of tools ICPLedgerAgent depends on |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +`ICPLedgerAgent` uses the `ICPLedgers` struct from the `anda_icp` library, which supports querying balances and transferring tokens for multiple tokens. |
| 133 | + |
| 134 | +The `tools` method returns tools to register with the Anda engine: |
| 135 | +```rust |
| 136 | +pub fn tools(&self) -> Result<ToolSet<BaseCtx>, BoxError> { |
| 137 | + let mut tools = ToolSet::new(); |
| 138 | + tools.add(BalanceOfTool::new(self.ledgers.clone()))?; // Balance query tool |
| 139 | + tools.add(TransferTool::new(self.ledgers.clone()))?; // Transfer tool |
| 140 | + Ok(tools) |
| 141 | +} |
| 142 | +``` |
| 143 | + |
| 144 | +The `Agent` trait implementation for `ICPLedgerAgent`: |
| 145 | +```rust |
| 146 | +impl Agent<AgentCtx> for ICPLedgerAgent { |
| 147 | + /// Returns the agent's name identifier |
| 148 | + fn name(&self) -> String { |
| 149 | + Self::NAME.to_string() |
| 150 | + } |
| 151 | + |
| 152 | + /// Returns a description of the agent's purpose and capabilities. |
| 153 | + fn description(&self) -> String { |
| 154 | + "Interacts with ICP blockchain ledgers".to_string() |
| 155 | + } |
| 156 | + |
| 157 | + /// Returns a list of tool names that this agent depends on |
| 158 | + fn tool_dependencies(&self) -> Vec<String> { |
| 159 | + self.tools.iter().map(|v| v.to_string()).collect() |
| 160 | + } |
| 161 | + |
| 162 | + /// Main execution method for the agent. |
| 163 | + async fn run( |
| 164 | + &self, |
| 165 | + ctx: AgentCtx, |
| 166 | + prompt: String, |
| 167 | + _attachment: Option<Vec<u8>>, |
| 168 | + ) -> Result<AgentOutput, BoxError> { |
| 169 | + let caller = ctx.caller(); |
| 170 | + if caller == ANONYMOUS { |
| 171 | + return Err("anonymous caller not allowed".into()); |
| 172 | + } |
| 173 | + |
| 174 | + let req = CompletionRequest { |
| 175 | + system: Some( |
| 176 | + "\ |
| 177 | + You are an AI assistant designed to interact with the ICP blockchain ledger by given tools.\n\ |
| 178 | + 1. Please decline any requests that are not related to the ICP blockchain ledger.\n\ |
| 179 | + 2. For requests that are not supported by the tools available, kindly inform the user \ |
| 180 | + of your current capabilities." |
| 181 | + .to_string(), |
| 182 | + ), |
| 183 | + prompt, |
| 184 | + tools: ctx.tool_definitions(Some(&self.tools)), |
| 185 | + tool_choice_required: false, |
| 186 | + ..Default::default() |
| 187 | + } |
| 188 | + .context("user_address".to_string(), caller.to_string()); |
| 189 | + let res = ctx.completion(req).await?; |
| 190 | + Ok(res) |
| 191 | + } |
| 192 | +} |
| 193 | +``` |
| 194 | + |
| 195 | +Key aspects of the code: |
| 196 | +- `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. |
| 197 | +- The `system` field in `CompletionRequest` is an optional system prompt that defines the AI Agent's functional purpose. |
| 198 | +- The `tools` field in `CompletionRequest` lists the tools available to the AI Agent. The AI model selects the appropriate tool based on user prompts. |
| 199 | +- `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. |
| 200 | +- `ctx.completion(req).await` executes the AI model's inferring, returning the `AgentOutput` struct. |
| 201 | + |
| 202 | +For more on `CompletionRequest`, see: https://docs.rs/anda_core/latest/anda_core/model/struct.CompletionRequest.html |
| 203 | + |
| 204 | +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 |
| 205 | + |
| 206 | +### 2.2 Starting the Service `main.rs` |
| 207 | + |
| 208 | +`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. |
| 209 | + |
| 210 | +Key initialization variables: |
| 211 | +```rust |
| 212 | +let global_cancel_token = CancellationToken::new(); |
| 213 | +let identity = load_identity(&cli.id_secret)?; |
| 214 | +let root_secret = const_hex::decode(&cli.root_secret)?; |
| 215 | +let root_secret: [u8; 48] = root_secret |
| 216 | + .try_into() |
| 217 | + .map_err(|_| format!("invalid root_secret: {:?}", cli.root_secret))?; |
| 218 | +``` |
| 219 | + |
| 220 | +Key aspects: |
| 221 | +- `global_cancel_token` is a global async cancellation token for canceling all async tasks upon service exit. |
| 222 | +- `load_identity` loads the AI Agent's identity defined by `ID_SECRET`. |
| 223 | +- `root_secret` is used for deriving encryption operations in `KeysFeatures`. More info: https://docs.rs/anda_core/latest/anda_core/context/trait.KeysFeatures.html. |
| 224 | + |
| 225 | +Initializing `Web3Client`: |
| 226 | +```rust |
| 227 | +let web3 = Web3Client::builder() |
| 228 | + .with_ic_host(&cli.ic_host) |
| 229 | + .with_identity(Arc::new(identity)) |
| 230 | + .with_root_secret(root_secret) |
| 231 | + .build() |
| 232 | + .await?; |
| 233 | +let my_principal = web3.get_principal(); |
| 234 | +log::info!( |
| 235 | + "start local service, principal: {:?}", |
| 236 | + my_principal.to_text() |
| 237 | +); |
| 238 | +``` |
| 239 | + |
| 240 | +`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/. |
| 241 | + |
| 242 | +For more on `anda_web3_client`, see: |
| 243 | +- https://docs.rs/anda_engine/latest/anda_engine/context/struct.Web3Client.html |
| 244 | +- https://github.com/ldclabs/anda/tree/main/anda_web3_client |
| 245 | + |
| 246 | +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 |
| 247 | + |
| 248 | +Building the engine: |
| 249 | +```rust |
| 250 | +let engine = EngineBuilder::new() |
| 251 | + .with_id(my_principal) |
| 252 | + .with_name(APP_NAME.to_string()) |
| 253 | + .with_cancellation_token(global_cancel_token.clone()) |
| 254 | + .with_web3_client(Arc::new(Web3SDK::from_web3(Arc::new(web3.clone())))) |
| 255 | + .with_model(model) |
| 256 | + .with_store(Store::new(object_store)) |
| 257 | + .register_tools(agent.tools()?)? |
| 258 | + .register_agent(agent)?; |
| 259 | + |
| 260 | +let engine = engine.build(ICPLedgerAgent::NAME.to_string())?; |
| 261 | +``` |
| 262 | + |
| 263 | +Key aspects: |
| 264 | +- `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. |
| 265 | +- `register_tools(agent.tools()?)?` registers available tool sets. |
| 266 | +- `register_agent(agent)?` registers an Agent, or use `register_agents` for multiple agents. |
| 267 | +- `build(ICPLedgerAgent::NAME.to_string())?` builds the engine, setting the default agent with `ICPLedgerAgent::NAME`. |
| 268 | + |
| 269 | +For more on `EngineBuilder`, see: https://docs.rs/anda_engine/latest/anda_engine/engine/index.html |
| 270 | + |
| 271 | +Starting the Anda engine's HTTP runtime service: |
| 272 | +```rust |
| 273 | +let mut engines = BTreeMap::new(); |
| 274 | +engines.insert(engine.id(), engine); |
| 275 | + |
| 276 | +ServerBuilder::new() |
| 277 | + .with_app_name(APP_NAME.to_string()) |
| 278 | + .with_app_version(APP_VERSION.to_string()) |
| 279 | + .with_addr(format!("127.0.0.1:{}", cli.port)) |
| 280 | + .with_engines(engines, None) |
| 281 | + .serve(shutdown_signal(global_cancel_token, Duration::from_secs(3))) |
| 282 | + .await?; |
| 283 | +``` |
| 284 | + |
| 285 | +Key aspects: |
| 286 | +- `with_engines(engines, None)`: A single HTTP runtime service can host multiple engines. |
| 287 | +- `serve(shutdown_signal(global_cancel_token, Duration::from_secs(3)))` implements a graceful shutdown mechanism. |
| 288 | + |
| 289 | +`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. |
| 290 | + |
| 291 | +Like `anda_web3_client`, `anda_engine_server` cannot be published to https://crates.io/. |
| 292 | + |
| 293 | +For more on `ServerBuilder`, see: https://github.com/ldclabs/anda/tree/main/anda_engine_server |
| 294 | + |
| 295 | +## Conclusion |
| 296 | + |
| 297 | +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. |
| 298 | + |
| 299 | +For questions or further assistance, visit https://github.com/ldclabs/anda/discussions. |
0 commit comments