use artificial::openai::OpenAiAdapterBuilder;
use artificial::{
StreamingEventsProvider as _,
generic::{GenericFunctionSpec, GenericMessage, GenericRole, StreamEvent},
model::{Model, OpenAiModel},
provider::ChatCompleteParameters,
};
use futures_util::StreamExt;
use std::io::{self, Write};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let backend = OpenAiAdapterBuilder::new_from_env().build()?;
let weather_api_tool = GenericFunctionSpec {
name: "current_weather".to_string(),
description: "Fetch the current weather report (temperature in °C and condition)."
.to_string(),
parameters: serde_json::json!({
"type": "object",
"additionalProperties": false,
"required": ["location", "unit"],
"properties": {
"location": { "type": "string", "description": "City name, e.g. Berlin" },
"unit": { "type": "string", "enum": ["celsius", "fahrenheit"], "default": "celsius" }
}
}),
};
let mut messages = vec![
GenericMessage::new(
"You are a helpful assistant that uses tools. Keep replies short.".into(),
GenericRole::System,
),
GenericMessage::new(
"What's the weather like in Berlin in celsius?".into(),
GenericRole::User,
),
];
let params =
ChatCompleteParameters::new(messages.clone(), Model::OpenAi(OpenAiModel::Gpt4oMini))
.with_tools(vec![weather_api_tool.clone()]);
let mut stream = backend.chat_complete_events_stream(params);
print!("Assistant: ");
io::stdout().flush().ok();
let mut tool_intents: Vec<artificial::generic::GenericFunctionCallIntent> = Vec::new();
while let Some(event) = stream.next().await {
match event {
Ok(StreamEvent::TextDelta(s)) => {
print!("{s}");
io::stdout().flush().ok();
}
Ok(StreamEvent::ToolCallStart { index, id, name }) => {
eprintln!("\n[debug] tool-call[{index}] start: id={id:?}, name={name:?}");
}
Ok(StreamEvent::ToolCallArgumentsDelta {
index,
arguments_fragment,
}) => {
eprintln!("\n[debug] tool-call[{index}] args += {arguments_fragment:?}");
}
Ok(StreamEvent::ToolCallComplete { index, intent }) => {
eprintln!(
"\n[debug] tool-call[{index}] complete: {} {:?}",
intent.function.name, intent.function.arguments
);
tool_intents.push(intent);
}
Ok(StreamEvent::MessageEnd) => {
break;
}
Ok(StreamEvent::Usage(_usage)) => {
}
Err(e) => {
eprintln!("\n\nError while streaming: {e}");
return Ok(());
}
}
}
if !tool_intents.is_empty() {
messages.push(GenericMessage {
content: None,
role: GenericRole::Assistant,
name: None,
tool_calls: Some(tool_intents.clone()),
tool_call_id: None,
});
for intent in &tool_intents {
match intent.function.name.as_str() {
"current_weather" => {
let (location, unit) = {
let args = &intent.function.arguments;
let location = args
.as_object()
.and_then(|o| o.get("location"))
.and_then(|v| v.as_str())
.unwrap_or("Berlin")
.to_string();
let unit = args
.as_object()
.and_then(|o| o.get("unit"))
.and_then(|v| v.as_str())
.unwrap_or("celsius")
.to_string();
(location, unit)
};
let tool_output = get_weather(location, unit);
messages.push(
GenericMessage::new(tool_output, GenericRole::Tool)
.with_tool_call_id(intent.id.clone()),
);
}
other => {
eprintln!("[warn] Unsupported tool requested: {other}");
}
}
}
let params2 = ChatCompleteParameters::new(messages, Model::OpenAi(OpenAiModel::Gpt4oMini))
.with_tools(vec![weather_api_tool]);
let mut stream2 = backend.chat_complete_events_stream(params2);
print!("\nAssistant (after tool): ");
io::stdout().flush().ok();
while let Some(event) = stream2.next().await {
match event {
Ok(StreamEvent::TextDelta(s)) => {
print!("{s}");
io::stdout().flush().ok();
}
Ok(StreamEvent::MessageEnd) => break,
Ok(_) => {}
Err(e) => {
eprintln!("\n\nError while streaming follow-up: {e}");
break;
}
}
}
println!();
} else {
println!();
}
Ok(())
}
fn get_weather(location: String, unit: String) -> String {
let (temperature, condition) = match location.to_lowercase().as_str() {
"berlin" => (12, "Cloudy"),
"london" => (10, "Rain"),
"san francisco" => (16, "Fog"),
_ => (20, "Sunny"),
};
format!(
r#"{{"location":"{location}","unit":"{unit}","temperature":{temperature},"condition":"{condition}"}}"#
)
}