learngraph 0.2.0 → 0.4.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/README.md +82 -1
- package/dist/cjs/llm/adapters/anthropic.js +124 -0
- package/dist/cjs/llm/adapters/anthropic.js.map +1 -0
- package/dist/cjs/llm/adapters/base.js +100 -0
- package/dist/cjs/llm/adapters/base.js.map +1 -0
- package/dist/cjs/llm/adapters/gemini.js +156 -0
- package/dist/cjs/llm/adapters/gemini.js.map +1 -0
- package/dist/cjs/llm/adapters/index.js +33 -0
- package/dist/cjs/llm/adapters/index.js.map +1 -0
- package/dist/cjs/llm/adapters/mediapipe.js +290 -0
- package/dist/cjs/llm/adapters/mediapipe.js.map +1 -0
- package/dist/cjs/llm/adapters/ollama.js +149 -0
- package/dist/cjs/llm/adapters/ollama.js.map +1 -0
- package/dist/cjs/llm/adapters/openai.js +126 -0
- package/dist/cjs/llm/adapters/openai.js.map +1 -0
- package/dist/cjs/llm/adapters/openrouter.js +190 -0
- package/dist/cjs/llm/adapters/openrouter.js.map +1 -0
- package/dist/cjs/llm/index.js +42 -5
- package/dist/cjs/llm/index.js.map +1 -1
- package/dist/cjs/llm/orchestrator.js +219 -0
- package/dist/cjs/llm/orchestrator.js.map +1 -0
- package/dist/cjs/llm/prompts.js +367 -0
- package/dist/cjs/llm/prompts.js.map +1 -0
- package/dist/cjs/types/llm.js +8 -0
- package/dist/cjs/types/llm.js.map +1 -0
- package/dist/esm/llm/adapters/anthropic.js +119 -0
- package/dist/esm/llm/adapters/anthropic.js.map +1 -0
- package/dist/esm/llm/adapters/base.js +95 -0
- package/dist/esm/llm/adapters/base.js.map +1 -0
- package/dist/esm/llm/adapters/gemini.js +151 -0
- package/dist/esm/llm/adapters/gemini.js.map +1 -0
- package/dist/esm/llm/adapters/index.js +13 -0
- package/dist/esm/llm/adapters/index.js.map +1 -0
- package/dist/esm/llm/adapters/mediapipe.js +252 -0
- package/dist/esm/llm/adapters/mediapipe.js.map +1 -0
- package/dist/esm/llm/adapters/ollama.js +144 -0
- package/dist/esm/llm/adapters/ollama.js.map +1 -0
- package/dist/esm/llm/adapters/openai.js +121 -0
- package/dist/esm/llm/adapters/openai.js.map +1 -0
- package/dist/esm/llm/adapters/openrouter.js +185 -0
- package/dist/esm/llm/adapters/openrouter.js.map +1 -0
- package/dist/esm/llm/index.js +12 -6
- package/dist/esm/llm/index.js.map +1 -1
- package/dist/esm/llm/orchestrator.js +214 -0
- package/dist/esm/llm/orchestrator.js.map +1 -0
- package/dist/esm/llm/prompts.js +360 -0
- package/dist/esm/llm/prompts.js.map +1 -0
- package/dist/esm/types/llm.js +7 -0
- package/dist/esm/types/llm.js.map +1 -0
- package/dist/types/llm/adapters/anthropic.d.ts +21 -0
- package/dist/types/llm/adapters/anthropic.d.ts.map +1 -0
- package/dist/types/llm/adapters/base.d.ts +46 -0
- package/dist/types/llm/adapters/base.d.ts.map +1 -0
- package/dist/types/llm/adapters/gemini.d.ts +30 -0
- package/dist/types/llm/adapters/gemini.d.ts.map +1 -0
- package/dist/types/llm/adapters/index.d.ts +14 -0
- package/dist/types/llm/adapters/index.d.ts.map +1 -0
- package/dist/types/llm/adapters/mediapipe.d.ts +113 -0
- package/dist/types/llm/adapters/mediapipe.d.ts.map +1 -0
- package/dist/types/llm/adapters/ollama.d.ts +30 -0
- package/dist/types/llm/adapters/ollama.d.ts.map +1 -0
- package/dist/types/llm/adapters/openai.d.ts +22 -0
- package/dist/types/llm/adapters/openai.d.ts.map +1 -0
- package/dist/types/llm/adapters/openrouter.d.ts +58 -0
- package/dist/types/llm/adapters/openrouter.d.ts.map +1 -0
- package/dist/types/llm/index.d.ts +5 -0
- package/dist/types/llm/index.d.ts.map +1 -1
- package/dist/types/llm/orchestrator.d.ts +35 -0
- package/dist/types/llm/orchestrator.d.ts.map +1 -0
- package/dist/types/llm/prompts.d.ts +269 -0
- package/dist/types/llm/prompts.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +1 -0
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/types/types/llm.d.ts +337 -0
- package/dist/types/types/llm.d.ts.map +1 -0
- package/package.json +6 -2
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI adapter for LLM integration
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { BaseLLMAdapter, LLMError, DEFAULT_CONFIG } from './base.js';
|
|
7
|
+
/**
|
|
8
|
+
* OpenAI adapter for chat completions
|
|
9
|
+
*/
|
|
10
|
+
export class OpenAIAdapter extends BaseLLMAdapter {
|
|
11
|
+
baseUrl;
|
|
12
|
+
organization;
|
|
13
|
+
constructor(config) {
|
|
14
|
+
super(config);
|
|
15
|
+
this.baseUrl = config.baseUrl ?? 'https://api.openai.com/v1';
|
|
16
|
+
if (config.organization) {
|
|
17
|
+
this.organization = config.organization;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
get provider() {
|
|
21
|
+
return 'openai';
|
|
22
|
+
}
|
|
23
|
+
async complete(request) {
|
|
24
|
+
if (!this.isConfigured()) {
|
|
25
|
+
throw new LLMError('OpenAI adapter not configured. Set OPENAI_API_KEY or pass apiKey in config.', 'NOT_CONFIGURED', this.provider);
|
|
26
|
+
}
|
|
27
|
+
return this.withRetry(async () => {
|
|
28
|
+
const headers = {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
31
|
+
};
|
|
32
|
+
if (this.organization) {
|
|
33
|
+
headers['OpenAI-Organization'] = this.organization;
|
|
34
|
+
}
|
|
35
|
+
const body = {
|
|
36
|
+
model: this.config.model,
|
|
37
|
+
messages: request.messages.map((m) => ({
|
|
38
|
+
role: m.role,
|
|
39
|
+
content: m.content,
|
|
40
|
+
})),
|
|
41
|
+
max_tokens: request.maxTokens ?? this.config.maxTokens ?? DEFAULT_CONFIG.maxTokens,
|
|
42
|
+
temperature: request.temperature ?? this.config.temperature ?? DEFAULT_CONFIG.temperature,
|
|
43
|
+
};
|
|
44
|
+
// Add JSON mode if requested
|
|
45
|
+
if (request.responseFormat === 'json') {
|
|
46
|
+
body.response_format = { type: 'json_object' };
|
|
47
|
+
}
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
const timeout = setTimeout(() => controller.abort(), this.config.timeout ?? DEFAULT_CONFIG.timeout);
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers,
|
|
54
|
+
body: JSON.stringify(body),
|
|
55
|
+
signal: controller.signal,
|
|
56
|
+
});
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorData = (await response.json().catch(() => ({})));
|
|
60
|
+
const errorMessage = errorData.error?.message ?? `HTTP ${response.status}`;
|
|
61
|
+
if (response.status === 429) {
|
|
62
|
+
throw new LLMError(`Rate limit exceeded: ${errorMessage}`, 'RATE_LIMIT', this.provider);
|
|
63
|
+
}
|
|
64
|
+
throw new LLMError(`OpenAI API error: ${errorMessage}`, 'API_ERROR', this.provider);
|
|
65
|
+
}
|
|
66
|
+
const data = (await response.json());
|
|
67
|
+
const choice = data.choices[0];
|
|
68
|
+
if (!choice) {
|
|
69
|
+
throw new LLMError('No response from OpenAI', 'INVALID_RESPONSE', this.provider);
|
|
70
|
+
}
|
|
71
|
+
const content = choice.message.content;
|
|
72
|
+
let json;
|
|
73
|
+
if (request.responseFormat === 'json') {
|
|
74
|
+
json = this.parseJSON(content);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
content,
|
|
78
|
+
json,
|
|
79
|
+
usage: {
|
|
80
|
+
promptTokens: data.usage.prompt_tokens,
|
|
81
|
+
completionTokens: data.usage.completion_tokens,
|
|
82
|
+
totalTokens: data.usage.total_tokens,
|
|
83
|
+
},
|
|
84
|
+
model: data.model,
|
|
85
|
+
finishReason: choice.finish_reason === 'stop' ? 'stop' :
|
|
86
|
+
choice.finish_reason === 'length' ? 'length' :
|
|
87
|
+
choice.finish_reason === 'content_filter' ? 'content_filter' : 'error',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
clearTimeout(timeout);
|
|
92
|
+
if (error instanceof LLMError) {
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
if (error.name === 'AbortError') {
|
|
97
|
+
throw new LLMError('Request timeout', 'TIMEOUT', this.provider, error);
|
|
98
|
+
}
|
|
99
|
+
throw new LLMError(`Network error: ${error.message}`, 'NETWORK_ERROR', this.provider, error);
|
|
100
|
+
}
|
|
101
|
+
throw new LLMError('Unknown error', 'API_ERROR', this.provider);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create an OpenAI adapter from environment variables
|
|
108
|
+
*/
|
|
109
|
+
export function createOpenAIAdapter(model = 'gpt-4o', overrides) {
|
|
110
|
+
const apiKey = typeof process !== 'undefined' ? process.env.OPENAI_API_KEY : undefined;
|
|
111
|
+
const config = {
|
|
112
|
+
provider: 'openai',
|
|
113
|
+
model,
|
|
114
|
+
...overrides,
|
|
115
|
+
};
|
|
116
|
+
if (apiKey) {
|
|
117
|
+
config.apiKey = apiKey;
|
|
118
|
+
}
|
|
119
|
+
return new OpenAIAdapter(config);
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../../src/llm/adapters/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenRouter LLM adapter
|
|
3
|
+
*
|
|
4
|
+
* OpenRouter provides access to many models through a single API:
|
|
5
|
+
* - OpenAI (GPT-4, GPT-3.5)
|
|
6
|
+
* - Anthropic (Claude)
|
|
7
|
+
* - Google (Gemini, PaLM)
|
|
8
|
+
* - Meta (Llama)
|
|
9
|
+
* - Mistral
|
|
10
|
+
* - And many more
|
|
11
|
+
*
|
|
12
|
+
* @packageDocumentation
|
|
13
|
+
*/
|
|
14
|
+
import { BaseLLMAdapter, LLMError } from './base.js';
|
|
15
|
+
/**
|
|
16
|
+
* Popular models available on OpenRouter
|
|
17
|
+
*/
|
|
18
|
+
export const OPENROUTER_MODELS = {
|
|
19
|
+
// OpenAI
|
|
20
|
+
'openai/gpt-4o': 'openai/gpt-4o',
|
|
21
|
+
'openai/gpt-4-turbo': 'openai/gpt-4-turbo',
|
|
22
|
+
'openai/gpt-3.5-turbo': 'openai/gpt-3.5-turbo',
|
|
23
|
+
// Anthropic
|
|
24
|
+
'anthropic/claude-3.5-sonnet': 'anthropic/claude-3.5-sonnet',
|
|
25
|
+
'anthropic/claude-3-opus': 'anthropic/claude-3-opus',
|
|
26
|
+
'anthropic/claude-3-haiku': 'anthropic/claude-3-haiku',
|
|
27
|
+
// Google
|
|
28
|
+
'google/gemini-pro': 'google/gemini-pro',
|
|
29
|
+
'google/gemini-pro-1.5': 'google/gemini-pro-1.5',
|
|
30
|
+
// Meta
|
|
31
|
+
'meta-llama/llama-3.1-70b-instruct': 'meta-llama/llama-3.1-70b-instruct',
|
|
32
|
+
'meta-llama/llama-3.1-8b-instruct': 'meta-llama/llama-3.1-8b-instruct',
|
|
33
|
+
// Mistral
|
|
34
|
+
'mistralai/mistral-large': 'mistralai/mistral-large',
|
|
35
|
+
'mistralai/mistral-medium': 'mistralai/mistral-medium',
|
|
36
|
+
'mistralai/mixtral-8x7b-instruct': 'mistralai/mixtral-8x7b-instruct',
|
|
37
|
+
// DeepSeek
|
|
38
|
+
'deepseek/deepseek-chat': 'deepseek/deepseek-chat',
|
|
39
|
+
'deepseek/deepseek-coder': 'deepseek/deepseek-coder',
|
|
40
|
+
// Qwen
|
|
41
|
+
'qwen/qwen-2.5-72b-instruct': 'qwen/qwen-2.5-72b-instruct',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Adapter for OpenRouter API
|
|
45
|
+
*
|
|
46
|
+
* OpenRouter provides unified access to 100+ models from various providers.
|
|
47
|
+
* See https://openrouter.ai/docs for full documentation.
|
|
48
|
+
*/
|
|
49
|
+
export class OpenRouterAdapter extends BaseLLMAdapter {
|
|
50
|
+
baseUrl;
|
|
51
|
+
siteUrl;
|
|
52
|
+
appName;
|
|
53
|
+
constructor(config) {
|
|
54
|
+
super(config);
|
|
55
|
+
this.baseUrl = config.baseUrl ?? 'https://openrouter.ai/api/v1';
|
|
56
|
+
if (config.siteUrl) {
|
|
57
|
+
this.siteUrl = config.siteUrl;
|
|
58
|
+
}
|
|
59
|
+
if (config.appName) {
|
|
60
|
+
this.appName = config.appName;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
get provider() {
|
|
64
|
+
return 'openrouter';
|
|
65
|
+
}
|
|
66
|
+
async complete(request) {
|
|
67
|
+
if (!this.isConfigured()) {
|
|
68
|
+
throw new LLMError('OpenRouter adapter is not configured. Set OPENROUTER_API_KEY environment variable.', 'NOT_CONFIGURED', this.provider);
|
|
69
|
+
}
|
|
70
|
+
const openRouterRequest = this.buildRequest(request);
|
|
71
|
+
try {
|
|
72
|
+
const response = await this.executeRequest(openRouterRequest);
|
|
73
|
+
return this.parseResponse(response);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error instanceof LLMError) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
throw new LLMError(`OpenRouter API request failed: ${error instanceof Error ? error.message : 'Unknown error'}`, 'API_ERROR', this.provider, error instanceof Error ? error : undefined);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
buildRequest(request) {
|
|
83
|
+
const openRouterRequest = {
|
|
84
|
+
model: this.config.model,
|
|
85
|
+
messages: request.messages.map((m) => ({
|
|
86
|
+
role: m.role,
|
|
87
|
+
content: m.content,
|
|
88
|
+
})),
|
|
89
|
+
max_tokens: request.maxTokens ?? this.config.maxTokens ?? 4096,
|
|
90
|
+
temperature: request.temperature ?? this.config.temperature ?? 0.3,
|
|
91
|
+
};
|
|
92
|
+
// Handle JSON response format
|
|
93
|
+
if (request.responseFormat === 'json') {
|
|
94
|
+
openRouterRequest.response_format = { type: 'json_object' };
|
|
95
|
+
}
|
|
96
|
+
return openRouterRequest;
|
|
97
|
+
}
|
|
98
|
+
async executeRequest(request) {
|
|
99
|
+
const headers = {
|
|
100
|
+
'Content-Type': 'application/json',
|
|
101
|
+
Authorization: `Bearer ${this.config.apiKey}`,
|
|
102
|
+
};
|
|
103
|
+
// Add optional OpenRouter-specific headers for rankings
|
|
104
|
+
if (this.siteUrl) {
|
|
105
|
+
headers['HTTP-Referer'] = this.siteUrl;
|
|
106
|
+
}
|
|
107
|
+
if (this.appName) {
|
|
108
|
+
headers['X-Title'] = this.appName;
|
|
109
|
+
}
|
|
110
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
111
|
+
method: 'POST',
|
|
112
|
+
headers,
|
|
113
|
+
body: JSON.stringify(request),
|
|
114
|
+
signal: AbortSignal.timeout(this.config.timeout ?? 60000),
|
|
115
|
+
});
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
const errorBody = await response.text();
|
|
118
|
+
let errorMessage = `HTTP ${response.status}`;
|
|
119
|
+
try {
|
|
120
|
+
const errorJson = JSON.parse(errorBody);
|
|
121
|
+
errorMessage = errorJson.error?.message ?? errorMessage;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
errorMessage = errorBody || errorMessage;
|
|
125
|
+
}
|
|
126
|
+
if (response.status === 429) {
|
|
127
|
+
throw new LLMError(`Rate limit exceeded: ${errorMessage}`, 'RATE_LIMIT', this.provider);
|
|
128
|
+
}
|
|
129
|
+
if (response.status === 402) {
|
|
130
|
+
throw new LLMError(`Payment required or insufficient credits: ${errorMessage}`, 'API_ERROR', this.provider);
|
|
131
|
+
}
|
|
132
|
+
throw new LLMError(`OpenRouter API error: ${errorMessage}`, 'API_ERROR', this.provider);
|
|
133
|
+
}
|
|
134
|
+
return response.json();
|
|
135
|
+
}
|
|
136
|
+
parseResponse(response) {
|
|
137
|
+
if (!response.choices || response.choices.length === 0) {
|
|
138
|
+
throw new LLMError('No choices in OpenRouter response', 'INVALID_RESPONSE', this.provider);
|
|
139
|
+
}
|
|
140
|
+
const choice = response.choices[0];
|
|
141
|
+
if (!choice) {
|
|
142
|
+
throw new LLMError('No choice in OpenRouter response', 'INVALID_RESPONSE', this.provider);
|
|
143
|
+
}
|
|
144
|
+
const content = choice.message.content;
|
|
145
|
+
// Try to parse JSON if present
|
|
146
|
+
let json;
|
|
147
|
+
try {
|
|
148
|
+
json = this.parseJSON(content);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Not JSON, that's fine
|
|
152
|
+
}
|
|
153
|
+
// Map finish reasons to our standard format
|
|
154
|
+
const finishReason = choice.finish_reason === 'stop' ? 'stop' :
|
|
155
|
+
choice.finish_reason === 'length' ? 'length' :
|
|
156
|
+
choice.finish_reason === 'content_filter' ? 'content_filter' : 'stop';
|
|
157
|
+
return {
|
|
158
|
+
content,
|
|
159
|
+
json,
|
|
160
|
+
usage: {
|
|
161
|
+
promptTokens: response.usage.prompt_tokens,
|
|
162
|
+
completionTokens: response.usage.completion_tokens,
|
|
163
|
+
totalTokens: response.usage.total_tokens,
|
|
164
|
+
},
|
|
165
|
+
model: response.model,
|
|
166
|
+
finishReason,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Create an OpenRouter adapter from environment variables
|
|
172
|
+
*/
|
|
173
|
+
export function createOpenRouterAdapter(model = 'anthropic/claude-3.5-sonnet', overrides) {
|
|
174
|
+
const apiKey = typeof process !== 'undefined' ? process.env.OPENROUTER_API_KEY : undefined;
|
|
175
|
+
const config = {
|
|
176
|
+
provider: 'openrouter',
|
|
177
|
+
model,
|
|
178
|
+
...overrides,
|
|
179
|
+
};
|
|
180
|
+
if (apiKey) {
|
|
181
|
+
config.apiKey = apiKey;
|
|
182
|
+
}
|
|
183
|
+
return new OpenRouterAdapter(config);
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=openrouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openrouter.js","sourceRoot":"","sources":["../../../../src/llm/adapters/openrouter.ts"],"names":[],"mappings":"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"}
|
package/dist/esm/llm/index.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* LLM integration for skill extraction and prerequisite inference
|
|
4
3
|
*
|
|
5
4
|
* @packageDocumentation
|
|
6
5
|
*/
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
//
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
// Adapters
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
export { BaseLLMAdapter, LLMError, DEFAULT_CONFIG, OpenAIAdapter, createOpenAIAdapter, AnthropicAdapter, createAnthropicAdapter, OllamaAdapter, createOllamaAdapter, GeminiAdapter, createGeminiAdapter, OpenRouterAdapter, createOpenRouterAdapter, OPENROUTER_MODELS, MediaPipeAdapter, createMediaPipeAdapter, MEDIAPIPE_MODELS, } from './adapters/index.js';
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// Orchestrator
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
export { LLMOrchestrator, createOrchestrator } from './orchestrator.js';
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// Prompts
|
|
16
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
export { SYSTEM_PROMPTS, EXTRACTION_SCHEMA, PREREQUISITE_SCHEMA, BLOOM_ANALYSIS_SCHEMA, DECOMPOSITION_SCHEMA, buildExtractionPrompt, buildPrerequisitePrompt, buildBloomPrompt, buildDecompositionPrompt, } from './prompts.js';
|
|
12
18
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/llm/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/llm/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgCH,gFAAgF;AAChF,WAAW;AACX,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,QAAQ,EACR,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,gBAAgB,EAChB,sBAAsB,EACtB,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAG7B,gFAAgF;AAChF,eAAe;AACf,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAExE,gFAAgF;AAChF,UAAU;AACV,gFAAgF;AAChF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,uBAAuB,EACvB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM orchestrator for educational tasks
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
import { SKILL_DEFAULTS, THRESHOLD_CONCEPT_MASTERY } from '../types/skill.js';
|
|
7
|
+
import { SYSTEM_PROMPTS, EXTRACTION_SCHEMA, PREREQUISITE_SCHEMA, BLOOM_ANALYSIS_SCHEMA, DECOMPOSITION_SCHEMA, buildExtractionPrompt, buildPrerequisitePrompt, buildBloomPrompt, buildDecompositionPrompt, } from './prompts.js';
|
|
8
|
+
/** Default estimated minutes for skill mastery */
|
|
9
|
+
const DEFAULT_ESTIMATED_MINUTES = 30;
|
|
10
|
+
/**
|
|
11
|
+
* Difficulty mapping for Bloom's levels
|
|
12
|
+
*/
|
|
13
|
+
const BLOOM_DIFFICULTY = {
|
|
14
|
+
remember: 0.2,
|
|
15
|
+
understand: 0.35,
|
|
16
|
+
apply: 0.5,
|
|
17
|
+
analyze: 0.65,
|
|
18
|
+
evaluate: 0.8,
|
|
19
|
+
create: 0.9,
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* High-level orchestrator for LLM-based educational tasks
|
|
23
|
+
*/
|
|
24
|
+
export class LLMOrchestrator {
|
|
25
|
+
adapter;
|
|
26
|
+
constructor(adapter) {
|
|
27
|
+
this.adapter = adapter;
|
|
28
|
+
}
|
|
29
|
+
getAdapter() {
|
|
30
|
+
return this.adapter;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extract skills from curriculum content
|
|
34
|
+
*/
|
|
35
|
+
async extractSkills(request) {
|
|
36
|
+
const startTime = Date.now();
|
|
37
|
+
// Build options object conditionally to avoid undefined values
|
|
38
|
+
const extractionOptions = {};
|
|
39
|
+
if (request.domain)
|
|
40
|
+
extractionOptions.domain = request.domain;
|
|
41
|
+
if (request.gradeLevel)
|
|
42
|
+
extractionOptions.gradeLevel = request.gradeLevel;
|
|
43
|
+
if (request.context)
|
|
44
|
+
extractionOptions.context = request.context;
|
|
45
|
+
const messages = [
|
|
46
|
+
{ role: 'system', content: SYSTEM_PROMPTS.skillExtraction },
|
|
47
|
+
{
|
|
48
|
+
role: 'user',
|
|
49
|
+
content: buildExtractionPrompt(request.content, extractionOptions),
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
const response = await this.adapter.complete({
|
|
53
|
+
messages,
|
|
54
|
+
responseFormat: 'json',
|
|
55
|
+
jsonSchema: EXTRACTION_SCHEMA,
|
|
56
|
+
});
|
|
57
|
+
const parsed = response.json;
|
|
58
|
+
// Apply filters and defaults
|
|
59
|
+
let skills = parsed.skills.map((skill) => ({
|
|
60
|
+
...skill,
|
|
61
|
+
isThresholdConcept: skill.isThresholdConcept ?? false,
|
|
62
|
+
confidence: skill.confidence ?? 0.8,
|
|
63
|
+
}));
|
|
64
|
+
// Filter by minimum confidence
|
|
65
|
+
if (request.minConfidence) {
|
|
66
|
+
skills = skills.filter((s) => s.confidence >= request.minConfidence);
|
|
67
|
+
}
|
|
68
|
+
// Calculate overall confidence
|
|
69
|
+
const confidence = skills.length > 0 ? skills.reduce((acc, s) => acc + s.confidence, 0) / skills.length : 0;
|
|
70
|
+
return {
|
|
71
|
+
skills,
|
|
72
|
+
confidence,
|
|
73
|
+
usage: response.usage,
|
|
74
|
+
warnings: parsed.warnings ?? [],
|
|
75
|
+
durationMs: Date.now() - startTime,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Infer prerequisites between skills
|
|
80
|
+
*/
|
|
81
|
+
async inferPrerequisites(request) {
|
|
82
|
+
const startTime = Date.now();
|
|
83
|
+
// Build options object conditionally to avoid undefined values
|
|
84
|
+
const prerequisiteOptions = {};
|
|
85
|
+
if (request.domain)
|
|
86
|
+
prerequisiteOptions.domain = request.domain;
|
|
87
|
+
if (request.inferTransitive !== undefined)
|
|
88
|
+
prerequisiteOptions.inferTransitive = request.inferTransitive;
|
|
89
|
+
const messages = [
|
|
90
|
+
{ role: 'system', content: SYSTEM_PROMPTS.prerequisiteInference },
|
|
91
|
+
{
|
|
92
|
+
role: 'user',
|
|
93
|
+
content: buildPrerequisitePrompt(request.skills, prerequisiteOptions),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
const response = await this.adapter.complete({
|
|
97
|
+
messages,
|
|
98
|
+
responseFormat: 'json',
|
|
99
|
+
jsonSchema: PREREQUISITE_SCHEMA,
|
|
100
|
+
});
|
|
101
|
+
const parsed = response.json;
|
|
102
|
+
// Filter by minimum confidence
|
|
103
|
+
let prerequisites = parsed.prerequisites;
|
|
104
|
+
if (request.minConfidence) {
|
|
105
|
+
prerequisites = prerequisites.filter((p) => p.confidence >= request.minConfidence);
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
prerequisites,
|
|
109
|
+
usage: response.usage,
|
|
110
|
+
durationMs: Date.now() - startTime,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Analyze Bloom's level of text
|
|
115
|
+
*/
|
|
116
|
+
async analyzeBloomLevel(request) {
|
|
117
|
+
const messages = [
|
|
118
|
+
{ role: 'system', content: SYSTEM_PROMPTS.bloomAnalysis },
|
|
119
|
+
{ role: 'user', content: buildBloomPrompt(request.text, request.context) },
|
|
120
|
+
];
|
|
121
|
+
const response = await this.adapter.complete({
|
|
122
|
+
messages,
|
|
123
|
+
responseFormat: 'json',
|
|
124
|
+
jsonSchema: BLOOM_ANALYSIS_SCHEMA,
|
|
125
|
+
});
|
|
126
|
+
const parsed = response.json;
|
|
127
|
+
return {
|
|
128
|
+
level: parsed.level,
|
|
129
|
+
confidence: parsed.confidence,
|
|
130
|
+
indicators: parsed.indicators,
|
|
131
|
+
reasoning: parsed.reasoning,
|
|
132
|
+
usage: response.usage,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Full curriculum decomposition into skill graph
|
|
137
|
+
*/
|
|
138
|
+
async decompose(request) {
|
|
139
|
+
const startTime = Date.now();
|
|
140
|
+
// Build options object conditionally to avoid undefined values
|
|
141
|
+
const decompositionOptions = {};
|
|
142
|
+
if (request.title)
|
|
143
|
+
decompositionOptions.title = request.title;
|
|
144
|
+
if (request.domain)
|
|
145
|
+
decompositionOptions.domain = request.domain;
|
|
146
|
+
if (request.gradeLevel)
|
|
147
|
+
decompositionOptions.gradeLevel = request.gradeLevel;
|
|
148
|
+
if (request.context)
|
|
149
|
+
decompositionOptions.context = request.context;
|
|
150
|
+
if (request.maxDepth !== undefined)
|
|
151
|
+
decompositionOptions.maxDepth = request.maxDepth;
|
|
152
|
+
const messages = [
|
|
153
|
+
{ role: 'system', content: SYSTEM_PROMPTS.curriculumDecomposition },
|
|
154
|
+
{
|
|
155
|
+
role: 'user',
|
|
156
|
+
content: buildDecompositionPrompt(request.content, decompositionOptions),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
const response = await this.adapter.complete({
|
|
160
|
+
messages,
|
|
161
|
+
responseFormat: 'json',
|
|
162
|
+
jsonSchema: DECOMPOSITION_SCHEMA,
|
|
163
|
+
maxTokens: 8192, // Decomposition can be verbose
|
|
164
|
+
});
|
|
165
|
+
const parsed = response.json;
|
|
166
|
+
// Convert extracted skills to SkillNodeInput format
|
|
167
|
+
const skillInputs = parsed.skills.map((skill) => ({
|
|
168
|
+
name: skill.name,
|
|
169
|
+
description: skill.description,
|
|
170
|
+
bloomLevel: skill.bloomLevel,
|
|
171
|
+
difficulty: skill.difficulty ?? BLOOM_DIFFICULTY[skill.bloomLevel] ?? 0.5,
|
|
172
|
+
isThresholdConcept: skill.isThresholdConcept ?? false,
|
|
173
|
+
masteryThreshold: skill.isThresholdConcept
|
|
174
|
+
? THRESHOLD_CONCEPT_MASTERY
|
|
175
|
+
: SKILL_DEFAULTS.masteryThreshold,
|
|
176
|
+
estimatedMinutes: skill.estimatedMinutes ?? DEFAULT_ESTIMATED_MINUTES,
|
|
177
|
+
tags: skill.keywords ?? [],
|
|
178
|
+
metadata: {
|
|
179
|
+
llmExtracted: true,
|
|
180
|
+
llmId: skill.id,
|
|
181
|
+
confidence: skill.confidence ?? 0.8,
|
|
182
|
+
},
|
|
183
|
+
}));
|
|
184
|
+
// Convert prerequisites with confidence
|
|
185
|
+
const prerequisites = parsed.prerequisites.map((p) => ({
|
|
186
|
+
sourceId: p.sourceId,
|
|
187
|
+
targetId: p.targetId,
|
|
188
|
+
strength: p.strength,
|
|
189
|
+
type: p.type,
|
|
190
|
+
confidence: 0.8, // Default confidence for decomposition
|
|
191
|
+
reasoning: p.reasoning ?? 'Inferred from curriculum structure',
|
|
192
|
+
}));
|
|
193
|
+
return {
|
|
194
|
+
title: parsed.title || request.title || 'Untitled Curriculum',
|
|
195
|
+
skills: parsed.skills.map((s) => ({
|
|
196
|
+
...s,
|
|
197
|
+
isThresholdConcept: s.isThresholdConcept ?? false,
|
|
198
|
+
confidence: s.confidence ?? 0.8,
|
|
199
|
+
})),
|
|
200
|
+
prerequisites,
|
|
201
|
+
skillInputs,
|
|
202
|
+
usage: response.usage,
|
|
203
|
+
durationMs: Date.now() - startTime,
|
|
204
|
+
warnings: parsed.warnings ?? [],
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Create an orchestrator with a specific adapter
|
|
210
|
+
*/
|
|
211
|
+
export function createOrchestrator(adapter) {
|
|
212
|
+
return new LLMOrchestrator(adapter);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=orchestrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../../src/llm/orchestrator.ts"],"names":[],"mappings":"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"}
|