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:
ProofOfConcept 2026-04-04 16:39:04 -04:00 committed by Kent Overstreet
parent d195160b1e
commit 51e632c997
7 changed files with 33 additions and 78 deletions

View file

@ -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,

View file

@ -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),

View file

@ -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")]

View file

@ -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,

View file

@ -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.

View file

@ -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,

View file

@ -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(", ");