use llm_toolkit::{
agent::impls::ClaudeCodeAgent,
orchestrator::{BlueprintWorkflow, Orchestrator},
};
use serde_json::Value;
use std::collections::HashMap;
use tokio::sync::mpsc;
use tracing::{Event, Subscriber};
use tracing_subscriber::prelude::*;
use tracing_subscriber::{Layer, layer::Context};
pub struct JsonChannelLayer {
sender: mpsc::Sender<String>,
}
impl<S> Layer<S> for JsonChannelLayer
where
S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>,
{
fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
let mut fields = HashMap::new();
let mut visitor = JsonVisitor(&mut fields);
event.record(&mut visitor);
let mut span_fields = HashMap::new();
if let Some(span) = ctx.current_span().id().and_then(|id| ctx.span(id)) {
let extensions = span.extensions();
if let Some(visitor) = extensions.get::<JsonVisitor>() {
span_fields = visitor.0.clone();
}
}
let output = serde_json::json!({
"target": event.metadata().target(),
"level": event.metadata().level().to_string(),
"message": fields.get("message").cloned().unwrap_or_default(),
"fields": fields,
"span": span_fields,
});
let _ = self.sender.try_send(output.to_string());
}
}
struct JsonVisitor<'a>(&'a mut HashMap<String, Value>);
impl<'a> tracing::field::Visit for JsonVisitor<'a> {
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.0.insert(
field.name().to_string(),
serde_json::json!(format!("{:?}", value)),
);
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let (tx, mut rx) = mpsc::channel::<String>(100);
let subscriber = tracing_subscriber::registry()
.with(JsonChannelLayer { sender: tx })
.with(tracing_subscriber::filter::EnvFilter::new("info")); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
let listener_handle = tokio::spawn(async move {
println!("--- Orchestrator Events ---");
while let Some(event_str) = rx.recv().await {
match serde_json::from_str::<Value>(&event_str) {
Ok(json) => {
if let Ok(pretty_json) = serde_json::to_string_pretty(&json) {
println!("{}", pretty_json);
println!("--------------------");
}
}
Err(_) => {
println!("{}", event_str); println!("--------------------");
}
}
}
});
let blueprint = BlueprintWorkflow::new(
"Goal: Write a short, engaging blog post about a technical topic.
Workflow:
1. **Brainstorming**: Generate a few potential topics.
2. **Outline Creation**: Choose the best topic and create an outline.
3. **Drafting**: Write the blog post based on the outline.
4. **Review and Refine**: Review the draft for clarity, tone, and technical accuracy.
"
.to_string(),
);
let mut orchestrator = Orchestrator::new(blueprint);
orchestrator.add_agent(ClaudeCodeAgent::new());
let task = "the Rust programming language's ownership system";
let result = orchestrator.execute(task).await;
println!("\n--- Final Orchestrator Result ---");
println!("{}", serde_json::to_string_pretty(&result)?);
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
drop(orchestrator);
listener_handle.await?;
Ok(())
}