#![allow(
dead_code,
reason = "each example uses the part of the support it needs"
)]
use rig_core::serve::Dispatch;
use std::sync::Mutex;
use bevy_app::{App, AppExit};
use bevy_ecs::prelude::*;
use rig_core::{
completion::{CompletionResponse, ModelRef, ProviderCapabilities, Usage},
effect::{EffectKind, FamilyDescriptor, HandlerDescriptor, HandlerKey, Outcome},
error::{ErrorKind, ErrorReport},
message::AssistantContent,
serve::{Reply, Serve, ServingPolicy},
tool::{ToolOutput, ToolResult},
};
use rig_ecs::{
agent::{
AdditionalParams, DefaultMaxTurns, Failed, InvalidCalls, MaxTokens, MaxTurns, Output,
Owner, Preamble, RunResult, Settled, Temperature, ToolChoiceSpec, UsesModel,
},
bus::Handlers,
};
pub const MODEL: &str = "demo/model:default";
pub struct Scripted {
turns: Mutex<std::collections::VecDeque<Vec<AssistantContent>>>,
}
impl Scripted {
pub fn new(turns: Vec<Vec<AssistantContent>>) -> Self {
Self {
turns: Mutex::new(turns.into()),
}
}
fn next(&self) -> Vec<AssistantContent> {
self.turns
.lock()
.expect("turns")
.pop_front()
.unwrap_or_else(|| vec![AssistantContent::text("(the script is over)")])
}
}
impl Serve for Scripted {
type Family = rig_core::effect::family::Completion;
fn descriptor(&self) -> HandlerDescriptor {
model_descriptor()
}
async fn serve(&self, kind: EffectKind, _dispatch: Dispatch) -> Reply {
match kind {
EffectKind::Completion { stream: false, .. } => {
let response = CompletionResponse::new(
self.next(),
Usage::default(),
rig_core::message::Origin::new("example.api", "scripted", ""),
serde_json::json!({}),
);
Reply::Outcome(Ok(Outcome::Completion(response)))
}
EffectKind::Completion { stream: true, .. } => {
let parts = self.next();
Reply::written(
rig_core::message::Origin::new("scripted", "scripted", "scripted"),
move |mut writer| async move {
for part in parts {
match part {
AssistantContent::Text(text) => {
for word in text.text.split_inclusive(' ') {
if writer.text(word).await.is_err() {
return;
}
}
}
AssistantContent::ToolCall(call)
if writer
.tool_call(
call.function.name.clone(),
call.function.arguments_value(),
)
.await
.is_err() =>
{
return;
}
_ => {}
}
}
writer.raw(serde_json::json!({ "provider": "scripted" }));
let _ = writer
.finish(rig_core::operation::Finish {
usage: Usage::default(),
..rig_core::operation::Finish::default()
})
.await;
},
)
}
other => Reply::Outcome(Err(ErrorReport::new(
ErrorKind::HandlerUnavailable,
format!("a model cannot serve {}", other.name()),
))),
}
}
}
fn model_descriptor() -> HandlerDescriptor {
HandlerDescriptor {
key: HandlerKey::from(MODEL),
family: FamilyDescriptor::Completion {
model: ModelRef::new("scripted"),
capabilities: ProviderCapabilities::default(),
},
layers: Vec::new(),
}
}
pub fn call(name: &str, arguments: serde_json::Value) -> AssistantContent {
AssistantContent::tool_call(
format!("call-{name}"),
rig_core::message::ToolName::new(name).expect("tool name"),
arguments,
)
}
pub struct Tool {
pub key: String,
pub name: &'static str,
pub description: &'static str,
pub parameters: serde_json::Value,
pub run: fn(serde_json::Value) -> serde_json::Value,
}
impl Tool {
pub fn key(name: &str, index: usize) -> String {
format!("demo/tool:{name}#{index}")
}
}
impl Serve for Tool {
type Family = rig_core::effect::family::Tool;
fn descriptor(&self) -> HandlerDescriptor {
HandlerDescriptor {
key: HandlerKey::from(self.key.as_str()),
family: FamilyDescriptor::Tool {
name: self.name.to_owned(),
description: self.description.to_owned(),
parameters: self.parameters.clone(),
embedding: None,
},
layers: Vec::new(),
}
}
async fn serve(&self, kind: EffectKind, _dispatch: Dispatch) -> rig_core::serve::Reply {
let EffectKind::ToolCall { args, .. } = kind else {
return rig_core::serve::Reply::Outcome(Err(ErrorReport::new(
ErrorKind::Internal,
"not a call",
)));
};
let args: serde_json::Value = serde_json::from_str(&args).unwrap_or_default();
let value = (self.run)(args);
rig_core::serve::Reply::Outcome(Ok(Outcome::ToolResult {
result: ToolResult::success(ToolOutput::json(value)),
}))
}
}
fn xy() -> serde_json::Value {
serde_json::json!({"type": "object", "properties": {"x": {"type": "integer"}, "y": {"type": "integer"}}, "required": ["x", "y"]})
}
pub fn add() -> Tool {
Tool {
key: Tool::key("add", 0),
name: "add",
description: "Add x and y",
parameters: xy(),
run: |args| {
serde_json::json!(args["x"].as_i64().unwrap_or(0) + args["y"].as_i64().unwrap_or(0))
},
}
}
pub fn subtract() -> Tool {
Tool {
key: Tool::key("subtract", 1),
name: "subtract",
description: "Subtract y from x",
parameters: xy(),
run: |args| {
serde_json::json!(args["x"].as_i64().unwrap_or(0) - args["y"].as_i64().unwrap_or(0))
},
}
}
pub fn send_email() -> Tool {
Tool {
key: Tool::key("send_email", 0),
name: "send_email",
description: "Send an email to a recipient.",
parameters: serde_json::json!({"type": "object", "properties": {"to": {"type": "string"}, "subject": {"type": "string"}, "body": {"type": "string"}}, "required": ["to", "subject", "body"]}),
run: |args| serde_json::json!(format!("sent to {}", args["to"].as_str().unwrap_or("?"))),
}
}
pub fn app() -> App {
let mut app = App::new();
app.add_plugins(rig_ecs::RigPlugin::with_policy(ServingPolicy::default()));
app.add_observer(exit_when_failed);
app
}
pub fn register(
handlers: &mut Handlers,
model: Scripted,
tools: Vec<Tool>,
) -> (Entity, Vec<Entity>) {
let model = handlers.register(MODEL, model).expect("a fresh key");
let tools = tools
.into_iter()
.map(|tool| {
let key = tool.key.clone();
handlers.register(key.as_str(), tool).expect("a fresh key")
})
.collect();
(model, tools)
}
pub fn agent(commands: &mut Commands, model: Entity, preamble: &str, max_turns: usize) -> Entity {
commands
.spawn((
Owner("demo".to_owned()),
Preamble(Some(preamble.to_owned())),
Temperature(None),
MaxTokens(Some(1024)),
AdditionalParams(None),
ToolChoiceSpec(None),
Output::default(),
DefaultMaxTurns(Some(max_turns)),
MaxTurns(max_turns),
InvalidCalls::default(),
UsesModel(model),
))
.id()
}
pub fn print_the_answer_and_exit(
settled: On<Add, Settled>,
results: Query<&RunResult>,
mut exit: MessageWriter<AppExit>,
) {
if let Ok(result) = results.get(settled.event().entity) {
println!("{}", result.0);
}
exit.write(AppExit::Success);
}
pub fn exit_when_failed(
failed: On<Add, Failed>,
failures: Query<&Failed>,
mut exit: MessageWriter<AppExit>,
) {
if let Ok(Failed(failure)) = failures.get(failed.event().entity) {
eprintln!("the run failed: {failure:?}");
}
exit.write(AppExit::error());
}