sim_lib_openai_server/routes/
models.rs1use std::collections::BTreeSet;
2
3use serde_json::{Value, json};
4use sim_kernel::{Args, Cx, Error, Expr, Result, Symbol, Value as SimValue};
5use sim_lib_agent_runner_core::ModelCard;
6
7use crate::server::{GatewayRequest, GatewayResponse, GatewayRouteState};
8
9pub const MODELS_PATH: &str = "/v1/models";
11
12const FIXTURE_ECHO_MODEL: &str = "fixture/echo";
13const SIM_BROWSE_PLAN_MODEL: &str = "sim/browse/plan";
14
15#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct OpenAiModel {
19 id: String,
20 owned_by: String,
21}
22
23impl OpenAiModel {
24 pub fn new(id: impl Into<String>, owned_by: impl Into<String>) -> Self {
26 Self {
27 id: id.into(),
28 owned_by: owned_by.into(),
29 }
30 }
31
32 pub fn fixture_echo() -> Self {
34 Self::new(FIXTURE_ECHO_MODEL, "sim")
35 }
36
37 pub fn sim_native(id: impl Into<String>) -> Self {
39 Self::new(id, "sim")
40 }
41
42 pub fn from_model_card(card: ModelCard) -> Self {
45 Self::new(card.model, card.provider.to_string())
46 }
47
48 pub fn id(&self) -> &str {
50 &self.id
51 }
52
53 fn to_json(&self) -> Value {
54 json!({
55 "id": self.id,
56 "object": "model",
57 "created": 0,
58 "owned_by": self.owned_by,
59 })
60 }
61}
62
63#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct ModelCatalog {
67 models: Vec<OpenAiModel>,
68}
69
70impl ModelCatalog {
71 pub fn default_fixture() -> Self {
73 Self::from_parts(Vec::new())
74 }
75
76 pub fn from_runner_card_exprs(cards: impl IntoIterator<Item = Expr>) -> Result<Self> {
79 let cards = cards
80 .into_iter()
81 .map(ModelCard::try_from)
82 .collect::<Result<Vec<_>>>()?;
83 Ok(Self::from_model_cards(cards))
84 }
85
86 pub fn from_model_cards(cards: impl IntoIterator<Item = ModelCard>) -> Self {
88 let runner_models = cards
89 .into_iter()
90 .map(OpenAiModel::from_model_card)
91 .collect::<Vec<_>>();
92 Self::from_parts(runner_models)
93 }
94
95 pub fn from_runner_args(cx: &mut Cx, args: Vec<SimValue>) -> Result<Self> {
101 if args.is_empty() {
102 return Ok(Self::default_fixture());
103 }
104 let cards = cx.call_function(&runner_cards_symbol(), Args::new(args))?;
105 let expr = cards.object().as_expr(cx)?;
106 let Expr::List(items) = expr else {
107 return Err(Error::Eval(
108 "runner/cards must return a list of model-card transcripts".to_owned(),
109 ));
110 };
111 Self::from_runner_card_exprs(items)
112 }
113
114 pub fn models(&self) -> &[OpenAiModel] {
116 &self.models
117 }
118
119 fn from_parts(runner_models: Vec<OpenAiModel>) -> Self {
120 let mut seen = BTreeSet::new();
121 let mut models = Vec::new();
122 push_unique(&mut models, &mut seen, OpenAiModel::fixture_echo());
123 for model in runner_models {
124 push_unique(&mut models, &mut seen, model);
125 }
126 push_unique(
127 &mut models,
128 &mut seen,
129 OpenAiModel::sim_native(SIM_BROWSE_PLAN_MODEL),
130 );
131 Self { models }
132 }
133
134 fn to_json(&self) -> Value {
135 json!({
136 "object": "list",
137 "data": self.models.iter().map(OpenAiModel::to_json).collect::<Vec<_>>(),
138 })
139 }
140}
141
142pub fn handle_models(_request: &GatewayRequest, state: &GatewayRouteState) -> GatewayResponse {
145 models_response_for_catalog(&ModelCatalog::from_model_cards(state.runners().cards()))
146}
147
148pub fn models_response() -> GatewayResponse {
150 models_response_for_catalog(&ModelCatalog::default_fixture())
151}
152
153pub fn models_response_for_runner_args(
156 cx: &mut Cx,
157 args: Vec<SimValue>,
158) -> Result<GatewayResponse> {
159 let catalog = ModelCatalog::from_runner_args(cx, args)?;
160 Ok(models_response_for_catalog(&catalog))
161}
162
163pub fn models_response_for_catalog(catalog: &ModelCatalog) -> GatewayResponse {
165 GatewayResponse::json(200, catalog.to_json().to_string().into_bytes())
166}
167
168pub fn runner_cards_symbol() -> Symbol {
170 Symbol::qualified("runner", "cards")
171}
172
173fn push_unique(models: &mut Vec<OpenAiModel>, seen: &mut BTreeSet<String>, model: OpenAiModel) {
174 if seen.insert(model.id.clone()) {
175 models.push(model);
176 }
177}