sim_lib_agent_runner_core/
model.rs1use crate::{ModelBid, ModelCard, ModelEvent, ModelEventSink};
2use sim_citizen::CitizenField;
3use sim_citizen_derive::Citizen;
4use sim_kernel::{Cx, EvalRequest, Expr, Result, Symbol};
5
6pub trait ModelRunner: Send + Sync {
8 fn card(&self) -> ModelCard;
10
11 fn infer(&self, cx: &mut Cx, request: ModelRequest) -> Result<ModelResponse>;
13
14 fn infer_request(&self, cx: &mut Cx, request: EvalRequest) -> Result<ModelResponse> {
19 self.infer(cx, ModelRequest::try_from(request.expr)?)
20 }
21
22 fn infer_stream(
27 &self,
28 cx: &mut Cx,
29 request: ModelRequest,
30 sink: &mut dyn ModelEventSink,
31 ) -> Result<ModelResponse> {
32 let response = self.infer(cx, request)?;
33 sink.emit(ModelEvent::final_of(&response))?;
34 Ok(response)
35 }
36
37 fn infer_stream_request(
42 &self,
43 cx: &mut Cx,
44 request: EvalRequest,
45 sink: &mut dyn ModelEventSink,
46 ) -> Result<ModelResponse> {
47 self.infer_stream(cx, ModelRequest::try_from(request.expr)?, sink)
48 }
49
50 fn bid(&self, _request: &ModelRequest) -> Result<ModelBid> {
52 Ok(ModelBid::unavailable("runner did not implement bidding"))
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Citizen)]
58#[citizen(symbol = "agent-runner/ModelRequest", version = 1)]
59pub struct ModelRequest {
60 pub task: Expr,
62 pub messages: Vec<Expr>,
64 pub extra: Vec<(Expr, Expr)>,
66}
67
68impl ModelRequest {
69 pub fn new(task: Expr, messages: Vec<Expr>) -> Self {
71 Self {
72 task,
73 messages,
74 extra: Vec::new(),
75 }
76 }
77}
78
79impl Default for ModelRequest {
80 fn default() -> Self {
81 Self::new(Expr::String("citizen fixture task".to_owned()), Vec::new())
82 }
83}
84
85#[derive(Clone, Debug, PartialEq, Citizen)]
87#[citizen(symbol = "agent-runner/ModelResponse", version = 1)]
88pub struct ModelResponse {
89 pub runner: Symbol,
91 pub model: String,
93 pub content: Vec<Expr>,
95 pub stop_reason: Symbol,
97 pub usage: Option<ModelUsage>,
99 pub extra: Vec<(Expr, Expr)>,
101}
102
103impl ModelResponse {
104 pub fn new(
106 runner: Symbol,
107 model: impl Into<String>,
108 content: Vec<Expr>,
109 stop_reason: Symbol,
110 ) -> Self {
111 Self {
112 runner,
113 model: model.into(),
114 content,
115 stop_reason,
116 usage: None,
117 extra: Vec::new(),
118 }
119 }
120}
121
122impl Default for ModelResponse {
123 fn default() -> Self {
124 Self::new(
125 Symbol::qualified("runner", "fixture"),
126 "fixture-model",
127 vec![Expr::String("ok".to_owned())],
128 Symbol::new("stop"),
129 )
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Default, Citizen)]
135#[citizen(symbol = "agent-runner/ModelUsage", version = 1)]
136pub struct ModelUsage {
137 pub input_tokens: Option<u64>,
139 pub output_tokens: Option<u64>,
141 pub latency_ms: Option<u64>,
143 pub cost_usd: Option<f64>,
145 pub extra: Vec<(Expr, Expr)>,
147}
148
149impl CitizenField for ModelUsage {
150 fn encode_field(&self) -> Expr {
151 Expr::List(vec![
152 self.input_tokens.encode_field(),
153 self.output_tokens.encode_field(),
154 self.latency_ms.encode_field(),
155 self.cost_usd.encode_field(),
156 self.extra.encode_field(),
157 ])
158 }
159
160 fn decode_field_expr(expr: &Expr, field: &'static str) -> Result<Self> {
161 let Expr::List(items) = expr else {
162 return Err(sim_citizen::field_error(field, "expected model usage list"));
163 };
164 let [input_tokens, output_tokens, latency_ms, cost_usd, extra] = items.as_slice() else {
165 return Err(sim_citizen::field_error(
166 field,
167 format!("expected 5 model usage field(s), found {}", items.len()),
168 ));
169 };
170 Ok(Self {
171 input_tokens: Option::<u64>::decode_field_expr(input_tokens, field)?,
172 output_tokens: Option::<u64>::decode_field_expr(output_tokens, field)?,
173 latency_ms: Option::<u64>::decode_field_expr(latency_ms, field)?,
174 cost_usd: Option::<f64>::decode_field_expr(cost_usd, field)?,
175 extra: Vec::<(Expr, Expr)>::decode_field_expr(extra, field)?,
176 })
177 }
178}
179
180pub fn model_request_class_symbol() -> Symbol {
182 Symbol::qualified("agent-runner", "ModelRequest")
183}
184
185pub fn model_response_class_symbol() -> Symbol {
187 Symbol::qualified("agent-runner", "ModelResponse")
188}
189
190pub fn model_usage_class_symbol() -> Symbol {
192 Symbol::qualified("agent-runner", "ModelUsage")
193}