Today I finished the implementation of pi-rust. Now I want to share how to build an AI agent.
From my perspective, the core of an agent is the agent loop. The flow looks like this:
Read steering messages
↓
Call the model
↓
Get assistant message
↓
Check for tool calls
├─ No: check whether to stop
└─ Yes: execute tools
↓
Write tool result
↓
Call the model again
A simplified version of the code looks like this:
while has_more_tool_calls || !pending_messages.is_empty() {
// 1. Inject steering/follow-up messages
// 2. Call the model and get the assistant response
let message = stream_assistant_response(...).await?;
// 3. Check stop_reason
if message.stop_reason == StopReason::Error {
...
}
// 4. Extract tool calls from the assistant message
let tool_calls = message.content.iter()
.filter_map(|content| match content {
Content::ToolCall(tool_call) => Some(tool_call.clone()),
_ => None,
})
.collect();
// 5. Execute tools
let batch = execute_tool_calls(...).await?;
// 6. Add ToolResultMessage back to the context
current_context.messages.push(...);
// 7. Decide whether to continue to the next round
}
Another question arises: how do we parse messages from different providers, such as Anthropic, OpenAI, and other completion protocols? The response is streamed, so we need to process it in a special way.
while let Some(event) = response.next().await {
match &event {
AssistantMessageEvent::Start { partial } => { ... }
AssistantMessageEvent::TextDelta { partial, .. } => { ... }
AssistantMessageEvent::ThinkingDelta { partial, .. } => { ... }
AssistantMessageEvent::ToolCallDelta { partial, .. } => { ... }
AssistantMessageEvent::Done { .. }
| AssistantMessageEvent::Error { .. } => { ... }
}
}
The streamed message sequence can look like this:
Start
↓
TextStart
↓
TextDelta
↓
TextDelta
↓
ToolCallStart
↓
ToolCallDelta
↓
Done
That's the core idea behind pi-rust: keep the agent loop simple, and normalize provider-specific streaming events into one consistent event model.
If you're building something similar, I'd love to hear how you handle provider differences and streaming tool calls.
Top comments (0)
Some comments have been hidden by the post's author - find out more