Skip to content

Commit 26b9685

Browse files
feat: model settings and system configs (#74)
Co-authored-by: Robert Yan <mstr.raphael@gmail.com>
1 parent b841091 commit 26b9685

File tree

27 files changed

+2481
-516
lines changed

27 files changed

+2481
-516
lines changed

crates/api/src/main.rs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use services::{
1212
conversation::service::ConversationServiceImpl,
1313
file::service::FileServiceImpl,
1414
metrics::{MockMetricsService, OtlpMetricsService},
15+
model::service::ModelServiceImpl,
1516
response::service::OpenAIProxy,
1617
user::UserServiceImpl,
1718
user::UserSettingsServiceImpl,
@@ -71,6 +72,8 @@ async fn main() -> anyhow::Result<()> {
7172
let app_config_repo = db.app_config_repository();
7273
let near_nonce_repo = db.near_nonce_repository();
7374
let analytics_repo = db.analytics_repository();
75+
let system_configs_repo = db.system_configs_repository();
76+
let model_repo = db.model_repository();
7477

7578
// Create services
7679
tracing::info!("Initializing services...");
@@ -89,9 +92,9 @@ async fn main() -> anyhow::Result<()> {
8992

9093
let user_service = Arc::new(UserServiceImpl::new(user_repo.clone()));
9194

92-
let user_settings_service = Arc::new(UserSettingsServiceImpl::new(
93-
user_settings_repo as Arc<dyn services::user::ports::UserSettingsRepository>,
94-
));
95+
let user_settings_service = Arc::new(UserSettingsServiceImpl::new(user_settings_repo));
96+
97+
let model_service = Arc::new(ModelServiceImpl::new(model_repo));
9598

9699
// Initialize VPC credentials service and get API key
97100
let vpc_auth_config = if config.vpc_auth.is_configured() {
@@ -150,6 +153,15 @@ async fn main() -> anyhow::Result<()> {
150153
analytics_repo as Arc<dyn services::analytics::AnalyticsRepository>,
151154
));
152155

156+
// Initialize system configs service
157+
tracing::info!("Initializing system configs service...");
158+
let system_configs_service = Arc::new(
159+
services::system_configs::service::SystemConfigsServiceImpl::new(
160+
system_configs_repo
161+
as Arc<dyn services::system_configs::ports::SystemConfigsRepository>,
162+
),
163+
);
164+
153165
// Initialize metrics service
154166
tracing::info!("Initializing metrics service...");
155167
let metrics_service: Arc<dyn services::metrics::MetricsServiceTrait> =
@@ -209,6 +221,8 @@ async fn main() -> anyhow::Result<()> {
209221
oauth_service,
210222
user_service,
211223
user_settings_service,
224+
model_service,
225+
system_configs_service,
212226
session_repository: session_repo,
213227
proxy_service,
214228
conversation_service,
@@ -222,6 +236,7 @@ async fn main() -> anyhow::Result<()> {
222236
analytics_service,
223237
near_rpc_url: config.near.rpc_url.clone(),
224238
near_balance_cache: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
239+
model_settings_cache: Arc::new(tokio::sync::RwLock::new(std::collections::HashMap::new())),
225240
};
226241

227242
// Create router with CORS support

crates/api/src/models.rs

Lines changed: 166 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -191,16 +191,6 @@ impl From<services::user::ports::UserSettingsContent> for UserSettingsContent {
191191
}
192192
}
193193

194-
/// User settings response
195-
#[derive(Debug, Serialize, Deserialize, ToSchema)]
196-
pub struct UserSettingsResponse {
197-
/// User ID
198-
pub user_id: UserId,
199-
/// Settings content (serialized as "settings")
200-
#[serde(rename = "settings")]
201-
pub content: UserSettingsContent,
202-
}
203-
204194
/// User settings update request
205195
#[derive(Debug, Serialize, Deserialize, ToSchema)]
206196
pub struct UpdateUserSettingsRequest {
@@ -251,6 +241,124 @@ impl UpdateUserSettingsPartiallyRequest {
251241
}
252242
}
253243

244+
/// User settings response
245+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
246+
pub struct UserSettingsResponse {
247+
/// User ID
248+
pub user_id: UserId,
249+
/// Settings content (serialized as "settings")
250+
#[serde(rename = "settings")]
251+
pub content: UserSettingsContent,
252+
}
253+
254+
/// Model settings content for API responses
255+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
256+
pub struct ModelSettings {
257+
/// Whether models are public (visible/usable in responses)
258+
pub public: bool,
259+
/// Optional system-level system prompt for this model
260+
#[serde(skip_serializing_if = "Option::is_none")]
261+
pub system_prompt: Option<String>,
262+
}
263+
264+
impl From<services::model::ports::ModelSettings> for ModelSettings {
265+
fn from(content: services::model::ports::ModelSettings) -> Self {
266+
Self {
267+
public: content.public,
268+
system_prompt: content.system_prompt,
269+
}
270+
}
271+
}
272+
273+
impl From<ModelSettings> for services::model::ports::ModelSettings {
274+
fn from(content: ModelSettings) -> Self {
275+
Self {
276+
public: content.public,
277+
system_prompt: content.system_prompt,
278+
}
279+
}
280+
}
281+
282+
/// Partial model settings for API requests
283+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
284+
pub struct PartialModelSettings {
285+
/// Whether models are public (visible/usable in responses)
286+
pub public: Option<bool>,
287+
/// Optional system-level system prompt for this model
288+
pub system_prompt: Option<String>,
289+
}
290+
291+
impl From<services::model::ports::PartialModelSettings> for PartialModelSettings {
292+
fn from(content: services::model::ports::PartialModelSettings) -> Self {
293+
Self {
294+
public: content.public,
295+
system_prompt: content.system_prompt,
296+
}
297+
}
298+
}
299+
300+
impl From<PartialModelSettings> for services::model::ports::PartialModelSettings {
301+
fn from(content: PartialModelSettings) -> Self {
302+
Self {
303+
public: content.public,
304+
system_prompt: content.system_prompt,
305+
}
306+
}
307+
}
308+
309+
/// Complete model response
310+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
311+
pub struct ModelResponse {
312+
/// External model identifier (e.g. "gpt-4.1")
313+
pub model_id: String,
314+
/// Settings stored for this model
315+
pub settings: ModelSettings,
316+
}
317+
318+
impl From<services::model::ports::Model> for ModelResponse {
319+
fn from(model: services::model::ports::Model) -> Self {
320+
Self {
321+
model_id: model.model_id,
322+
settings: model.settings.into(),
323+
}
324+
}
325+
}
326+
327+
/// Model upsert request
328+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
329+
pub struct UpsertModelsRequest {
330+
pub settings: ModelSettings,
331+
}
332+
333+
/// Model settings update request (partial update)
334+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
335+
pub struct UpdateModelRequest {
336+
pub settings: Option<PartialModelSettings>,
337+
}
338+
339+
/// Batch model upsert request
340+
///
341+
/// Maps model_id to partial settings to update.
342+
/// Example: { "gpt-4": { "public": true }, "gpt-3.5": { "public": false, "system_prompt": "..." } }
343+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
344+
pub struct BatchUpsertModelsRequest {
345+
#[serde(flatten)]
346+
pub models: std::collections::HashMap<String, PartialModelSettings>,
347+
}
348+
349+
/// Model list response with pagination
350+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
351+
pub struct ModelListResponse {
352+
/// List of models
353+
pub models: Vec<ModelResponse>,
354+
/// Maximum number of items returned
355+
pub limit: i64,
356+
/// Number of items skipped
357+
pub offset: i64,
358+
/// Total number of models
359+
pub total: i64,
360+
}
361+
254362
/// Paginated user list response
255363
#[derive(Debug, Serialize, Deserialize, ToSchema)]
256364
pub struct UserListResponse {
@@ -264,6 +372,54 @@ pub struct UserListResponse {
264372
pub total: u64,
265373
}
266374

375+
/// System configs response
376+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
377+
pub struct SystemConfigsResponse {
378+
/// Default model identifier to use when not specified
379+
#[serde(skip_serializing_if = "Option::is_none")]
380+
pub default_model: Option<String>,
381+
}
382+
383+
impl From<services::system_configs::ports::SystemConfigs> for SystemConfigsResponse {
384+
fn from(config: services::system_configs::ports::SystemConfigs) -> Self {
385+
Self {
386+
default_model: config.default_model,
387+
}
388+
}
389+
}
390+
391+
/// System configs upsert request (full replace)
392+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
393+
pub struct UpsertSystemConfigsRequest {
394+
/// Default model identifier to use when not specified
395+
#[serde(skip_serializing_if = "Option::is_none")]
396+
pub default_model: Option<String>,
397+
}
398+
399+
impl From<UpsertSystemConfigsRequest> for services::system_configs::ports::SystemConfigs {
400+
fn from(req: UpsertSystemConfigsRequest) -> Self {
401+
services::system_configs::ports::SystemConfigs {
402+
default_model: req.default_model,
403+
}
404+
}
405+
}
406+
407+
/// System configs update request (partial)
408+
#[derive(Debug, Serialize, Deserialize, ToSchema)]
409+
pub struct UpdateSystemConfigsRequest {
410+
/// Default model identifier to use when not specified
411+
#[serde(skip_serializing_if = "Option::is_none")]
412+
pub default_model: Option<String>,
413+
}
414+
415+
impl From<UpdateSystemConfigsRequest> for services::system_configs::ports::PartialSystemConfigs {
416+
fn from(req: UpdateSystemConfigsRequest) -> Self {
417+
services::system_configs::ports::PartialSystemConfigs {
418+
default_model: req.default_model,
419+
}
420+
}
421+
}
422+
267423
/// File list response with pagination
268424
#[derive(Debug, Serialize, Deserialize, ToSchema)]
269425
pub struct FileListResponse {

crates/api/src/openapi.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ use utoipa::OpenApi;
2424
crate::routes::users::get_current_user,
2525
// Admin endpoints
2626
crate::routes::admin::list_users,
27+
crate::routes::admin::list_models,
28+
crate::routes::admin::batch_upsert_models,
29+
crate::routes::admin::delete_model,
30+
crate::routes::admin::upsert_system_configs,
31+
// Configs endpoints
32+
crate::routes::configs::get_system_configs,
2733
crate::routes::users::get_user_settings,
2834
crate::routes::users::update_user_settings_partially,
2935
crate::routes::users::update_user_settings,
@@ -47,6 +53,14 @@ use utoipa::OpenApi;
4753
crate::models::UserSettingsResponse,
4854
crate::models::UpdateUserSettingsPartiallyRequest,
4955
crate::models::UpdateUserSettingsRequest,
56+
// Model settings / model admin models
57+
crate::models::ModelResponse,
58+
crate::models::ModelListResponse,
59+
crate::models::BatchUpsertModelsRequest,
60+
crate::models::UpdateModelRequest,
61+
// System configs models
62+
crate::models::SystemConfigsResponse,
63+
crate::models::UpdateSystemConfigsRequest,
5064
// Attestation models
5165
crate::models::ApiGatewayAttestation,
5266
crate::models::ModelAttestation,
@@ -58,6 +72,7 @@ use utoipa::OpenApi;
5872
(name = "Auth", description = "OAuth authentication endpoints"),
5973
(name = "Users", description = "User profile management endpoints"),
6074
(name = "Admin", description = "Admin management endpoints"),
75+
(name = "Configs", description = "System configuration endpoints"),
6176
(name = "attestation", description = "Attestation reporting endpoints for TEE verification")
6277
)
6378
)]

0 commit comments

Comments
 (0)