Implement SEP-1577: Sampling With Tools (#628)
* feat: implement SEP-1577 sampling with tools support * feat: add TryFrom<Content> for backward-compatible migration
This commit is contained in:
parent
be23334f9d
commit
8bd3fcb890
2 changed files with 1850 additions and 141 deletions
|
|
@ -194,6 +194,19 @@ pub struct ElicitationCapability {
|
|||
pub schema_validation: Option<bool>,
|
||||
}
|
||||
|
||||
/// Sampling capability with optional sub-capabilities (SEP-1577).
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct SamplingCapability {
|
||||
/// Support for `tools` and `toolChoice` parameters
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tools: Option<JsonObject>,
|
||||
/// Support for `includeContext` (soft-deprecated)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub context: Option<JsonObject>,
|
||||
}
|
||||
|
||||
///
|
||||
/// # Builder
|
||||
/// ```rust
|
||||
|
|
@ -217,8 +230,9 @@ pub struct ClientCapabilities {
|
|||
pub extensions: Option<ExtensionCapabilities>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub roots: Option<RootsCapabilities>,
|
||||
/// Capability for LLM sampling requests (SEP-1577)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sampling: Option<JsonObject>,
|
||||
pub sampling: Option<SamplingCapability>,
|
||||
/// Capability to handle elicitation requests from servers for interactive user input
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub elicitation: Option<ElicitationCapability>,
|
||||
|
|
@ -449,7 +463,7 @@ builder! {
|
|||
experimental: ExperimentalCapabilities,
|
||||
extensions: ExtensionCapabilities,
|
||||
roots: RootsCapabilities,
|
||||
sampling: JsonObject,
|
||||
sampling: SamplingCapability,
|
||||
elicitation: ElicitationCapability,
|
||||
tasks: TasksCapability,
|
||||
}
|
||||
|
|
@ -466,6 +480,26 @@ impl<const E: bool, const EXT: bool, const S: bool, const EL: bool, const TASKS:
|
|||
}
|
||||
}
|
||||
|
||||
impl<const E: bool, const EXT: bool, const R: bool, const EL: bool, const TASKS: bool>
|
||||
ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, true, EL, TASKS>>
|
||||
{
|
||||
/// Enable tool calling in sampling requests
|
||||
pub fn enable_sampling_tools(mut self) -> Self {
|
||||
if let Some(c) = self.sampling.as_mut() {
|
||||
c.tools = Some(JsonObject::default());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable context inclusion in sampling (soft-deprecated)
|
||||
pub fn enable_sampling_context(mut self) -> Self {
|
||||
if let Some(c) = self.sampling.as_mut() {
|
||||
c.context = Some(JsonObject::default());
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "elicitation")]
|
||||
impl<const E: bool, const EXT: bool, const R: bool, const S: bool, const TASKS: bool>
|
||||
ClientCapabilitiesBuilder<ClientCapabilitiesBuilderState<E, EXT, R, S, true, TASKS>>
|
||||
|
|
|
|||
|
|
@ -59,6 +59,137 @@ pub struct RawAudioContent {
|
|||
|
||||
pub type AudioContent = Annotated<RawAudioContent>;
|
||||
|
||||
/// Tool call request from assistant (SEP-1577).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolUseContent {
|
||||
/// Unique identifier for this tool call
|
||||
pub id: String,
|
||||
/// Name of the tool to call
|
||||
pub name: String,
|
||||
/// Input arguments for the tool
|
||||
pub input: super::JsonObject,
|
||||
/// Optional metadata (preserved for caching)
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<super::Meta>,
|
||||
}
|
||||
|
||||
/// Tool execution result in user message (SEP-1577).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolResultContent {
|
||||
/// Optional metadata
|
||||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<super::Meta>,
|
||||
/// ID of the corresponding tool use
|
||||
pub tool_use_id: String,
|
||||
/// Content blocks returned by the tool
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub content: Vec<Content>,
|
||||
/// Optional structured result
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub structured_content: Option<super::JsonObject>,
|
||||
/// Whether tool execution failed
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_error: Option<bool>,
|
||||
}
|
||||
|
||||
impl ToolUseContent {
|
||||
pub fn new(id: impl Into<String>, name: impl Into<String>, input: super::JsonObject) -> Self {
|
||||
Self {
|
||||
id: id.into(),
|
||||
name: name.into(),
|
||||
input,
|
||||
meta: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToolResultContent {
|
||||
pub fn new(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
|
||||
Self {
|
||||
meta: None,
|
||||
tool_use_id: tool_use_id.into(),
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
|
||||
Self {
|
||||
meta: None,
|
||||
tool_use_id: tool_use_id.into(),
|
||||
content,
|
||||
structured_content: None,
|
||||
is_error: Some(true),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Assistant message content types (SEP-1577).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum AssistantMessageContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
Audio(RawAudioContent),
|
||||
ToolUse(ToolUseContent),
|
||||
}
|
||||
|
||||
/// User message content types (SEP-1577).
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub enum UserMessageContent {
|
||||
Text(RawTextContent),
|
||||
Image(RawImageContent),
|
||||
Audio(RawAudioContent),
|
||||
ToolResult(ToolResultContent),
|
||||
}
|
||||
|
||||
impl AssistantMessageContent {
|
||||
/// Create a text content
|
||||
pub fn text(text: impl Into<String>) -> Self {
|
||||
Self::Text(RawTextContent {
|
||||
text: text.into(),
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a tool use content
|
||||
pub fn tool_use(
|
||||
id: impl Into<String>,
|
||||
name: impl Into<String>,
|
||||
input: super::JsonObject,
|
||||
) -> Self {
|
||||
Self::ToolUse(ToolUseContent::new(id, name, input))
|
||||
}
|
||||
}
|
||||
|
||||
impl UserMessageContent {
|
||||
/// Create a text content
|
||||
pub fn text(text: impl Into<String>) -> Self {
|
||||
Self::Text(RawTextContent {
|
||||
text: text.into(),
|
||||
meta: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a tool result content
|
||||
pub fn tool_result(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
|
||||
Self::ToolResult(ToolResultContent::new(tool_use_id, content))
|
||||
}
|
||||
|
||||
/// Create an error tool result content
|
||||
pub fn tool_result_error(tool_use_id: impl Into<String>, content: Vec<Content>) -> Self {
|
||||
Self::ToolResult(ToolResultContent::error(tool_use_id, content))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
|
|
|
|||
Loading…
Reference in a new issue