tools: delete ToolDef and FunctionDef
ToolDef and FunctionDef are gone. Tool definitions are static strings on the Tool struct. The API layer builds JSON from Tool::to_json(). - ChatRequest.tools is now Option<serde_json::Value> - start_stream takes &[Tool] instead of Option<&[ToolDef]> - openai::stream_events takes &serde_json::Value for tools - memory_and_journal_tools() returns Vec<Tool> for subconscious agents - Subconscious agents filter by t.name instead of t.function.name No more runtime JSON construction for tool definitions. No more ToolDef::new(). No more FunctionDef. Co-Authored-By: Proof of Concept <poc@bcachefs.org>
This commit is contained in:
parent
d195160b1e
commit
51e632c997
7 changed files with 33 additions and 78 deletions
|
|
@ -17,7 +17,7 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::agent::tools::{ToolCall, ToolDef, FunctionCall};
|
use crate::agent::tools::{self as agent_tools, ToolCall, FunctionCall};
|
||||||
use crate::user::ui_channel::{UiMessage, UiSender};
|
use crate::user::ui_channel::{UiMessage, UiSender};
|
||||||
|
|
||||||
/// A JoinHandle that aborts its task when dropped.
|
/// A JoinHandle that aborts its task when dropped.
|
||||||
|
|
@ -41,6 +41,12 @@ pub struct SamplingParams {
|
||||||
// Stream events — yielded by backends, consumed by the runner
|
// Stream events — yielded by backends, consumed by the runner
|
||||||
// ─────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Build the tools JSON string from a slice of Tools.
|
||||||
|
fn tools_to_json_str(tools: &[agent_tools::Tool]) -> String {
|
||||||
|
let inner: Vec<String> = tools.iter().map(|t| t.to_json()).collect();
|
||||||
|
format!("[{}]", inner.join(","))
|
||||||
|
}
|
||||||
|
|
||||||
/// Events produced by the streaming API backends.
|
/// Events produced by the streaming API backends.
|
||||||
/// The runner reads these and decides what to display where.
|
/// The runner reads these and decides what to display where.
|
||||||
pub enum StreamEvent {
|
pub enum StreamEvent {
|
||||||
|
|
@ -98,7 +104,7 @@ impl ApiClient {
|
||||||
pub fn start_stream(
|
pub fn start_stream(
|
||||||
&self,
|
&self,
|
||||||
messages: &[Message],
|
messages: &[Message],
|
||||||
tools: Option<&[ToolDef]>,
|
tools: &[agent_tools::Tool],
|
||||||
ui_tx: &UiSender,
|
ui_tx: &UiSender,
|
||||||
reasoning_effort: &str,
|
reasoning_effort: &str,
|
||||||
sampling: SamplingParams,
|
sampling: SamplingParams,
|
||||||
|
|
@ -109,7 +115,8 @@ impl ApiClient {
|
||||||
let api_key = self.api_key.clone();
|
let api_key = self.api_key.clone();
|
||||||
let model = self.model.clone();
|
let model = self.model.clone();
|
||||||
let messages = messages.to_vec();
|
let messages = messages.to_vec();
|
||||||
let tools = tools.map(|t| t.to_vec());
|
let tools_json = tools_to_json_str(tools);
|
||||||
|
let tools_value: serde_json::Value = serde_json::from_str(&tools_json).unwrap_or_default();
|
||||||
let ui_tx = ui_tx.clone();
|
let ui_tx = ui_tx.clone();
|
||||||
let reasoning_effort = reasoning_effort.to_string();
|
let reasoning_effort = reasoning_effort.to_string();
|
||||||
let base_url = self.base_url.clone();
|
let base_url = self.base_url.clone();
|
||||||
|
|
@ -117,7 +124,7 @@ impl ApiClient {
|
||||||
let handle = tokio::spawn(async move {
|
let handle = tokio::spawn(async move {
|
||||||
let result = openai::stream_events(
|
let result = openai::stream_events(
|
||||||
&client, &base_url, &api_key, &model,
|
&client, &base_url, &api_key, &model,
|
||||||
&messages, tools.as_deref(), &tx, &ui_tx,
|
&messages, &tools_value, &tx, &ui_tx,
|
||||||
&reasoning_effort, sampling, priority,
|
&reasoning_effort, sampling, priority,
|
||||||
).await;
|
).await;
|
||||||
if let Err(e) = result {
|
if let Err(e) = result {
|
||||||
|
|
@ -131,7 +138,7 @@ impl ApiClient {
|
||||||
pub async fn chat_completion_stream_temp(
|
pub async fn chat_completion_stream_temp(
|
||||||
&self,
|
&self,
|
||||||
messages: &[Message],
|
messages: &[Message],
|
||||||
tools: Option<&[ToolDef]>,
|
tools: &[agent_tools::Tool],
|
||||||
ui_tx: &UiSender,
|
ui_tx: &UiSender,
|
||||||
reasoning_effort: &str,
|
reasoning_effort: &str,
|
||||||
sampling: SamplingParams,
|
sampling: SamplingParams,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ use anyhow::Result;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use crate::agent::tools::ToolDef;
|
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
use crate::user::ui_channel::{UiMessage, UiSender};
|
use crate::user::ui_channel::{UiMessage, UiSender};
|
||||||
use super::StreamEvent;
|
use super::StreamEvent;
|
||||||
|
|
@ -22,18 +21,19 @@ pub(super) async fn stream_events(
|
||||||
api_key: &str,
|
api_key: &str,
|
||||||
model: &str,
|
model: &str,
|
||||||
messages: &[Message],
|
messages: &[Message],
|
||||||
tools: Option<&[ToolDef]>,
|
tools_json: &serde_json::Value,
|
||||||
tx: &mpsc::UnboundedSender<StreamEvent>,
|
tx: &mpsc::UnboundedSender<StreamEvent>,
|
||||||
ui_tx: &UiSender,
|
ui_tx: &UiSender,
|
||||||
reasoning_effort: &str,
|
reasoning_effort: &str,
|
||||||
sampling: super::SamplingParams,
|
sampling: super::SamplingParams,
|
||||||
priority: Option<i32>,
|
priority: Option<i32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
let has_tools = tools_json.as_array().map_or(false, |a| !a.is_empty());
|
||||||
let request = ChatRequest {
|
let request = ChatRequest {
|
||||||
model: model.to_string(),
|
model: model.to_string(),
|
||||||
messages: messages.to_vec(),
|
messages: messages.to_vec(),
|
||||||
tool_choice: tools.map(|_| "auto".to_string()),
|
tool_choice: if has_tools { Some("auto".to_string()) } else { None },
|
||||||
tools: tools.map(|t| t.to_vec()),
|
tools: if has_tools { Some(tools_json.clone()) } else { None },
|
||||||
max_tokens: Some(16384),
|
max_tokens: Some(16384),
|
||||||
temperature: Some(sampling.temperature),
|
temperature: Some(sampling.temperature),
|
||||||
top_p: Some(sampling.top_p),
|
top_p: Some(sampling.top_p),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::agent::tools::{ToolCall, ToolCallDelta, ToolDef};
|
use crate::agent::tools::{ToolCall, ToolCallDelta};
|
||||||
|
|
||||||
/// Message content — either plain text or an array of content parts
|
/// Message content — either plain text or an array of content parts
|
||||||
/// (for multimodal messages with images). Serializes as a JSON string
|
/// (for multimodal messages with images). Serializes as a JSON string
|
||||||
|
|
@ -87,7 +87,7 @@ pub struct ChatRequest {
|
||||||
pub model: String,
|
pub model: String,
|
||||||
pub messages: Vec<Message>,
|
pub messages: Vec<Message>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub tools: Option<Vec<ToolDef>>,
|
pub tools: Option<serde_json::Value>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub tool_choice: Option<String>,
|
pub tool_choice: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
||||||
|
|
@ -306,10 +306,9 @@ impl Agent {
|
||||||
top_p: me.top_p,
|
top_p: me.top_p,
|
||||||
top_k: me.top_k,
|
top_k: me.top_k,
|
||||||
};
|
};
|
||||||
let tool_defs: Vec<_> = me.tools.iter().map(|t| t.to_tool_def()).collect();
|
|
||||||
me.client.start_stream(
|
me.client.start_stream(
|
||||||
&api_messages,
|
&api_messages,
|
||||||
Some(&tool_defs),
|
&me.tools,
|
||||||
ui_tx,
|
ui_tx,
|
||||||
&me.reasoning_effort,
|
&me.reasoning_effort,
|
||||||
sampling,
|
sampling,
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ mod write;
|
||||||
// Agent-specific tools
|
// Agent-specific tools
|
||||||
mod control;
|
mod control;
|
||||||
mod vision;
|
mod vision;
|
||||||
pub mod working_stack;
|
mod working_stack;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
@ -55,19 +55,6 @@ impl Tool {
|
||||||
self.parameters_json,
|
self.parameters_json,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a ToolDef (for backward compat where ToolDef is still used).
|
|
||||||
pub fn to_tool_def(&self) -> ToolDef {
|
|
||||||
ToolDef {
|
|
||||||
tool_type: "function".to_string(),
|
|
||||||
function: FunctionDef {
|
|
||||||
name: self.name.to_string(),
|
|
||||||
description: self.description.to_string(),
|
|
||||||
parameters: serde_json::from_str(self.parameters_json)
|
|
||||||
.expect("invalid JSON in tool parameters"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function call within a tool call — name + JSON arguments.
|
/// Function call within a tool call — name + JSON arguments.
|
||||||
|
|
@ -77,15 +64,6 @@ pub struct FunctionCall {
|
||||||
pub arguments: String,
|
pub arguments: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function definition for tool schema.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct FunctionDef {
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub parameters: serde_json::Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Partial function call within a streaming delta.
|
/// Partial function call within a streaming delta.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct FunctionCallDelta {
|
pub struct FunctionCallDelta {
|
||||||
|
|
@ -93,27 +71,6 @@ pub struct FunctionCallDelta {
|
||||||
pub arguments: Option<String>,
|
pub arguments: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tool definition sent to the model.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct ToolDef {
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
pub tool_type: String,
|
|
||||||
pub function: FunctionDef,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToolDef {
|
|
||||||
pub fn new(name: &str, description: &str, parameters: serde_json::Value) -> Self {
|
|
||||||
Self {
|
|
||||||
tool_type: "function".to_string(),
|
|
||||||
function: FunctionDef {
|
|
||||||
name: name.to_string(),
|
|
||||||
description: description.to_string(),
|
|
||||||
parameters,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A tool call requested by the model.
|
/// A tool call requested by the model.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct ToolCall {
|
pub struct ToolCall {
|
||||||
|
|
@ -135,7 +92,6 @@ pub struct ToolCallDelta {
|
||||||
pub function: Option<FunctionCallDelta>,
|
pub function: Option<FunctionCallDelta>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A tool call in flight — metadata for TUI + JoinHandle for
|
/// A tool call in flight — metadata for TUI + JoinHandle for
|
||||||
/// result collection and cancellation.
|
/// result collection and cancellation.
|
||||||
pub struct ActiveToolCall {
|
pub struct ActiveToolCall {
|
||||||
|
|
@ -220,17 +176,11 @@ pub fn tools() -> Vec<Tool> {
|
||||||
all
|
all
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return all tool definitions for the API.
|
/// Memory + journal tools only — for subconscious agents.
|
||||||
pub fn definitions() -> Vec<ToolDef> {
|
pub fn memory_and_journal_tools() -> Vec<Tool> {
|
||||||
tools().into_iter().map(|t| t.to_tool_def()).collect()
|
let mut all = memory::memory_tools().to_vec();
|
||||||
}
|
all.extend(memory::journal_tools());
|
||||||
|
all
|
||||||
/// Return memory + journal tool definitions only.
|
|
||||||
pub fn memory_and_journal_definitions() -> Vec<ToolDef> {
|
|
||||||
memory::memory_tools().into_iter()
|
|
||||||
.chain(memory::journal_tools())
|
|
||||||
.map(|t| t.to_tool_def())
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a short summary of tool args for the tools pane header.
|
/// Create a short summary of tool args for the tools pane header.
|
||||||
|
|
|
||||||
|
|
@ -46,13 +46,12 @@ pub async fn call_api_with_tools(
|
||||||
let (ui_tx, mut ui_rx) = crate::user::ui_channel::channel();
|
let (ui_tx, mut ui_rx) = crate::user::ui_channel::channel();
|
||||||
|
|
||||||
// All available native tools for subconscious agents
|
// All available native tools for subconscious agents
|
||||||
let all_tools = agent_tools::memory_and_journal_definitions();
|
let all_tools = agent_tools::memory_and_journal_tools();
|
||||||
// If agent header specifies a tools whitelist, filter to only those
|
let agent_tool_list: Vec<_> = if tools.is_empty() {
|
||||||
let tool_defs: Vec<_> = if tools.is_empty() {
|
|
||||||
all_tools
|
all_tools
|
||||||
} else {
|
} else {
|
||||||
all_tools.into_iter()
|
all_tools.into_iter()
|
||||||
.filter(|t| tools.iter().any(|w| w == &t.function.name))
|
.filter(|t| tools.iter().any(|w| *w == t.name))
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
// Provenance tracks which agent:phase is making writes.
|
// Provenance tracks which agent:phase is making writes.
|
||||||
|
|
@ -83,7 +82,7 @@ pub async fn call_api_with_tools(
|
||||||
};
|
};
|
||||||
match client.chat_completion_stream_temp(
|
match client.chat_completion_stream_temp(
|
||||||
&messages,
|
&messages,
|
||||||
Some(&tool_defs),
|
&agent_tool_list,
|
||||||
&ui_tx,
|
&ui_tx,
|
||||||
&reasoning,
|
&reasoning,
|
||||||
sampling,
|
sampling,
|
||||||
|
|
|
||||||
|
|
@ -295,13 +295,13 @@ fn run_one_agent_inner(
|
||||||
_llm_tag: &str,
|
_llm_tag: &str,
|
||||||
log: &(dyn Fn(&str) + Sync),
|
log: &(dyn Fn(&str) + Sync),
|
||||||
) -> Result<AgentResult, String> {
|
) -> Result<AgentResult, String> {
|
||||||
let all_tools = crate::agent::tools::memory_and_journal_definitions();
|
let all_tools = crate::agent::tools::memory_and_journal_tools();
|
||||||
let effective_tools: Vec<String> = if def.tools.is_empty() {
|
let effective_tools: Vec<String> = if def.tools.is_empty() {
|
||||||
all_tools.iter().map(|t| t.function.name.clone()).collect()
|
all_tools.iter().map(|t| t.name.to_string()).collect()
|
||||||
} else {
|
} else {
|
||||||
all_tools.iter()
|
all_tools.iter()
|
||||||
.filter(|t| def.tools.iter().any(|w| w == &t.function.name))
|
.filter(|t| def.tools.iter().any(|w| w == &t.name))
|
||||||
.map(|t| t.function.name.clone())
|
.map(|t| t.name.to_string())
|
||||||
.collect()
|
.collect()
|
||||||
};
|
};
|
||||||
let tools_desc = effective_tools.join(", ");
|
let tools_desc = effective_tools.join(", ");
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue