Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ export * from './structuredCompletionError';
export * from './structuredCompletionRequest';
export * from './structuredCompletionRequestAdditionalInstructions';
export * from './structuredCompletionResponse';
export * from './toolSchema';
export * from './toolSchemas';
export * from './toolSet';
export * from './toolSetOneOf';
export * from './toolSetOneOfThree';
Expand Down

This file was deleted.

This file was deleted.

31 changes: 0 additions & 31 deletions js/app/packages/service-clients/service-cognition/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3198,37 +3198,6 @@
"result": {}
}
},
"ToolSchema": {
"type": "object",
"description": "Schema information for a single tool, serializable for API responses.",
"required": ["name", "inputSchema", "outputSchema"],
"properties": {
"inputSchema": {
"description": "The JSON schema for the tool's input parameters."
},
"name": {
"type": "string",
"description": "The name of the tool."
},
"outputSchema": {
"description": "The JSON schema for the tool's output."
}
}
},
"ToolSchemas": {
"type": "object",
"description": "A collection of tool schemas, typically used for API responses.",
"required": ["schemas"],
"properties": {
"schemas": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ToolSchema"
},
"description": "The list of tool schemas."
}
}
},
"ToolSet": {
"oneOf": [
{
Expand Down
19 changes: 10 additions & 9 deletions js/app/scripts/generate-dcs-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ const JsonObjectValidator: z.ZodType<JsonObject> = z.record(
z.unknown()
);

// The combined schema has shared $defs and a tools array
const CombinedSchemaValidator = z.object({
// The frontend typegen schema (ai_toolset FrontendSchemas) has shared $defs
// and a tools array
const FrontendSchemasValidator = z.object({
$defs: z.record(z.string(), JsonObjectValidator),
tools: z.array(
z.object({
Expand All @@ -57,7 +58,7 @@ const CombinedSchemaValidator = z.object({
),
});

type CombinedSchema = z.infer<typeof CombinedSchemaValidator>;
type FrontendSchemas = z.infer<typeof FrontendSchemasValidator>;

async function buildAndRunSchemaGenerator(): Promise<void> {
console.log('Building gen_tool_schemas binary...');
Expand All @@ -77,17 +78,17 @@ async function buildAndRunSchemaGenerator(): Promise<void> {
console.log('Schema generation complete.');
}

async function loadCombinedSchema(): Promise<CombinedSchema> {
async function loadFrontendSchemas(): Promise<FrontendSchemas> {
await buildAndRunSchemaGenerator();
const data = await readFile(schemasJsonPath, 'utf-8');
const parsed = CombinedSchemaValidator.parse(JSON.parse(data));
const parsed = FrontendSchemasValidator.parse(JSON.parse(data));
console.log(
`Loaded ${parsed.tools.length} tools, ${Object.keys(parsed.$defs).length} definitions`
);
return parsed;
}

async function generateSchemasFile(schema: CombinedSchema) {
async function generateSchemasFile(schema: FrontendSchemas) {
const resolved = await $RefParser.dereference(structuredClone(schema));
const defs = (resolved as { $defs: Record<string, JsonObject> }).$defs;
const seen = new Set<string>();
Expand Down Expand Up @@ -118,7 +119,7 @@ async function generateSchemasFile(schema: CombinedSchema) {
);
}

async function generateToolTypesFile(schema: CombinedSchema) {
async function generateToolTypesFile(schema: FrontendSchemas) {
const properties: Record<string, unknown> = {};
for (const key of Object.keys(schema.$defs)) {
properties[key] = { $ref: `#/$defs/${key}` };
Expand Down Expand Up @@ -148,7 +149,7 @@ async function generateToolTypesFile(schema: CombinedSchema) {
await Bun.write(typesFile, `${warning}\n${cleaned}`);
}

async function generateToolsFile(schema: CombinedSchema) {
async function generateToolsFile(schema: FrontendSchemas) {
const entries = schema.tools
.map((t) => ({
name: t.name,
Expand Down Expand Up @@ -243,7 +244,7 @@ export function deserializeToolResponse(
await Bun.write(toolFile, contents);
}

const schema = await loadCombinedSchema();
const schema = await loadFrontendSchemas();

await generateSchemasFile(schema);
await generateToolsFile(schema);
Expand Down
4 changes: 2 additions & 2 deletions rust/cloud-storage/ai_tools/src/bin/gen_tool_schemas.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Binary to generate combined tool schemas JSON file.

fn main() {
let combined = ai_tools::all_tool_combined_schema();
let json = serde_json::to_string_pretty(&combined).expect("serialize schemas");
let schemas = ai_tools::all_tool_frontend_schemas();
let json = schemas.to_json_pretty().expect("serialize schemas");
std::fs::create_dir("schemas").expect("create schemas dir");
std::fs::write("schemas/tools.json", &json).expect("write tools.json");
println!("Generated ai_tools/schemas/tools.json");
Expand Down
17 changes: 8 additions & 9 deletions rust/cloud-storage/ai_tools/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#![recursion_limit = "256"]

use ai_toolset::AsyncToolCollection;
use ai_toolset::schema::{CombinedToolSchemas, ToolSchemaGenerator};
use ai_toolset::schema::{FrontendSchemas, ToolSchemaGenerator, frontend_schemas_builder};
mod build_context;
mod schemas;
pub mod search;
Expand Down Expand Up @@ -52,14 +52,10 @@ pub struct ToolSetWithPrompt {
}

impl ToolSchemaGenerator for ToolSetWithPrompt {
fn generate_schemas(&self) -> ai_toolset::schema::ToolSchemas {
self.toolset.generate_schemas()
}

fn register_schemas(
&self,
generator: &mut schemars::SchemaGenerator,
) -> Vec<ai_toolset::schema::CombinedToolEntry> {
) -> Vec<ai_toolset::schema::FrontendToolEntry> {
self.toolset.register_schemas(generator)
}
}
Expand Down Expand Up @@ -92,9 +88,12 @@ pub fn all_tools() -> ToolSetWithPrompt {
}
}

/// Combined schema with shared, deduplicated `$defs`.
pub fn all_tool_combined_schema() -> CombinedToolSchemas {
CombinedToolSchemas::builder()
/// Frontend typegen schemas with shared, deduplicated `$defs`.
///
/// These feed `gen_tool_schemas` / `generate-dcs-tools.ts` and are never
/// sent to AI providers.
pub fn all_tool_frontend_schemas() -> FrontendSchemas {
frontend_schemas_builder()
.merge(&all_tools())
.merge(&read::read_thread())
.build()
Expand Down
15 changes: 7 additions & 8 deletions rust/cloud-storage/ai_tools/src/search/search_service/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,22 @@ impl AsyncTool<Arc<SearchServiceClient>> for ContentSearch {
#[cfg(test)]
mod tests {
use super::*;
use ai_toolset::generate_tool_input_schema;
use ai_toolset::tool_object::validate_tool_schema;
use ai_toolset::schema::generate_validated_input_schema;

#[test]
fn test_content_search_schema_validation() {
let schema = generate_tool_input_schema!(ContentSearch);

let result = validate_tool_schema(&schema);
let result = generate_validated_input_schema::<ContentSearch>();
assert!(result.is_ok(), "{:?}", result);

let (name, description) = result.unwrap();
let validated = result.unwrap();
assert_eq!(
name, "ContentSearch",
validated.name, "ContentSearch",
"Tool name should match the schemars title"
);
assert!(
description.contains("Search items by their content"),
validated
.description
.contains("Search items by their content"),
"Description should contain expected text"
);
}
Expand Down
13 changes: 5 additions & 8 deletions rust/cloud-storage/ai_tools/src/search/search_service/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,23 +87,20 @@ impl AsyncTool<Arc<SearchServiceClient>> for NameSearch {
#[cfg(test)]
mod tests {
use super::*;
use ai_toolset::generate_tool_input_schema;
use ai_toolset::tool_object::validate_tool_schema;
use ai_toolset::schema::generate_validated_input_schema;

#[test]
fn test_name_search_schema_validation() {
let schema = generate_tool_input_schema!(NameSearch);

let result = validate_tool_schema(&schema);
let result = generate_validated_input_schema::<NameSearch>();
assert!(result.is_ok(), "{:?}", result);

let (name, description) = result.unwrap();
let validated = result.unwrap();
assert_eq!(
name, "NameSearch",
validated.name, "NameSearch",
"Tool name should match the schemars title"
);
assert!(
description.contains("Search items by their name"),
validated.description.contains("Search items by their name"),
"Description should contain expected text"
);
}
Expand Down
44 changes: 44 additions & 0 deletions rust/cloud-storage/ai_toolset/src/schema/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use serde_json::Error as JsonError;
use thiserror::Error;

/// Errors that can occur when validating a tool's schema.
#[derive(Debug, Error)]
pub enum ValidationError {
/// The schema is missing required metadata (title or description).
#[error("missing metadata")]
MissingMetadata,
/// Failed to serialize the schema to JSON.
#[error("could not convert to json")]
JsonSerialization(JsonError),
/// The schema contains nested objects which are not supported.
#[error("schema exceeds depth one - nested objects with properties are not allowed")]
ExceedsDepthOne,
/// The schema title is empty.
#[error("title is empty")]
EmptyTitle,
/// The schema contains `oneOf` which is not supported for AI tools.
#[error("schema must not have oneOf set. Do not use descriptions or /// on enum types.")]
OneOf,
/// Schema must be a serde_json::Value::Object
#[error("schema must be a serde_json::Value::Object")]
ExpectedObject,
/// The schema contains a `$ref`, which means a recursive type survived
/// inlining. Strict tool use cannot express recursive schemas.
#[error(
"schema contains $ref — recursive types cannot be inlined and are not supported by strict tool use"
)]
UnsupportedRef,
/// An object's `additionalProperties` is not `false` — map types with
/// arbitrary keys cannot be expressed in strict mode.
#[error(
"additionalProperties must be false — map types (e.g. HashMap) are not supported by strict tool use; use a Vec of key/value structs instead"
)]
AdditionalProperties,
/// An `enum` contains object or array values; strict mode only allows
/// primitive enum members.
#[error("enum values must be primitives (string, number, bool, or null)")]
ComplexEnum,
/// The root of a tool input schema must be an object.
#[error("tool input schema root must have type \"object\"")]
RootNotObject,
}
Loading
Loading