feat: models

This commit is contained in:
Acbox
2026-01-23 18:53:20 +08:00
parent 0edaba4e74
commit c332ce7749
17 changed files with 2765 additions and 39 deletions
+17
View File
@@ -55,6 +55,23 @@ CREATE TABLE IF NOT EXISTS snapshots (
created_at TIMESTAMPTZ NOT NULL DEFAULT now() created_at TIMESTAMPTZ NOT NULL DEFAULT now()
); );
CREATE TABLE IF NOT EXISTS models (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
model_id TEXT NOT NULL,
name TEXT,
base_url TEXT NOT NULL,
api_key TEXT NOT NULL,
client_type TEXT NOT NULL,
dimensions INTEGER,
type TEXT NOT NULL DEFAULT 'chat',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT models_model_id_unique UNIQUE (model_id),
CONSTRAINT models_type_check CHECK (type IN ('chat', 'embedding')),
CONSTRAINT models_client_type_check CHECK (client_type IN ('openai', 'anthropic', 'google')),
CONSTRAINT models_dimensions_check CHECK (type != 'embedding' OR dimensions IS NOT NULL)
);
CREATE INDEX IF NOT EXISTS idx_snapshots_container_id ON snapshots(container_id); CREATE INDEX IF NOT EXISTS idx_snapshots_container_id ON snapshots(container_id);
CREATE INDEX IF NOT EXISTS idx_snapshots_parent_id ON snapshots(parent_snapshot_id); CREATE INDEX IF NOT EXISTS idx_snapshots_parent_id ON snapshots(parent_snapshot_id);
+71
View File
@@ -0,0 +1,71 @@
-- name: CreateModel :one
INSERT INTO models (model_id, name, base_url, api_key, client_type, dimensions, type)
VALUES (
sqlc.arg(model_id),
sqlc.arg(name),
sqlc.arg(base_url),
sqlc.arg(api_key),
sqlc.arg(client_type),
sqlc.arg(dimensions),
sqlc.arg(type)
)
RETURNING *;
-- name: GetModelByID :one
SELECT * FROM models WHERE id = sqlc.arg(id);
-- name: GetModelByModelID :one
SELECT * FROM models WHERE model_id = sqlc.arg(model_id);
-- name: ListModels :many
SELECT * FROM models
ORDER BY created_at DESC;
-- name: ListModelsByType :many
SELECT * FROM models
WHERE type = sqlc.arg(type)
ORDER BY created_at DESC;
-- name: ListModelsByClientType :many
SELECT * FROM models
WHERE client_type = sqlc.arg(client_type)
ORDER BY created_at DESC;
-- name: UpdateModel :one
UPDATE models
SET
name = sqlc.arg(name),
base_url = sqlc.arg(base_url),
api_key = sqlc.arg(api_key),
client_type = sqlc.arg(client_type),
dimensions = sqlc.arg(dimensions),
type = sqlc.arg(type),
updated_at = now()
WHERE id = sqlc.arg(id)
RETURNING *;
-- name: UpdateModelByModelID :one
UPDATE models
SET
name = sqlc.arg(name),
base_url = sqlc.arg(base_url),
api_key = sqlc.arg(api_key),
client_type = sqlc.arg(client_type),
dimensions = sqlc.arg(dimensions),
type = sqlc.arg(type),
updated_at = now()
WHERE model_id = sqlc.arg(model_id)
RETURNING *;
-- name: DeleteModel :exec
DELETE FROM models WHERE id = sqlc.arg(id);
-- name: DeleteModelByModelID :exec
DELETE FROM models WHERE model_id = sqlc.arg(model_id);
-- name: CountModels :one
SELECT COUNT(*) FROM models;
-- name: CountModelsByType :one
SELECT COUNT(*) FROM models WHERE type = sqlc.arg(type);
+504 -5
View File
@@ -610,6 +610,395 @@ const docTemplate = `{
} }
} }
} }
},
"/models": {
"get": {
"description": "Get a list of all configured models, optionally filtered by type or client type",
"tags": [
"models"
],
"summary": "List all models",
"parameters": [
{
"type": "string",
"description": "Model type (chat, embedding)",
"name": "type",
"in": "query"
},
{
"type": "string",
"description": "Client type (openai, anthropic, google)",
"name": "client_type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/models.GetResponse"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"post": {
"description": "Create a new model configuration",
"tags": [
"models"
],
"summary": "Create a new model",
"parameters": [
{
"description": "Model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.AddRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/models.AddResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/count": {
"get": {
"description": "Get the total count of models, optionally filtered by type",
"tags": [
"models"
],
"summary": "Get model count",
"parameters": [
{
"type": "string",
"description": "Model type (chat, embedding)",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.CountResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/model/{modelId}": {
"get": {
"description": "Get a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Get model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"put": {
"description": "Update a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Update model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
},
{
"description": "Updated model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.UpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Delete model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/{id}": {
"get": {
"description": "Get a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Get model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"put": {
"description": "Update a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Update model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Updated model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.UpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Delete model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
@@ -923,18 +1312,128 @@ const docTemplate = `{
"type": "string" "type": "string"
} }
} }
},
"models.AddRequest": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.AddResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"model_id": {
"type": "string"
}
}
},
"models.ClientType": {
"type": "string",
"enum": [
"openai",
"anthropic",
"google"
],
"x-enum-varnames": [
"ClientTypeOpenAI",
"ClientTypeAnthropic",
"ClientTypeGoogle"
]
},
"models.CountResponse": {
"type": "object",
"properties": {
"count": {
"type": "integer"
}
}
},
"models.GetResponse": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.UpdateRequest": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
} }
} }
}` }`
// SwaggerInfo holds exported Swagger Info so clients can modify it // SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{ var SwaggerInfo = &swag.Spec{
Version: "1.0", Version: "",
Host: "", Host: "",
BasePath: "/", BasePath: "",
Schemes: []string{"http"}, Schemes: []string{},
Title: "Memoh Go API", Title: "",
Description: "User-scoped filesystem API for containerd-backed data.", Description: "",
InfoInstanceName: "swagger", InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate, SwaggerTemplate: docTemplate,
LeftDelim: "{{", LeftDelim: "{{",
+500 -8
View File
@@ -1,15 +1,8 @@
{ {
"schemes": [
"http"
],
"swagger": "2.0", "swagger": "2.0",
"info": { "info": {
"description": "User-scoped filesystem API for containerd-backed data.", "contact": {}
"title": "Memoh Go API",
"contact": {},
"version": "1.0"
}, },
"basePath": "/",
"paths": { "paths": {
"/auth/login": { "/auth/login": {
"post": { "post": {
@@ -606,6 +599,395 @@
} }
} }
} }
},
"/models": {
"get": {
"description": "Get a list of all configured models, optionally filtered by type or client type",
"tags": [
"models"
],
"summary": "List all models",
"parameters": [
{
"type": "string",
"description": "Model type (chat, embedding)",
"name": "type",
"in": "query"
},
{
"type": "string",
"description": "Client type (openai, anthropic, google)",
"name": "client_type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/models.GetResponse"
}
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"post": {
"description": "Create a new model configuration",
"tags": [
"models"
],
"summary": "Create a new model",
"parameters": [
{
"description": "Model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.AddRequest"
}
}
],
"responses": {
"201": {
"description": "Created",
"schema": {
"$ref": "#/definitions/models.AddResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/count": {
"get": {
"description": "Get the total count of models, optionally filtered by type",
"tags": [
"models"
],
"summary": "Get model count",
"parameters": [
{
"type": "string",
"description": "Model type (chat, embedding)",
"name": "type",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.CountResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/model/{modelId}": {
"get": {
"description": "Get a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Get model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"put": {
"description": "Update a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Update model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
},
{
"description": "Updated model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.UpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a model configuration by its model_id field (e.g., gpt-4)",
"tags": [
"models"
],
"summary": "Delete model by model ID",
"parameters": [
{
"type": "string",
"description": "Model ID (e.g., gpt-4)",
"name": "modelId",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
},
"/models/{id}": {
"get": {
"description": "Get a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Get model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"put": {
"description": "Update a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Update model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Updated model configuration",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/models.UpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.GetResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
},
"delete": {
"description": "Delete a model configuration by its internal UUID",
"tags": [
"models"
],
"summary": "Delete model by internal ID",
"parameters": [
{
"type": "string",
"description": "Model internal ID (UUID)",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": "No Content"
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/handlers.ErrorResponse"
}
}
}
}
} }
}, },
"definitions": { "definitions": {
@@ -919,6 +1301,116 @@
"type": "string" "type": "string"
} }
} }
},
"models.AddRequest": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.AddResponse": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"model_id": {
"type": "string"
}
}
},
"models.ClientType": {
"type": "string",
"enum": [
"openai",
"anthropic",
"google"
],
"x-enum-varnames": [
"ClientTypeOpenAI",
"ClientTypeAnthropic",
"ClientTypeGoogle"
]
},
"models.CountResponse": {
"type": "object",
"properties": {
"count": {
"type": "integer"
}
}
},
"models.GetResponse": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
},
"models.UpdateRequest": {
"type": "object",
"properties": {
"api_key": {
"type": "string"
},
"base_url": {
"type": "string"
},
"client_type": {
"$ref": "#/definitions/models.ClientType"
},
"dimensions": {
"type": "integer"
},
"model_id": {
"type": "string"
},
"name": {
"type": "string"
},
"type": {
"type": "string"
}
}
} }
} }
} }
+331 -6
View File
@@ -1,4 +1,3 @@
basePath: /
definitions: definitions:
handlers.ApplyPatchRequest: handlers.ApplyPatchRequest:
properties: properties:
@@ -203,11 +202,81 @@ definitions:
memory_id: memory_id:
type: string type: string
type: object type: object
models.AddRequest:
properties:
api_key:
type: string
base_url:
type: string
client_type:
$ref: '#/definitions/models.ClientType'
dimensions:
type: integer
model_id:
type: string
name:
type: string
type:
type: string
type: object
models.AddResponse:
properties:
id:
type: string
model_id:
type: string
type: object
models.ClientType:
enum:
- openai
- anthropic
- google
type: string
x-enum-varnames:
- ClientTypeOpenAI
- ClientTypeAnthropic
- ClientTypeGoogle
models.CountResponse:
properties:
count:
type: integer
type: object
models.GetResponse:
properties:
api_key:
type: string
base_url:
type: string
client_type:
$ref: '#/definitions/models.ClientType'
dimensions:
type: integer
model_id:
type: string
name:
type: string
type:
type: string
type: object
models.UpdateRequest:
properties:
api_key:
type: string
base_url:
type: string
client_type:
$ref: '#/definitions/models.ClientType'
dimensions:
type: integer
model_id:
type: string
name:
type: string
type:
type: string
type: object
info: info:
contact: {} contact: {}
description: User-scoped filesystem API for containerd-backed data.
title: Memoh Go API
version: "1.0"
paths: paths:
/auth/login: /auth/login:
post: post:
@@ -599,6 +668,262 @@ paths:
summary: Update memory summary: Update memory
tags: tags:
- memory - memory
schemes: /models:
- http get:
description: Get a list of all configured models, optionally filtered by type
or client type
parameters:
- description: Model type (chat, embedding)
in: query
name: type
type: string
- description: Client type (openai, anthropic, google)
in: query
name: client_type
type: string
responses:
"200":
description: OK
schema:
items:
$ref: '#/definitions/models.GetResponse'
type: array
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: List all models
tags:
- models
post:
description: Create a new model configuration
parameters:
- description: Model configuration
in: body
name: payload
required: true
schema:
$ref: '#/definitions/models.AddRequest'
responses:
"201":
description: Created
schema:
$ref: '#/definitions/models.AddResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Create a new model
tags:
- models
/models/{id}:
delete:
description: Delete a model configuration by its internal UUID
parameters:
- description: Model internal ID (UUID)
in: path
name: id
required: true
type: string
responses:
"204":
description: No Content
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Delete model by internal ID
tags:
- models
get:
description: Get a model configuration by its internal UUID
parameters:
- description: Model internal ID (UUID)
in: path
name: id
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.GetResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Get model by internal ID
tags:
- models
put:
description: Update a model configuration by its internal UUID
parameters:
- description: Model internal ID (UUID)
in: path
name: id
required: true
type: string
- description: Updated model configuration
in: body
name: payload
required: true
schema:
$ref: '#/definitions/models.UpdateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.GetResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Update model by internal ID
tags:
- models
/models/count:
get:
description: Get the total count of models, optionally filtered by type
parameters:
- description: Model type (chat, embedding)
in: query
name: type
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.CountResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Get model count
tags:
- models
/models/model/{modelId}:
delete:
description: Delete a model configuration by its model_id field (e.g., gpt-4)
parameters:
- description: Model ID (e.g., gpt-4)
in: path
name: modelId
required: true
type: string
responses:
"204":
description: No Content
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Delete model by model ID
tags:
- models
get:
description: Get a model configuration by its model_id field (e.g., gpt-4)
parameters:
- description: Model ID (e.g., gpt-4)
in: path
name: modelId
required: true
type: string
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.GetResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Get model by model ID
tags:
- models
put:
description: Update a model configuration by its model_id field (e.g., gpt-4)
parameters:
- description: Model ID (e.g., gpt-4)
in: path
name: modelId
required: true
type: string
- description: Updated model configuration
in: body
name: payload
required: true
schema:
$ref: '#/definitions/models.UpdateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.GetResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/handlers.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/handlers.ErrorResponse'
summary: Update model by model ID
tags:
- models
swagger: "2.0" swagger: "2.0"
+7 -6
View File
@@ -7,7 +7,7 @@ require (
github.com/containerd/containerd/api v1.10.0 github.com/containerd/containerd/api v1.10.0
github.com/containerd/containerd/v2 v2.2.1 github.com/containerd/containerd/v2 v2.2.1
github.com/containerd/errdefs v1.0.0 github.com/containerd/errdefs v1.0.0
github.com/cyphar/filepath-securejoin v0.6.1 github.com/cyphar/filepath-securejoin v0.5.1
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.8.0 github.com/jackc/pgx/v5 v5.8.0
@@ -16,12 +16,12 @@ require (
github.com/opencontainers/runtime-spec v1.3.0 github.com/opencontainers/runtime-spec v1.3.0
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2
github.com/qdrant/go-client v1.16.2 github.com/qdrant/go-client v1.16.2
github.com/stretchr/testify v1.11.1
github.com/swaggo/swag v1.16.6 github.com/swaggo/swag v1.16.6
golang.org/x/crypto v0.47.0 golang.org/x/crypto v0.47.0
) )
require ( require (
cyphar.com/go-pathrs v0.2.2 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect github.com/Microsoft/hcsshim v0.14.0-rc.1 // indirect
@@ -86,8 +86,9 @@ require (
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect golang.org/x/time v0.14.0 // indirect
golang.org/x/tools v0.41.0 // indirect golang.org/x/tools v0.40.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba // indirect
google.golang.org/grpc v1.78.0 // indirect google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+10 -12
View File
@@ -1,6 +1,4 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cyphar.com/go-pathrs v0.2.2 h1:y9w7hxbkr3zEL78Fjzeg4HEhs2xNy+fbwHiHGJJY2Xo=
cyphar.com/go-pathrs v0.2.2/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@@ -41,8 +39,8 @@ github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRq
github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= github.com/cyphar/filepath-securejoin v0.5.1 h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/cyphar/filepath-securejoin v0.5.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -267,8 +265,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -280,15 +278,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba h1:UKgtfRM7Yh93Sya0Fo8ZzhDP4qBckrrxEr2oF5UIVb8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20251111163417-95abcf5c77ba/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -298,8 +296,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+2
View File
@@ -0,0 +1,2 @@
package chat
+13
View File
@@ -41,6 +41,19 @@ type LifecycleEvent struct {
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
} }
type Model struct {
ID pgtype.UUID `json:"id"`
ModelID string `json:"model_id"`
Name pgtype.Text `json:"name"`
BaseUrl string `json:"base_url"`
ApiKey string `json:"api_key"`
ClientType string `json:"client_type"`
Dimensions pgtype.Int4 `json:"dimensions"`
Type string `json:"type"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type Snapshot struct { type Snapshot struct {
ID string `json:"id"` ID string `json:"id"`
ContainerID string `json:"container_id"` ContainerID string `json:"container_id"`
+356
View File
@@ -0,0 +1,356 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: models.sql
package sqlc
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countModels = `-- name: CountModels :one
SELECT COUNT(*) FROM models
`
func (q *Queries) CountModels(ctx context.Context) (int64, error) {
row := q.db.QueryRow(ctx, countModels)
var count int64
err := row.Scan(&count)
return count, err
}
const countModelsByType = `-- name: CountModelsByType :one
SELECT COUNT(*) FROM models WHERE type = $1
`
func (q *Queries) CountModelsByType(ctx context.Context, type_ string) (int64, error) {
row := q.db.QueryRow(ctx, countModelsByType, type_)
var count int64
err := row.Scan(&count)
return count, err
}
const createModel = `-- name: CreateModel :one
INSERT INTO models (model_id, name, base_url, api_key, client_type, dimensions, type)
VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7
)
RETURNING id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at
`
type CreateModelParams struct {
ModelID string `json:"model_id"`
Name pgtype.Text `json:"name"`
BaseUrl string `json:"base_url"`
ApiKey string `json:"api_key"`
ClientType string `json:"client_type"`
Dimensions pgtype.Int4 `json:"dimensions"`
Type string `json:"type"`
}
func (q *Queries) CreateModel(ctx context.Context, arg CreateModelParams) (Model, error) {
row := q.db.QueryRow(ctx, createModel,
arg.ModelID,
arg.Name,
arg.BaseUrl,
arg.ApiKey,
arg.ClientType,
arg.Dimensions,
arg.Type,
)
var i Model
err := row.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteModel = `-- name: DeleteModel :exec
DELETE FROM models WHERE id = $1
`
func (q *Queries) DeleteModel(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteModel, id)
return err
}
const deleteModelByModelID = `-- name: DeleteModelByModelID :exec
DELETE FROM models WHERE model_id = $1
`
func (q *Queries) DeleteModelByModelID(ctx context.Context, modelID string) error {
_, err := q.db.Exec(ctx, deleteModelByModelID, modelID)
return err
}
const getModelByID = `-- name: GetModelByID :one
SELECT id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at FROM models WHERE id = $1
`
func (q *Queries) GetModelByID(ctx context.Context, id pgtype.UUID) (Model, error) {
row := q.db.QueryRow(ctx, getModelByID, id)
var i Model
err := row.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const getModelByModelID = `-- name: GetModelByModelID :one
SELECT id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at FROM models WHERE model_id = $1
`
func (q *Queries) GetModelByModelID(ctx context.Context, modelID string) (Model, error) {
row := q.db.QueryRow(ctx, getModelByModelID, modelID)
var i Model
err := row.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const listModels = `-- name: ListModels :many
SELECT id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at FROM models
ORDER BY created_at DESC
`
func (q *Queries) ListModels(ctx context.Context) ([]Model, error) {
rows, err := q.db.Query(ctx, listModels)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Model
for rows.Next() {
var i Model
if err := rows.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listModelsByClientType = `-- name: ListModelsByClientType :many
SELECT id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at FROM models
WHERE client_type = $1
ORDER BY created_at DESC
`
func (q *Queries) ListModelsByClientType(ctx context.Context, clientType string) ([]Model, error) {
rows, err := q.db.Query(ctx, listModelsByClientType, clientType)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Model
for rows.Next() {
var i Model
if err := rows.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listModelsByType = `-- name: ListModelsByType :many
SELECT id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at FROM models
WHERE type = $1
ORDER BY created_at DESC
`
func (q *Queries) ListModelsByType(ctx context.Context, type_ string) ([]Model, error) {
rows, err := q.db.Query(ctx, listModelsByType, type_)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Model
for rows.Next() {
var i Model
if err := rows.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateModel = `-- name: UpdateModel :one
UPDATE models
SET
name = $1,
base_url = $2,
api_key = $3,
client_type = $4,
dimensions = $5,
type = $6,
updated_at = now()
WHERE id = $7
RETURNING id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at
`
type UpdateModelParams struct {
Name pgtype.Text `json:"name"`
BaseUrl string `json:"base_url"`
ApiKey string `json:"api_key"`
ClientType string `json:"client_type"`
Dimensions pgtype.Int4 `json:"dimensions"`
Type string `json:"type"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) UpdateModel(ctx context.Context, arg UpdateModelParams) (Model, error) {
row := q.db.QueryRow(ctx, updateModel,
arg.Name,
arg.BaseUrl,
arg.ApiKey,
arg.ClientType,
arg.Dimensions,
arg.Type,
arg.ID,
)
var i Model
err := row.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const updateModelByModelID = `-- name: UpdateModelByModelID :one
UPDATE models
SET
name = $1,
base_url = $2,
api_key = $3,
client_type = $4,
dimensions = $5,
type = $6,
updated_at = now()
WHERE model_id = $7
RETURNING id, model_id, name, base_url, api_key, client_type, dimensions, type, created_at, updated_at
`
type UpdateModelByModelIDParams struct {
Name pgtype.Text `json:"name"`
BaseUrl string `json:"base_url"`
ApiKey string `json:"api_key"`
ClientType string `json:"client_type"`
Dimensions pgtype.Int4 `json:"dimensions"`
Type string `json:"type"`
ModelID string `json:"model_id"`
}
func (q *Queries) UpdateModelByModelID(ctx context.Context, arg UpdateModelByModelIDParams) (Model, error) {
row := q.db.QueryRow(ctx, updateModelByModelID,
arg.Name,
arg.BaseUrl,
arg.ApiKey,
arg.ClientType,
arg.Dimensions,
arg.Type,
arg.ModelID,
)
var i Model
err := row.Scan(
&i.ID,
&i.ModelID,
&i.Name,
&i.BaseUrl,
&i.ApiKey,
&i.ClientType,
&i.Dimensions,
&i.Type,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
+258
View File
@@ -0,0 +1,258 @@
package handlers
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/memohai/memoh/internal/models"
)
type ModelsHandler struct {
service *models.Service
}
func NewModelsHandler(service *models.Service) *ModelsHandler {
return &ModelsHandler{service: service}
}
func (h *ModelsHandler) Register(e *echo.Echo) {
group := e.Group("/models")
group.POST("", h.Create)
group.GET("", h.List)
group.GET("/:id", h.GetByID)
group.GET("/model/:modelId", h.GetByModelID)
group.PUT("/:id", h.UpdateByID)
group.PUT("/model/:modelId", h.UpdateByModelID)
group.DELETE("/:id", h.DeleteByID)
group.DELETE("/model/:modelId", h.DeleteByModelID)
group.GET("/count", h.Count)
}
// Create godoc
// @Summary Create a new model
// @Description Create a new model configuration
// @Tags models
// @Param payload body models.AddRequest true "Model configuration"
// @Success 201 {object} models.AddResponse
// @Failure 400 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models [post]
func (h *ModelsHandler) Create(c echo.Context) error {
var req models.AddRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
resp, err := h.service.Create(c.Request().Context(), req)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusCreated, resp)
}
// List godoc
// @Summary List all models
// @Description Get a list of all configured models, optionally filtered by type or client type
// @Tags models
// @Param type query string false "Model type (chat, embedding)"
// @Param client_type query string false "Client type (openai, anthropic, google)"
// @Success 200 {array} models.GetResponse
// @Failure 400 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models [get]
func (h *ModelsHandler) List(c echo.Context) error {
modelType := c.QueryParam("type")
clientType := c.QueryParam("client_type")
var resp []models.GetResponse
var err error
if modelType != "" {
resp, err = h.service.ListByType(c.Request().Context(), models.ModelType(modelType))
} else if clientType != "" {
resp, err = h.service.ListByClientType(c.Request().Context(), models.ClientType(clientType))
} else {
resp, err = h.service.List(c.Request().Context())
}
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, resp)
}
// GetByID godoc
// @Summary Get model by internal ID
// @Description Get a model configuration by its internal UUID
// @Tags models
// @Param id path string true "Model internal ID (UUID)"
// @Success 200 {object} models.GetResponse
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/{id} [get]
func (h *ModelsHandler) GetByID(c echo.Context) error {
id := c.Param("id")
if id == "" {
return echo.NewHTTPError(http.StatusBadRequest, "id is required")
}
resp, err := h.service.GetByID(c.Request().Context(), id)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSON(http.StatusOK, resp)
}
// GetByModelID godoc
// @Summary Get model by model ID
// @Description Get a model configuration by its model_id field (e.g., gpt-4)
// @Tags models
// @Param modelId path string true "Model ID (e.g., gpt-4)"
// @Success 200 {object} models.GetResponse
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/model/{modelId} [get]
func (h *ModelsHandler) GetByModelID(c echo.Context) error {
modelID := c.Param("modelId")
if modelID == "" {
return echo.NewHTTPError(http.StatusBadRequest, "modelId is required")
}
resp, err := h.service.GetByModelID(c.Request().Context(), modelID)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSON(http.StatusOK, resp)
}
// UpdateByID godoc
// @Summary Update model by internal ID
// @Description Update a model configuration by its internal UUID
// @Tags models
// @Param id path string true "Model internal ID (UUID)"
// @Param payload body models.UpdateRequest true "Updated model configuration"
// @Success 200 {object} models.GetResponse
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/{id} [put]
func (h *ModelsHandler) UpdateByID(c echo.Context) error {
id := c.Param("id")
if id == "" {
return echo.NewHTTPError(http.StatusBadRequest, "id is required")
}
var req models.UpdateRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
resp, err := h.service.UpdateByID(c.Request().Context(), id, req)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, resp)
}
// UpdateByModelID godoc
// @Summary Update model by model ID
// @Description Update a model configuration by its model_id field (e.g., gpt-4)
// @Tags models
// @Param modelId path string true "Model ID (e.g., gpt-4)"
// @Param payload body models.UpdateRequest true "Updated model configuration"
// @Success 200 {object} models.GetResponse
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/model/{modelId} [put]
func (h *ModelsHandler) UpdateByModelID(c echo.Context) error {
modelID := c.Param("modelId")
if modelID == "" {
return echo.NewHTTPError(http.StatusBadRequest, "modelId is required")
}
var req models.UpdateRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
resp, err := h.service.UpdateByModelID(c.Request().Context(), modelID, req)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, resp)
}
// DeleteByID godoc
// @Summary Delete model by internal ID
// @Description Delete a model configuration by its internal UUID
// @Tags models
// @Param id path string true "Model internal ID (UUID)"
// @Success 204 "No Content"
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/{id} [delete]
func (h *ModelsHandler) DeleteByID(c echo.Context) error {
id := c.Param("id")
if id == "" {
return echo.NewHTTPError(http.StatusBadRequest, "id is required")
}
if err := h.service.DeleteByID(c.Request().Context(), id); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.NoContent(http.StatusNoContent)
}
// DeleteByModelID godoc
// @Summary Delete model by model ID
// @Description Delete a model configuration by its model_id field (e.g., gpt-4)
// @Tags models
// @Param modelId path string true "Model ID (e.g., gpt-4)"
// @Success 204 "No Content"
// @Failure 400 {object} ErrorResponse
// @Failure 404 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/model/{modelId} [delete]
func (h *ModelsHandler) DeleteByModelID(c echo.Context) error {
modelID := c.Param("modelId")
if modelID == "" {
return echo.NewHTTPError(http.StatusBadRequest, "modelId is required")
}
if err := h.service.DeleteByModelID(c.Request().Context(), modelID); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.NoContent(http.StatusNoContent)
}
// Count godoc
// @Summary Get model count
// @Description Get the total count of models, optionally filtered by type
// @Tags models
// @Param type query string false "Model type (chat, embedding)"
// @Success 200 {object} models.CountResponse
// @Failure 400 {object} ErrorResponse
// @Failure 500 {object} ErrorResponse
// @Router /models/count [get]
func (h *ModelsHandler) Count(c echo.Context) error {
modelType := c.QueryParam("type")
var count int64
var err error
if modelType != "" {
count, err = h.service.CountByType(c.Request().Context(), models.ModelType(modelType))
} else {
count, err = h.service.Count(c.Request().Context())
}
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, models.CountResponse{Count: count})
}
+1 -1
View File
@@ -8,7 +8,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
//go:generate go run github.com/swaggo/swag/cmd/swag@latest init -g ../../cmd/agent/docs.go -o ../../docs --parseDependency --parseInternal //go:generate go run github.com/swaggo/swag/cmd/swag@latest init -g swagger.go -o ../../docs --parseDependency --parseInternal
var ( var (
swaggerSpec []byte swaggerSpec []byte
+302
View File
@@ -0,0 +1,302 @@
package models
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/memohai/memoh/internal/db/sqlc"
)
// Service provides CRUD operations for models
type Service struct {
queries *sqlc.Queries
}
// NewService creates a new models service
func NewService(queries *sqlc.Queries) *Service {
return &Service{
queries: queries,
}
}
// Create adds a new model to the database
func (s *Service) Create(ctx context.Context, req AddRequest) (AddResponse, error) {
model := Model(req)
if err := model.Validate(); err != nil {
return AddResponse{}, fmt.Errorf("validation failed: %w", err)
}
// Convert to sqlc params
params := sqlc.CreateModelParams{
ModelID: model.ModelID,
BaseUrl: model.BaseURL,
ApiKey: model.APIKey,
ClientType: string(model.ClientType),
Type: string(model.Type),
}
// Handle optional name field
if model.Name != "" {
params.Name = pgtype.Text{String: model.Name, Valid: true}
}
// Handle optional dimensions field (only for embedding models)
if model.Type == ModelTypeEmbedding && model.Dimensions > 0 {
params.Dimensions = pgtype.Int4{Int32: int32(model.Dimensions), Valid: true}
}
created, err := s.queries.CreateModel(ctx, params)
if err != nil {
return AddResponse{}, fmt.Errorf("failed to create model: %w", err)
}
// Convert pgtype.UUID to string
var idStr string
if created.ID.Valid {
id, err := uuid.FromBytes(created.ID.Bytes[:])
if err != nil {
return AddResponse{}, fmt.Errorf("failed to convert UUID: %w", err)
}
idStr = id.String()
}
return AddResponse{
ID: idStr,
ModelID: created.ModelID,
}, nil
}
// GetByID retrieves a model by its internal UUID
func (s *Service) GetByID(ctx context.Context, id string) (GetResponse, error) {
uuid, err := parseUUID(id)
if err != nil {
return GetResponse{}, fmt.Errorf("invalid ID: %w", err)
}
dbModel, err := s.queries.GetModelByID(ctx, uuid)
if err != nil {
return GetResponse{}, fmt.Errorf("failed to get model: %w", err)
}
return convertToGetResponse(dbModel), nil
}
// GetByModelID retrieves a model by its model_id field
func (s *Service) GetByModelID(ctx context.Context, modelID string) (GetResponse, error) {
if modelID == "" {
return GetResponse{}, fmt.Errorf("model_id is required")
}
dbModel, err := s.queries.GetModelByModelID(ctx, modelID)
if err != nil {
return GetResponse{}, fmt.Errorf("failed to get model: %w", err)
}
return convertToGetResponse(dbModel), nil
}
// List returns all models
func (s *Service) List(ctx context.Context) ([]GetResponse, error) {
dbModels, err := s.queries.ListModels(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list models: %w", err)
}
return convertToGetResponseList(dbModels), nil
}
// ListByType returns models filtered by type (chat or embedding)
func (s *Service) ListByType(ctx context.Context, modelType ModelType) ([]GetResponse, error) {
if modelType != ModelTypeChat && modelType != ModelTypeEmbedding {
return nil, fmt.Errorf("invalid model type: %s", modelType)
}
dbModels, err := s.queries.ListModelsByType(ctx, string(modelType))
if err != nil {
return nil, fmt.Errorf("failed to list models by type: %w", err)
}
return convertToGetResponseList(dbModels), nil
}
// ListByClientType returns models filtered by client type
func (s *Service) ListByClientType(ctx context.Context, clientType ClientType) ([]GetResponse, error) {
if clientType != ClientTypeOpenAI && clientType != ClientTypeAnthropic && clientType != ClientTypeGoogle {
return nil, fmt.Errorf("invalid client type: %s", clientType)
}
dbModels, err := s.queries.ListModelsByClientType(ctx, string(clientType))
if err != nil {
return nil, fmt.Errorf("failed to list models by client type: %w", err)
}
return convertToGetResponseList(dbModels), nil
}
// UpdateByID updates a model by its internal UUID
func (s *Service) UpdateByID(ctx context.Context, id string, req UpdateRequest) (GetResponse, error) {
uuid, err := parseUUID(id)
if err != nil {
return GetResponse{}, fmt.Errorf("invalid ID: %w", err)
}
model := Model(req)
if err := model.Validate(); err != nil {
return GetResponse{}, fmt.Errorf("validation failed: %w", err)
}
params := sqlc.UpdateModelParams{
ID: uuid,
BaseUrl: model.BaseURL,
ApiKey: model.APIKey,
ClientType: string(model.ClientType),
Type: string(model.Type),
}
if model.Name != "" {
params.Name = pgtype.Text{String: model.Name, Valid: true}
}
if model.Type == ModelTypeEmbedding && model.Dimensions > 0 {
params.Dimensions = pgtype.Int4{Int32: int32(model.Dimensions), Valid: true}
}
updated, err := s.queries.UpdateModel(ctx, params)
if err != nil {
return GetResponse{}, fmt.Errorf("failed to update model: %w", err)
}
return convertToGetResponse(updated), nil
}
// UpdateByModelID updates a model by its model_id field
func (s *Service) UpdateByModelID(ctx context.Context, modelID string, req UpdateRequest) (GetResponse, error) {
if modelID == "" {
return GetResponse{}, fmt.Errorf("model_id is required")
}
model := Model(req)
if err := model.Validate(); err != nil {
return GetResponse{}, fmt.Errorf("validation failed: %w", err)
}
params := sqlc.UpdateModelByModelIDParams{
ModelID: modelID,
BaseUrl: model.BaseURL,
ApiKey: model.APIKey,
ClientType: string(model.ClientType),
Type: string(model.Type),
}
if model.Name != "" {
params.Name = pgtype.Text{String: model.Name, Valid: true}
}
if model.Type == ModelTypeEmbedding && model.Dimensions > 0 {
params.Dimensions = pgtype.Int4{Int32: int32(model.Dimensions), Valid: true}
}
updated, err := s.queries.UpdateModelByModelID(ctx, params)
if err != nil {
return GetResponse{}, fmt.Errorf("failed to update model: %w", err)
}
return convertToGetResponse(updated), nil
}
// DeleteByID deletes a model by its internal UUID
func (s *Service) DeleteByID(ctx context.Context, id string) error {
uuid, err := parseUUID(id)
if err != nil {
return fmt.Errorf("invalid ID: %w", err)
}
if err := s.queries.DeleteModel(ctx, uuid); err != nil {
return fmt.Errorf("failed to delete model: %w", err)
}
return nil
}
// DeleteByModelID deletes a model by its model_id field
func (s *Service) DeleteByModelID(ctx context.Context, modelID string) error {
if modelID == "" {
return fmt.Errorf("model_id is required")
}
if err := s.queries.DeleteModelByModelID(ctx, modelID); err != nil {
return fmt.Errorf("failed to delete model: %w", err)
}
return nil
}
// Count returns the total number of models
func (s *Service) Count(ctx context.Context) (int64, error) {
count, err := s.queries.CountModels(ctx)
if err != nil {
return 0, fmt.Errorf("failed to count models: %w", err)
}
return count, nil
}
// CountByType returns the number of models of a specific type
func (s *Service) CountByType(ctx context.Context, modelType ModelType) (int64, error) {
if modelType != ModelTypeChat && modelType != ModelTypeEmbedding {
return 0, fmt.Errorf("invalid model type: %s", modelType)
}
count, err := s.queries.CountModelsByType(ctx, string(modelType))
if err != nil {
return 0, fmt.Errorf("failed to count models by type: %w", err)
}
return count, nil
}
// Helper functions
func parseUUID(id string) (pgtype.UUID, error) {
parsed, err := uuid.Parse(id)
if err != nil {
return pgtype.UUID{}, fmt.Errorf("invalid UUID format: %w", err)
}
var pgUUID pgtype.UUID
copy(pgUUID.Bytes[:], parsed[:])
pgUUID.Valid = true
return pgUUID, nil
}
func convertToGetResponse(dbModel sqlc.Model) GetResponse {
resp := GetResponse{
ModelId: dbModel.ModelID,
Model: Model{
ModelID: dbModel.ModelID,
BaseURL: dbModel.BaseUrl,
APIKey: dbModel.ApiKey,
ClientType: ClientType(dbModel.ClientType),
Type: ModelType(dbModel.Type),
},
}
if dbModel.Name.Valid {
resp.Model.Name = dbModel.Name.String
}
if dbModel.Dimensions.Valid {
resp.Model.Dimensions = int(dbModel.Dimensions.Int32)
}
return resp
}
func convertToGetResponseList(dbModels []sqlc.Model) []GetResponse {
responses := make([]GetResponse, 0, len(dbModels))
for _, dbModel := range dbModels {
responses = append(responses, convertToGetResponse(dbModel))
}
return responses
}
+297
View File
@@ -0,0 +1,297 @@
package models_test
import (
"testing"
"github.com/memohai/memoh/internal/models"
"github.com/stretchr/testify/assert"
)
// This is an example test file demonstrating how to use the models service
// Actual tests would require database setup and mocking
func ExampleService_Create() {
// Example usage - in real code, you would initialize with actual database connection
// service := models.NewService(queries)
// ctx := context.Background()
// req := models.AddRequest{
// ModelID: "gpt-4",
// Name: "GPT-4",
// BaseURL: "https://api.openai.com/v1",
// APIKey: "sk-...",
// ClientType: models.ClientTypeOpenAI,
// Type: models.ModelTypeChat,
// }
// resp, err := service.Create(ctx, req)
// if err != nil {
// // handle error
// }
// fmt.Printf("Created model with ID: %s\n", resp.ID)
}
func ExampleService_GetByModelID() {
// Example usage
// service := models.NewService(queries)
// ctx := context.Background()
// resp, err := service.GetByModelID(ctx, "gpt-4")
// if err != nil {
// // handle error
// }
// fmt.Printf("Model: %+v\n", resp.Model)
}
func ExampleService_List() {
// Example usage
// service := models.NewService(queries)
// ctx := context.Background()
// models, err := service.List(ctx)
// if err != nil {
// // handle error
// }
// for _, model := range models {
// fmt.Printf("Model ID: %s, Type: %s\n", model.ModelID, model.Type)
// }
}
func ExampleService_ListByType() {
// Example usage
// service := models.NewService(queries)
// ctx := context.Background()
// chatModels, err := service.ListByType(ctx, models.ModelTypeChat)
// if err != nil {
// // handle error
// }
// fmt.Printf("Found %d chat models\n", len(chatModels))
}
func ExampleService_UpdateByModelID() {
// Example usage
// service := models.NewService(queries)
// ctx := context.Background()
// req := models.UpdateRequest{
// ModelID: "gpt-4",
// Name: "GPT-4 Turbo",
// BaseURL: "https://api.openai.com/v1",
// APIKey: "sk-...",
// ClientType: models.ClientTypeOpenAI,
// Type: models.ModelTypeChat,
// }
// resp, err := service.UpdateByModelID(ctx, "gpt-4", req)
// if err != nil {
// // handle error
// }
// fmt.Printf("Updated model: %s\n", resp.ModelId)
}
func ExampleService_DeleteByModelID() {
// Example usage
// service := models.NewService(queries)
// ctx := context.Background()
// err := service.DeleteByModelID(ctx, "gpt-4")
// if err != nil {
// // handle error
// }
// fmt.Println("Model deleted successfully")
}
func TestModel_Validate(t *testing.T) {
tests := []struct {
name string
model models.Model
wantErr bool
}{
{
name: "valid chat model",
model: models.Model{
ModelID: "gpt-4",
Name: "GPT-4",
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeChat,
},
wantErr: false,
},
{
name: "valid embedding model",
model: models.Model{
ModelID: "text-embedding-ada-002",
Name: "Ada Embeddings",
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeEmbedding,
Dimensions: 1536,
},
wantErr: false,
},
{
name: "missing model_id",
model: models.Model{
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeChat,
},
wantErr: true,
},
{
name: "missing base_url",
model: models.Model{
ModelID: "gpt-4",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeChat,
},
wantErr: true,
},
{
name: "missing api_key",
model: models.Model{
ModelID: "gpt-4",
BaseURL: "https://api.openai.com/v1",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeChat,
},
wantErr: true,
},
{
name: "invalid client type",
model: models.Model{
ModelID: "gpt-4",
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: "invalid",
Type: models.ModelTypeChat,
},
wantErr: true,
},
{
name: "invalid model type",
model: models.Model{
ModelID: "gpt-4",
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: "invalid",
},
wantErr: true,
},
{
name: "embedding model missing dimensions",
model: models.Model{
ModelID: "text-embedding-ada-002",
BaseURL: "https://api.openai.com/v1",
APIKey: "sk-test",
ClientType: models.ClientTypeOpenAI,
Type: models.ModelTypeEmbedding,
Dimensions: 0,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.model.Validate()
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestModelTypes(t *testing.T) {
t.Run("ModelType constants", func(t *testing.T) {
assert.Equal(t, models.ModelType("chat"), models.ModelTypeChat)
assert.Equal(t, models.ModelType("embedding"), models.ModelTypeEmbedding)
})
t.Run("ClientType constants", func(t *testing.T) {
assert.Equal(t, models.ClientType("openai"), models.ClientTypeOpenAI)
assert.Equal(t, models.ClientType("anthropic"), models.ClientTypeAnthropic)
assert.Equal(t, models.ClientType("google"), models.ClientTypeGoogle)
})
}
// Integration test example (requires actual database)
// func TestService_Integration(t *testing.T) {
// if testing.Short() {
// t.Skip("Skipping integration test")
// }
//
// ctx := context.Background()
//
// // Setup database connection
// pool, err := db.Open(ctx, config.PostgresConfig{
// Host: "localhost",
// Port: 5432,
// User: "test",
// Password: "test",
// Database: "test_db",
// SSLMode: "disable",
// })
// require.NoError(t, err)
// defer pool.Close()
//
// queries := sqlc.New(pool)
// service := models.NewService(queries)
//
// // Test Create
// createReq := models.AddRequest{
// ModelID: "test-gpt-4",
// Name: "Test GPT-4",
// BaseURL: "https://api.openai.com/v1",
// APIKey: "sk-test",
// ClientType: models.ClientTypeOpenAI,
// Type: models.ModelTypeChat,
// }
// createResp, err := service.Create(ctx, createReq)
// require.NoError(t, err)
// assert.NotEmpty(t, createResp.ID)
// assert.Equal(t, "test-gpt-4", createResp.ModelID)
//
// // Test GetByModelID
// getResp, err := service.GetByModelID(ctx, "test-gpt-4")
// require.NoError(t, err)
// assert.Equal(t, "test-gpt-4", getResp.ModelID)
// assert.Equal(t, "Test GPT-4", getResp.Name)
//
// // Test List
// models, err := service.List(ctx)
// require.NoError(t, err)
// assert.NotEmpty(t, models)
//
// // Test Update
// updateReq := models.UpdateRequest{
// ModelID: "test-gpt-4",
// Name: "Updated GPT-4",
// BaseURL: "https://api.openai.com/v1",
// APIKey: "sk-test-updated",
// ClientType: models.ClientTypeOpenAI,
// Type: models.ModelTypeChat,
// }
// updateResp, err := service.UpdateByModelID(ctx, "test-gpt-4", updateReq)
// require.NoError(t, err)
// assert.Equal(t, "Updated GPT-4", updateResp.Name)
//
// // Test Count
// count, err := service.Count(ctx)
// require.NoError(t, err)
// assert.Greater(t, count, int64(0))
//
// // Test Delete
// err = service.DeleteByModelID(ctx, "test-gpt-4")
// require.NoError(t, err)
// }
+91
View File
@@ -0,0 +1,91 @@
package models
import (
"errors"
)
type ModelType string
const (
ModelTypeChat = "chat"
ModelTypeEmbedding = "embedding"
)
type ClientType string
const (
ClientTypeOpenAI ClientType = "openai"
ClientTypeAnthropic ClientType = "anthropic"
ClientTypeGoogle ClientType = "google"
)
type Model struct {
ModelID string `json:"model_id"`
Name string `json:"name"`
BaseURL string `json:"base_url"`
APIKey string `json:"api_key"`
ClientType ClientType `json:"client_type"`
Type ModelType `json:"type"`
Dimensions int `json:"dimensions"`
}
func (m *Model) Validate() error {
if m.ModelID == "" {
return errors.New("model ID is required")
}
if m.BaseURL == "" {
return errors.New("base URL is required")
}
if m.APIKey == "" {
return errors.New("API key is required")
}
if m.ClientType == "" {
return errors.New("client type is required")
}
if m.Type != ModelTypeChat && m.Type != ModelTypeEmbedding {
return errors.New("invalid model type")
}
if m.ClientType != ClientTypeOpenAI && m.ClientType != ClientTypeAnthropic && m.ClientType != ClientTypeGoogle {
return errors.New("invalid client type")
}
if m.Type == ModelTypeEmbedding && m.Dimensions <= 0 {
return errors.New("dimensions must be greater than 0")
}
return nil
}
type AddRequest Model
type AddResponse struct {
ID string `json:"id"`
ModelID string `json:"model_id"`
}
type GetRequest struct {
ID string `json:"id"`
}
type GetResponse struct {
ModelId string `json:"model_id"`
Model
}
type UpdateRequest Model
type ListRequest struct {
Type ModelType `json:"type,omitempty"`
ClientType ClientType `json:"client_type,omitempty"`
}
type DeleteRequest struct {
ID string `json:"id,omitempty"`
ModelID string `json:"model_id,omitempty"`
}
type DeleteResponse struct {
Message string `json:"message"`
}
type CountResponse struct {
Count int64 `json:"count"`
}
+2 -1
View File
@@ -37,7 +37,8 @@
"vue-eslint-parser": "^10.2.0" "vue-eslint-parser": "^10.2.0"
}, },
"dependencies": { "dependencies": {
"dotenv": "^17.2.3" "dotenv": "^17.2.3",
"drizzle-kit": "^0.31.8"
}, },
"pnpm": { "pnpm": {
"peerDependencyRules": { "peerDependencyRules": {
+3
View File
@@ -11,6 +11,9 @@ importers:
dotenv: dotenv:
specifier: ^17.2.3 specifier: ^17.2.3
version: 17.2.3 version: 17.2.3
drizzle-kit:
specifier: ^0.31.8
version: 0.31.8
devDependencies: devDependencies:
'@types/node': '@types/node':
specifier: ^25.0.3 specifier: ^25.0.3