use async_trait::async_trait;
use oharness_core::event::EventKind;
use oharness_core::{
CompletionRequest, CompletionResponse, Content, LlmCapabilities, ModelId, StopReason, Task,
Termination, Usage,
};
use oharness_critic::{
AggregationPolicy, AssessmentContext, CompositeCritic, Critic, CriticVerdict,
};
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 NoHedgingCritic;
#[async_trait]
impl Critic for NoHedgingCritic {
fn name(&self) -> &str {
"no-hedging"
}
async fn assess(&self, ctx: &AssessmentContext<'_>) -> CriticVerdict {
let oharness_core::Message::Assistant { content, .. } = &ctx.latest_turn.message else {
return CriticVerdict::Accept;
};
let joined_text = content
.iter()
.filter_map(|c| match c {
Content::Text { text } => Some(text.as_str()),
_ => None,
})
.collect::<Vec<_>>()
.join(" ")
.to_ascii_lowercase();
const HEDGES: &[&str] = &["i'm not sure", "i am not sure", "maybe", "possibly"];
for hedge in HEDGES {
if joined_text.contains(hedge) {
return CriticVerdict::Reject {
reason: format!("response hedges: found '{hedge}'"),
};
}
}
CriticVerdict::Accept
}
}
struct ScriptedHedgeLlm;
#[async_trait]
impl Llm for ScriptedHedgeLlm {
fn name(&self) -> &str {
"scripted"
}
fn capabilities(&self) -> LlmCapabilities {
LlmCapabilities::default()
}
async fn complete(&self, _req: CompletionRequest) -> Result<CompletionResponse, LlmError> {
Ok(CompletionResponse {
id: "msg_1".into(),
model: ModelId::new("scripted-hedger"),
content: vec![Content::text("I'm not sure what you're asking.")],
stop_reason: StopReason::EndTurn,
usage: Usage {
tokens_input: 8,
tokens_output: 9,
..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 critics = Arc::new(
CompositeCritic::new("hedge-guard", AggregationPolicy::FirstReject)
.push(Box::new(NoHedgingCritic)),
);
let sink = Arc::new(InMemorySink::new());
let agent = Agent::builder()
.with_llm(Arc::new(ScriptedHedgeLlm))
.with_tools(Arc::new(FsToolSet::new()))
.with_event_sink(sink.clone())
.with_loop(Box::new(ReactLoop::new()))
.with_critics(critics)
.with_max_turns(3)
.build()?;
let outcome = agent.run(Task::new("figure it out")).await?;
match &outcome.termination {
Termination::Failed { error, .. } => {
println!("Termination: Failed (category={:?})", error.category);
println!("Critic message: {}", error.message);
}
other => println!("Termination: {other:?}"),
}
let rejections = sink
.events()
.iter()
.filter(|e| matches!(e.kind, EventKind::CriticRejected(_)))
.count();
println!("critic.rejected events: {rejections}");
Ok(())
}