@use-solace/openllm 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/elysia.ts ADDED
@@ -0,0 +1,99 @@
1
+ import type { Elysia } from "elysia";
2
+ import type {
3
+ APIConfig,
4
+ InferenceBackend,
5
+ InferenceRequest,
6
+ ModelRegistryEntry,
7
+ ModelRegistryImpl,
8
+ RegisterModelRequest,
9
+ } from "./types.js";
10
+ import { createOpenLLMClient } from "./client.js";
11
+
12
+ export function openllm(config: APIConfig = {}) {
13
+ const plugin = (app: Elysia) => {
14
+ const enginePort = config.engine ?? 8080;
15
+ const prefix = config.prefix ?? "/openllm";
16
+ const enableRouter = config.modelrouter ?? false;
17
+ const registry = config.registry as ModelRegistryImpl | undefined;
18
+
19
+ const client = createOpenLLMClient({ engine: enginePort });
20
+
21
+ app.get(`${prefix}/health`, async () => {
22
+ return await client.health();
23
+ });
24
+
25
+ app.get(`${prefix}/models`, async () => {
26
+ const result = await client.listModels();
27
+ return result.models;
28
+ });
29
+
30
+ app.post(`${prefix}/models/register`, async ({ body }) => {
31
+ const req = body as RegisterModelRequest;
32
+ return await client.registerModel(req);
33
+ });
34
+
35
+ app.post(`${prefix}/models/load`, async ({ body }) => {
36
+ const req = body as { model_id: string };
37
+ return await client.loadModel(req);
38
+ });
39
+
40
+ app.post(`${prefix}/models/unload/:modelId`, async ({ params }) => {
41
+ const modelId = params.modelId as string;
42
+ return await client.unloadModel(modelId);
43
+ });
44
+
45
+ app.post(`${prefix}/inference`, async ({ body }) => {
46
+ const req = body as InferenceRequest;
47
+ return await client.inference(req);
48
+ });
49
+
50
+ if (enableRouter && registry) {
51
+ app.post(`${prefix}/router/chat`, async ({ body }) => {
52
+ const request = body as {
53
+ prompt: string;
54
+ options?: {
55
+ model?: string;
56
+ latency?: string;
57
+ inference?: string;
58
+ minContext?: number;
59
+ max_tokens?: number;
60
+ temperature?: number;
61
+ };
62
+ };
63
+ const options = request.options ?? {};
64
+
65
+ let model: ModelRegistryEntry | undefined;
66
+ if (options.model) {
67
+ model = registry.get(options.model);
68
+ if (!model) {
69
+ throw new Error(`Model '${options.model}' not found in registry`);
70
+ }
71
+ } else {
72
+ model = registry.findOne({
73
+ capability: "chat",
74
+ latency: options.latency as any,
75
+ inference: options.inference as InferenceBackend,
76
+ minContext: options.minContext,
77
+ });
78
+
79
+ if (!model) {
80
+ throw new Error(
81
+ "No suitable model found for the given constraints",
82
+ );
83
+ }
84
+ }
85
+
86
+ return await client.inference({
87
+ model_id: model.id,
88
+ prompt: request.prompt,
89
+ max_tokens: options.max_tokens,
90
+ temperature: options.temperature,
91
+ });
92
+ });
93
+ }
94
+
95
+ return app;
96
+ };
97
+
98
+ return plugin;
99
+ }
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ export type {
2
+ InferenceBackend,
3
+ ModelCapability,
4
+ LatencyProfile,
5
+ ModelRegistryEntry,
6
+ RegistryEntryInput,
7
+ ModelRegistryConfig,
8
+ HealthResponse,
9
+ ModelListResponse,
10
+ RegisterModelResponse,
11
+ LoadModelRequest,
12
+ LoadModelResponse,
13
+ UnloadModelResponse,
14
+ InferenceRequest,
15
+ InferenceResponse,
16
+ StreamToken,
17
+ StreamCallback,
18
+ StreamCompleteCallback,
19
+ StreamErrorCallback,
20
+ StreamOptions,
21
+ OpenLLMConfig,
22
+ FindModelOptions,
23
+ APIConfig,
24
+ OpenLLMError,
25
+ ModelNotFoundError,
26
+ ModelNotLoadedError,
27
+ InferenceError,
28
+ ModelRegistryInstance,
29
+ ModelRegistryImpl,
30
+ } from "./types.js";
31
+
32
+ export {
33
+ ModelRegistry,
34
+ } from "./registry.js";
35
+
36
+ export type { ModelRegistry as ModelRegistryType } from "./registry.js";
37
+
38
+ export {
39
+ OpenLLMClient,
40
+ createOpenLLMClient,
41
+ } from "./client.js";
42
+
43
+ export { openllm } from "./elysia.js";
44
+
45
+ export const openllmAPI = {
46
+ start: (config: import("./types.js").APIConfig = {}) => {
47
+ const port = config.engine ?? 4292;
48
+ return {
49
+ start: (apiPort: number) => {
50
+ console.log(`Starting OpenLLM API on port ${apiPort}, connected to engine on port ${port}`);
51
+ console.log("Note: This is a client library. To start the actual server, use the Elysia plugin.");
52
+ },
53
+ };
54
+ },
55
+ };
@@ -0,0 +1,123 @@
1
+ import type {
2
+ FindModelOptions,
3
+ ModelRegistryConfig,
4
+ ModelRegistryEntry,
5
+ RegistryEntryInput,
6
+ } from "./types.js";
7
+
8
+ export class ModelRegistryImpl {
9
+ private entries: Map<string, ModelRegistryEntry> = new Map();
10
+
11
+ constructor(config: ModelRegistryConfig) {
12
+ const entries = config.entries ?? {};
13
+ for (const [id, entry] of Object.entries(entries)) {
14
+ this.registerEntry(id, entry);
15
+ }
16
+ }
17
+
18
+ private registerEntry(id: string, entry: RegistryEntryInput): ModelRegistryEntry {
19
+ const model: ModelRegistryEntry = {
20
+ id,
21
+ name: entry.id,
22
+ inference: entry.inference,
23
+ context: entry.context,
24
+ quant: entry.quant,
25
+ capabilities: entry.capabilities,
26
+ latency: entry.latency,
27
+ size_bytes: 4_000_000_000,
28
+ loaded: false,
29
+ loaded_at: undefined,
30
+ };
31
+ this.entries.set(id, model);
32
+ return model;
33
+ }
34
+
35
+ list(): ModelRegistryEntry[] {
36
+ return Array.from(this.entries.values());
37
+ }
38
+
39
+ get(id: string): ModelRegistryEntry | undefined {
40
+ return this.entries.get(id);
41
+ }
42
+
43
+ find(options: FindModelOptions = {}): ModelRegistryEntry[] {
44
+ const results = this.list().filter((model) => {
45
+ if (options.capability && !model.capabilities.includes(options.capability)) {
46
+ return false;
47
+ }
48
+ if (options.latency && model.latency !== options.latency) {
49
+ return false;
50
+ }
51
+ if (options.inference && model.inference !== options.inference) {
52
+ return false;
53
+ }
54
+ if (options.minContext && model.context < options.minContext) {
55
+ return false;
56
+ }
57
+ if (options.loaded !== undefined && model.loaded !== options.loaded) {
58
+ return false;
59
+ }
60
+ return true;
61
+ });
62
+ return results;
63
+ }
64
+
65
+ findOne(options: FindModelOptions = {}): ModelRegistryEntry | undefined {
66
+ return this.find(options)[0];
67
+ }
68
+
69
+ has(id: string): boolean {
70
+ return this.entries.has(id);
71
+ }
72
+
73
+ count(): number {
74
+ return this.entries.size;
75
+ }
76
+
77
+ add(id: string, entry: RegistryEntryInput): ModelRegistryEntry {
78
+ if (this.entries.has(id)) {
79
+ throw new Error(`Model with id '${id}' already exists`);
80
+ }
81
+ return this.registerEntry(id, entry);
82
+ }
83
+
84
+ update(id: string, updates: Partial<RegistryEntryInput>): ModelRegistryEntry {
85
+ const existing = this.entries.get(id);
86
+ if (!existing) {
87
+ throw new Error(`Model with id '${id}' not found`);
88
+ }
89
+
90
+ const updated: ModelRegistryEntry = {
91
+ ...existing,
92
+ ...updates,
93
+ id,
94
+ };
95
+ this.entries.set(id, updated);
96
+ return updated;
97
+ }
98
+
99
+ remove(id: string): boolean {
100
+ return this.entries.delete(id);
101
+ }
102
+
103
+ clear(): void {
104
+ this.entries.clear();
105
+ }
106
+
107
+ toObject(): Record<string, ModelRegistryEntry> {
108
+ return Object.fromEntries(this.entries);
109
+ }
110
+
111
+ fromObject(obj: Record<string, ModelRegistryEntry>): void {
112
+ this.entries.clear();
113
+ for (const [id, entry] of Object.entries(obj)) {
114
+ this.entries.set(id, entry);
115
+ }
116
+ }
117
+ }
118
+
119
+ export function ModelRegistry(
120
+ config: ModelRegistryConfig,
121
+ ): ModelRegistryImpl {
122
+ return new ModelRegistryImpl(config);
123
+ }
package/src/types.ts ADDED
@@ -0,0 +1,164 @@
1
+ export type InferenceBackend = "ollama" | "llama" | "huggingface" | "openai";
2
+
3
+ export type ModelCapability = "chat" | "vision" | "embedding" | "completion";
4
+
5
+ export type LatencyProfile = "extreme" | "fast" | "slow";
6
+
7
+ export interface ModelRegistryEntry {
8
+ id: string;
9
+ name: string;
10
+ inference: InferenceBackend;
11
+ context: number;
12
+ quant?: string;
13
+ capabilities: ModelCapability[];
14
+ latency?: LatencyProfile;
15
+ size_bytes: number;
16
+ loaded: boolean;
17
+ loaded_at?: string;
18
+ }
19
+
20
+ export interface RegistryEntryInput {
21
+ id: string;
22
+ inference: InferenceBackend;
23
+ context: number;
24
+ quant?: string;
25
+ capabilities: ModelCapability[];
26
+ latency?: LatencyProfile;
27
+ }
28
+
29
+ export interface ModelRegistryConfig {
30
+ entries: Record<string, RegistryEntryInput>;
31
+ }
32
+
33
+ export interface HealthResponse {
34
+ status: string;
35
+ timestamp: string;
36
+ models_loaded: number;
37
+ }
38
+
39
+ export interface ModelListResponse {
40
+ models: ModelRegistryEntry[];
41
+ }
42
+
43
+ export interface RegisterModelRequest {
44
+ id: string;
45
+ name: string;
46
+ inference: InferenceBackend;
47
+ context: number;
48
+ quant?: string;
49
+ capabilities: ModelCapability[];
50
+ latency?: LatencyProfile;
51
+ size_bytes?: number;
52
+ }
53
+
54
+ export interface RegisterModelResponse {
55
+ success: boolean;
56
+ model: ModelRegistryEntry;
57
+ message: string;
58
+ }
59
+
60
+ export interface LoadModelRequest {
61
+ model_id: string;
62
+ }
63
+
64
+ export interface LoadModelResponse {
65
+ success: boolean;
66
+ model_id: string;
67
+ message: string;
68
+ }
69
+
70
+ export interface UnloadModelResponse {
71
+ success: boolean;
72
+ model_id: string;
73
+ message: string;
74
+ }
75
+
76
+ export interface InferenceRequest {
77
+ model_id: string;
78
+ prompt: string;
79
+ max_tokens?: number;
80
+ temperature?: number;
81
+ }
82
+
83
+ export interface InferenceResponse {
84
+ model_id: string;
85
+ text: string;
86
+ tokens_generated: number;
87
+ finish_reason: string;
88
+ }
89
+
90
+ export interface StreamToken {
91
+ token: string;
92
+ token_id: number;
93
+ complete: boolean;
94
+ }
95
+
96
+ export type StreamCallback = (token: StreamToken) => void;
97
+ export type StreamCompleteCallback = (response: InferenceResponse) => void;
98
+ export type StreamErrorCallback = (error: Error) => void;
99
+
100
+ export interface StreamOptions {
101
+ onToken: StreamCallback;
102
+ onComplete?: StreamCompleteCallback;
103
+ onError?: StreamErrorCallback;
104
+ }
105
+
106
+ export interface OpenLLMConfig {
107
+ engine?: string | number;
108
+ timeout?: number;
109
+ }
110
+
111
+ export interface FindModelOptions {
112
+ capability?: ModelCapability;
113
+ latency?: LatencyProfile;
114
+ inference?: InferenceBackend;
115
+ minContext?: number;
116
+ loaded?: boolean;
117
+ }
118
+
119
+ export interface APIConfig {
120
+ modelrouter?: boolean;
121
+ registry?: unknown | string;
122
+ engine?: string | number;
123
+ prefix?: string;
124
+ }
125
+
126
+ export class OpenLLMError extends Error {
127
+ constructor(
128
+ message: string,
129
+ public code?: string,
130
+ public statusCode?: number,
131
+ ) {
132
+ super(message);
133
+ this.name = "OpenLLMError";
134
+ }
135
+ }
136
+
137
+ export class ModelNotFoundError extends OpenLLMError {
138
+ constructor(modelId: string) {
139
+ super(`Model '${modelId}' not found`, "MODEL_NOT_FOUND", 404);
140
+ this.name = "ModelNotFoundError";
141
+ }
142
+ }
143
+
144
+ export class ModelNotLoadedError extends OpenLLMError {
145
+ constructor(modelId: string) {
146
+ super(`Model '${modelId}' is not loaded`, "MODEL_NOT_LOADED", 412);
147
+ this.name = "ModelNotLoadedError";
148
+ }
149
+ }
150
+
151
+ export class InferenceError extends OpenLLMError {
152
+ constructor(message: string) {
153
+ super(message, "INFERENCE_ERROR", 502);
154
+ this.name = "InferenceError";
155
+ }
156
+ }
157
+
158
+ export interface ModelRegistryInstance {
159
+ list(): ReturnType<typeof import("./registry").ModelRegistryImpl.prototype.list>;
160
+ get(id: string): ReturnType<typeof import("./registry").ModelRegistryImpl.prototype.get>;
161
+ findOne(options: FindModelOptions): ReturnType<typeof import("./registry").ModelRegistryImpl.prototype.findOne>;
162
+ }
163
+
164
+ export type ModelRegistryImpl = ModelRegistryInstance;
package/tsconfig.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "resolveJsonModule": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "esModuleInterop": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "./src",
16
+ "sourceMap": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true,
20
+ "noImplicitReturns": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["src/**/*", "../backend/src/index.ts"],
24
+ "exclude": ["node_modules", "dist"]
25
+ }