conversation_api/execution/
llm.rs1use crate::execution::{ExternalError, InvocationContext};
3use async_trait::async_trait;
4pub use llm_api::{
5 Completion, CompletionRequest, ContentPart, Continuation, FinishReason, INPUT_AUDIO,
6 INPUT_FILE, INPUT_IMAGE, INPUT_VIDEO, Message, MessageRole, ModelCapabilities,
7 ModelConstraints, ModelMode, ModelProfile, TokenUsage, ToolCall, UseCase,
8 input_modality_for_kind, payload_input_modalities,
9};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use thiserror::Error;
13
14#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
16pub struct ToolDefinition {
17 pub name: String,
19 pub description: String,
21 pub input_schema: Value,
23 pub policy: ToolPolicy,
25}
26
27#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30pub enum ToolEffect {
31 ReadOnly,
33 Mutating,
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
39pub struct ToolPolicy {
40 pub effect: ToolEffect,
42 pub parallel_safe: bool,
44}
45
46#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
48#[serde(deny_unknown_fields)]
49pub struct UsageSummary {
50 pub model_requests: u32,
52 pub reported_model_requests: u32,
54 pub input_tokens: u64,
56 pub output_tokens: u64,
58 pub cached_input_tokens: Option<u64>,
60 pub reasoning_output_tokens: Option<u64>,
62 pub peak_input_tokens: u64,
64 pub credits: Option<u64>,
66 pub tool_calls: u32,
68 pub window_truncations: u32,
70 pub trimmed_messages: u64,
72}
73
74impl UsageSummary {
75 pub fn record_model_attempt(&mut self) {
77 self.model_requests = self.model_requests.saturating_add(1);
78 }
79
80 pub fn record_usage(&mut self, usage: TokenUsage, reported: bool) {
82 if reported {
83 self.reported_model_requests = self.reported_model_requests.saturating_add(1);
84 }
85 self.input_tokens = self.input_tokens.saturating_add(usage.input_tokens);
86 self.output_tokens = self.output_tokens.saturating_add(usage.output_tokens);
87 self.peak_input_tokens = self.peak_input_tokens.max(usage.input_tokens);
88 add_optional(&mut self.cached_input_tokens, usage.cached_input_tokens);
89 add_optional(
90 &mut self.reasoning_output_tokens,
91 usage.reasoning_output_tokens,
92 );
93 add_optional(&mut self.credits, usage.credits);
94 }
95
96 pub fn record_model_request(&mut self, usage: Option<TokenUsage>) {
98 self.record_model_attempt();
99 let Some(usage) = usage else {
100 return;
101 };
102 self.record_usage(usage, true);
103 }
104
105 pub fn record_window_truncation(&mut self, removed_messages: usize) {
107 self.window_truncations = self.window_truncations.saturating_add(1);
108 self.trimmed_messages = self
109 .trimmed_messages
110 .saturating_add(u64::try_from(removed_messages).unwrap_or(u64::MAX));
111 }
112}
113
114fn add_optional(total: &mut Option<u64>, value: Option<u64>) {
115 if let Some(value) = value {
116 *total = Some(total.unwrap_or(0).saturating_add(value));
117 }
118}
119
120#[derive(Clone, Debug, Error, Eq, PartialEq)]
122#[error("{error}")]
123pub struct LlmFailure {
124 pub error: ExternalError,
126 pub usage: Option<TokenUsage>,
128}
129
130impl From<ExternalError> for LlmFailure {
131 fn from(error: ExternalError) -> Self {
132 Self { error, usage: None }
133 }
134}
135
136#[async_trait]
141pub trait Llm: Send + Sync {
142 async fn model_profile(
144 &self,
145 context: &InvocationContext,
146 use_case: &UseCase,
147 model_mode: &ModelMode,
148 ) -> Result<ModelProfile, ExternalError>;
149
150 async fn complete(
152 &self,
153 context: &InvocationContext,
154 request: CompletionRequest,
155 ) -> Result<Completion, LlmFailure>;
156}
157
158impl ToolDefinition {
159 pub fn model_definition(&self) -> llm_api::ToolDefinition {
160 llm_api::ToolDefinition {
161 name: self.name.clone(),
162 description: self.description.clone(),
163 input_schema: self.input_schema.clone(),
164 }
165 }
166}