use async_trait::async_trait;
use oharness_budget::{BudgetMiddleware, TokenBudget};
use oharness_core::{
BudgetHandle, CompletionRequest, CompletionResponse, Content, LlmCapabilities, ModelId,
StopReason, Task, Termination, Usage,
};
use oharness_llm::{ChunkStream, Llm, LlmError};
use oharness_loop::{Agent, ReactLoop};
use oharness_tools::fs::FsToolSet;
use oharness_trace::InMemorySink;
use std::sync::Arc;
struct ChattyLlm;
#[async_trait]
impl Llm for ChattyLlm {
fn name(&self) -> &str {
"chatty"
}
fn capabilities(&self) -> LlmCapabilities {
LlmCapabilities::default()
}
async fn complete(&self, _req: CompletionRequest) -> Result<CompletionResponse, LlmError> {
Ok(CompletionResponse {
id: "msg_1".into(),
model: ModelId::new("chatty-model"),
content: vec![Content::text("Sure, here is a very long response…")],
stop_reason: StopReason::EndTurn,
usage: Usage {
tokens_input: 100,
tokens_output: 200,
..Default::default()
},
})
}
async fn stream(&self, _req: CompletionRequest) -> Result<ChunkStream, LlmError> {
Err(LlmError::Unsupported("stream"))
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let budget: Arc<dyn BudgetHandle> = Arc::new(TokenBudget::input_plus_output(50));
let bounded_llm = Arc::new(BudgetMiddleware::new(ChattyLlm, budget.clone()));
let sink = Arc::new(InMemorySink::new());
let agent = Agent::builder()
.with_llm(bounded_llm)
.with_tools(Arc::new(FsToolSet::new()))
.with_event_sink(sink.clone())
.with_loop(Box::new(ReactLoop::new()))
.with_max_turns(5)
.build()?;
let outcome = agent.run(Task::new("hello")).await?;
match &outcome.termination {
Termination::Failed { error, .. } => {
println!(
"Termination: Failed (category={:?})\n message: {}",
error.category, error.message
);
}
other => println!("Termination: {other:?}"),
}
let snapshot = budget.snapshot();
let remaining_in = snapshot
.remaining
.as_ref()
.map(|r| r.tokens_input.to_string())
.unwrap_or_else(|| "unbounded".into());
let remaining_out = snapshot
.remaining
.as_ref()
.map(|r| r.tokens_output.to_string())
.unwrap_or_else(|| "unbounded".into());
println!(
"Budget snapshot: consumed {}/{} input tokens, {}/{} output tokens (remaining)",
snapshot.consumed.tokens_input,
remaining_in,
snapshot.consumed.tokens_output,
remaining_out,
);
println!("Trajectory events captured: {}", sink.events().len());
Ok(())
}