coaiajs 0.1.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/CLAUDE.md +56 -0
- package/KINSHIP.md +63 -0
- package/README.md +148 -0
- package/articles/academic/creative-orientation-vs-problem-solving.md +177 -0
- package/articles/academic/jsonl-knowledge-graphs-agent-memory.md +142 -0
- package/articles/academic/langfuse-observability-llm-pipelines.md +144 -0
- package/articles/academic/medicine-wheel-software-architecture.md +163 -0
- package/articles/academic/mmot-autonomous-agents.md +156 -0
- package/articles/academic/model-context-protocol-interagent.md +161 -0
- package/articles/academic/pde-prompt-decomposition.md +186 -0
- package/articles/academic/structural-tension-in-ai-agents.md +134 -0
- package/articles/reviews/mcp-protocol-design-review.md +170 -0
- package/articles/reviews/observability-ai-systems-review.md +176 -0
- package/articles/reviews/prompt-engineering-decomposition-review.md +184 -0
- package/articles/surveys/agent-orchestration-survey.md +186 -0
- package/articles/surveys/knowledge-graph-storage-survey.md +204 -0
- package/articles/surveys/structural-tension-methodology-survey.md +154 -0
- package/articles/technical/aws-sdk-v3-polly.md +270 -0
- package/articles/technical/commander-cli-framework.md +262 -0
- package/articles/technical/dotenv-config-patterns.md +360 -0
- package/articles/technical/ioredis-vs-redis.md +142 -0
- package/articles/technical/langfuse-js-sdk-vs-rest.md +191 -0
- package/articles/technical/mcp-sdk-typescript.md +291 -0
- package/articles/technical/octokit-github-api.md +293 -0
- package/articles/technical/openai-sdk-modern.md +231 -0
- package/articles/technical/yaml-parsing-node.md +266 -0
- package/articles/technical/zod-runtime-validation.md +212 -0
- package/dist/mcp/config.d.ts +14 -0
- package/dist/mcp/config.js +185 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +324 -0
- package/dist/mcp/tools/coaiapy-tools.d.ts +8 -0
- package/dist/mcp/tools/coaiapy-tools.js +326 -0
- package/dist/mcp/tools/index.d.ts +3 -0
- package/dist/mcp/tools/index.js +3 -0
- package/dist/src/audio.d.ts +12 -0
- package/dist/src/audio.js +57 -0
- package/dist/src/cli-helpers.d.ts +9 -0
- package/dist/src/cli-helpers.js +78 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +1086 -0
- package/dist/src/config.d.ts +15 -0
- package/dist/src/config.js +145 -0
- package/dist/src/environment.d.ts +44 -0
- package/dist/src/environment.js +146 -0
- package/dist/src/github.d.ts +47 -0
- package/dist/src/github.js +79 -0
- package/dist/src/langfuse/client.d.ts +30 -0
- package/dist/src/langfuse/client.js +75 -0
- package/dist/src/langfuse/comments.d.ts +16 -0
- package/dist/src/langfuse/comments.js +36 -0
- package/dist/src/langfuse/datasets.d.ts +19 -0
- package/dist/src/langfuse/datasets.js +140 -0
- package/dist/src/langfuse/index.d.ts +13 -0
- package/dist/src/langfuse/index.js +10 -0
- package/dist/src/langfuse/media.d.ts +11 -0
- package/dist/src/langfuse/media.js +167 -0
- package/dist/src/langfuse/observations.d.ts +18 -0
- package/dist/src/langfuse/observations.js +113 -0
- package/dist/src/langfuse/prompts.d.ts +14 -0
- package/dist/src/langfuse/prompts.js +127 -0
- package/dist/src/langfuse/scores.d.ts +54 -0
- package/dist/src/langfuse/scores.js +366 -0
- package/dist/src/langfuse/traces.d.ts +29 -0
- package/dist/src/langfuse/traces.js +225 -0
- package/dist/src/llm.d.ts +23 -0
- package/dist/src/llm.js +76 -0
- package/dist/src/narrative/graph-manager.d.ts +106 -0
- package/dist/src/narrative/graph-manager.js +915 -0
- package/dist/src/narrative/index.d.ts +9 -0
- package/dist/src/narrative/index.js +7 -0
- package/dist/src/narrative/markdown-export.d.ts +21 -0
- package/dist/src/narrative/markdown-export.js +383 -0
- package/dist/src/narrative/tool-definitions.d.ts +21 -0
- package/dist/src/narrative/tool-definitions.js +588 -0
- package/dist/src/narrative/tool-handlers.d.ts +11 -0
- package/dist/src/narrative/tool-handlers.js +462 -0
- package/dist/src/narrative/types.d.ts +2 -0
- package/dist/src/narrative/types.js +2 -0
- package/dist/src/narrative/validation.d.ts +29 -0
- package/dist/src/narrative/validation.js +144 -0
- package/dist/src/pde/index.d.ts +8 -0
- package/dist/src/pde/index.js +8 -0
- package/dist/src/pde/mcp-handlers.d.ts +7 -0
- package/dist/src/pde/mcp-handlers.js +300 -0
- package/dist/src/pde/mcp-tools.d.ts +15 -0
- package/dist/src/pde/mcp-tools.js +191 -0
- package/dist/src/pde/session-manager.d.ts +32 -0
- package/dist/src/pde/session-manager.js +205 -0
- package/dist/src/pde/stc-mapper.d.ts +26 -0
- package/dist/src/pde/stc-mapper.js +234 -0
- package/dist/src/pipeline/index.d.ts +2 -0
- package/dist/src/pipeline/index.js +3 -0
- package/dist/src/pipeline/template-engine.d.ts +20 -0
- package/dist/src/pipeline/template-engine.js +361 -0
- package/dist/src/planning/index.d.ts +7 -0
- package/dist/src/planning/index.js +7 -0
- package/dist/src/planning/mcp-handlers.d.ts +7 -0
- package/dist/src/planning/mcp-handlers.js +310 -0
- package/dist/src/planning/mcp-tools.d.ts +15 -0
- package/dist/src/planning/mcp-tools.js +145 -0
- package/dist/src/planning/plan-parser.d.ts +35 -0
- package/dist/src/planning/plan-parser.js +506 -0
- package/dist/src/redis.d.ts +21 -0
- package/dist/src/redis.js +78 -0
- package/dist/src/types.d.ts +244 -0
- package/dist/src/types.js +8 -0
- package/mcp/config.ts +196 -0
- package/mcp/server.ts +402 -0
- package/mcp/tools/coaiapy-tools.ts +364 -0
- package/mcp/tools/index.ts +4 -0
- package/package.json +52 -0
- package/rispecs/00-coaiajs-platform.spec.md +102 -0
- package/rispecs/01-core-config.spec.md +89 -0
- package/rispecs/02-redis-module.spec.md +62 -0
- package/rispecs/03-langfuse-module.spec.md +101 -0
- package/rispecs/04-narrative-engine.spec.md +185 -0
- package/rispecs/05-pde-engine.spec.md +112 -0
- package/rispecs/06-planning-engine.spec.md +128 -0
- package/rispecs/07-pipeline-templates.spec.md +97 -0
- package/rispecs/08-cli-interface.spec.md +134 -0
- package/rispecs/09-mcp-server.spec.md +140 -0
- package/rispecs/10-audio-module.spec.md +63 -0
- package/rispecs/KINSHIP.md +56 -0
- package/rispecs/README.md +100 -0
- package/src/audio.ts +76 -0
- package/src/cli-helpers.ts +86 -0
- package/src/cli.ts +1223 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +171 -0
- package/src/github.ts +143 -0
- package/src/langfuse/client.ts +105 -0
- package/src/langfuse/comments.ts +52 -0
- package/src/langfuse/datasets.ts +178 -0
- package/src/langfuse/index.ts +33 -0
- package/src/langfuse/media.ts +193 -0
- package/src/langfuse/observations.ts +131 -0
- package/src/langfuse/prompts.ts +157 -0
- package/src/langfuse/scores.ts +456 -0
- package/src/langfuse/traces.ts +276 -0
- package/src/llm.ts +106 -0
- package/src/narrative/graph-manager.ts +1358 -0
- package/src/narrative/index.ts +32 -0
- package/src/narrative/markdown-export.ts +535 -0
- package/src/narrative/tool-definitions.ts +635 -0
- package/src/narrative/tool-handlers.ts +528 -0
- package/src/narrative/types.ts +9 -0
- package/src/narrative/validation.ts +179 -0
- package/src/pde/index.ts +8 -0
- package/src/pde/mcp-handlers.ts +359 -0
- package/src/pde/mcp-tools.ts +201 -0
- package/src/pde/session-manager.ts +248 -0
- package/src/pde/stc-mapper.ts +298 -0
- package/src/pipeline/index.ts +7 -0
- package/src/pipeline/template-engine.ts +398 -0
- package/src/planning/index.ts +13 -0
- package/src/planning/mcp-handlers.ts +369 -0
- package/src/planning/mcp-tools.ts +155 -0
- package/src/planning/plan-parser.ts +587 -0
- package/src/redis.ts +97 -0
- package/src/types.ts +280 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
// coaiajs/src/langfuse/scores.ts — Score operations
|
|
2
|
+
// Port of cofuse.py score functions including configs, presets, import/export
|
|
3
|
+
|
|
4
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { getClient, nowISO } from './client.js';
|
|
6
|
+
import type { ScoreConfig } from '../types.js';
|
|
7
|
+
|
|
8
|
+
export interface ScoreFilters {
|
|
9
|
+
userId?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
fromTimestamp?: string;
|
|
12
|
+
toTimestamp?: string;
|
|
13
|
+
configId?: string;
|
|
14
|
+
page?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ─── Score CRUD ─────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
export async function createScore(params: {
|
|
20
|
+
id?: string;
|
|
21
|
+
name?: string;
|
|
22
|
+
value: number;
|
|
23
|
+
traceId?: string;
|
|
24
|
+
observationId?: string;
|
|
25
|
+
configId?: string;
|
|
26
|
+
comment?: string;
|
|
27
|
+
}): Promise<string> {
|
|
28
|
+
const client = getClient();
|
|
29
|
+
const data: Record<string, unknown> = { value: params.value };
|
|
30
|
+
|
|
31
|
+
if (params.id) data.id = params.id;
|
|
32
|
+
if (params.name) data.name = params.name;
|
|
33
|
+
if (params.traceId) data.traceId = params.traceId;
|
|
34
|
+
if (params.observationId) data.observationId = params.observationId;
|
|
35
|
+
if (params.configId) data.configId = params.configId;
|
|
36
|
+
if (params.comment) data.comment = params.comment;
|
|
37
|
+
|
|
38
|
+
const result = await client.request<unknown>('POST', '/api/public/scores', data);
|
|
39
|
+
return JSON.stringify(result, null, 2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function applyScoreToTrace(
|
|
43
|
+
traceId: string,
|
|
44
|
+
scoreId: string,
|
|
45
|
+
value: number,
|
|
46
|
+
): Promise<string> {
|
|
47
|
+
return createScoreForTarget({
|
|
48
|
+
targetType: 'trace',
|
|
49
|
+
targetId: traceId,
|
|
50
|
+
scoreId,
|
|
51
|
+
value,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function createScoreForTarget(params: {
|
|
56
|
+
targetType: 'trace' | 'session';
|
|
57
|
+
targetId: string;
|
|
58
|
+
scoreId?: string;
|
|
59
|
+
value: number;
|
|
60
|
+
scoreName?: string;
|
|
61
|
+
observationId?: string;
|
|
62
|
+
configId?: string;
|
|
63
|
+
comment?: string;
|
|
64
|
+
}): Promise<string> {
|
|
65
|
+
const client = getClient();
|
|
66
|
+
const data: Record<string, unknown> = { value: params.value };
|
|
67
|
+
|
|
68
|
+
if (params.targetType === 'trace') {
|
|
69
|
+
data.traceId = params.targetId;
|
|
70
|
+
if (params.observationId) data.observationId = params.observationId;
|
|
71
|
+
} else {
|
|
72
|
+
data.sessionId = params.targetId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (params.configId) {
|
|
76
|
+
data.configId = params.configId;
|
|
77
|
+
} else {
|
|
78
|
+
if (params.scoreId) data.id = params.scoreId;
|
|
79
|
+
if (params.scoreName) data.name = params.scoreName;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (params.comment) data.comment = params.comment;
|
|
83
|
+
|
|
84
|
+
const result = await client.request<unknown>('POST', '/api/public/scores', data);
|
|
85
|
+
return JSON.stringify(result, null, 2);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function listScores(filters: ScoreFilters): Promise<string> {
|
|
89
|
+
const client = getClient();
|
|
90
|
+
const allScores: unknown[] = [];
|
|
91
|
+
let page = filters.page ?? 1;
|
|
92
|
+
|
|
93
|
+
while (true) {
|
|
94
|
+
const params = new URLSearchParams({ page: String(page) });
|
|
95
|
+
if (filters.userId) params.set('userId', filters.userId);
|
|
96
|
+
if (filters.name) params.set('name', filters.name);
|
|
97
|
+
if (filters.fromTimestamp) params.set('fromTimestamp', filters.fromTimestamp);
|
|
98
|
+
if (filters.toTimestamp) params.set('toTimestamp', filters.toTimestamp);
|
|
99
|
+
if (filters.configId) params.set('configId', filters.configId);
|
|
100
|
+
|
|
101
|
+
const data = await client.request<Record<string, unknown>>(
|
|
102
|
+
'GET',
|
|
103
|
+
`/api/public/v2/scores?${params.toString()}`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const scores = (data as Record<string, unknown>).data;
|
|
107
|
+
if (!scores || !Array.isArray(scores) || scores.length === 0) break;
|
|
108
|
+
allScores.push(...scores);
|
|
109
|
+
|
|
110
|
+
if (!shouldContinuePagination(data, page)) break;
|
|
111
|
+
page++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return JSON.stringify(allScores, null, 2);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ─── Score Configs ──────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export async function listScoreConfigs(): Promise<string> {
|
|
120
|
+
const client = getClient();
|
|
121
|
+
const allConfigs: unknown[] = [];
|
|
122
|
+
let page = 1;
|
|
123
|
+
|
|
124
|
+
while (true) {
|
|
125
|
+
const data = await client.request<Record<string, unknown>>(
|
|
126
|
+
'GET',
|
|
127
|
+
`/api/public/score-configs?page=${page}`,
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const configs = (data as Record<string, unknown>).data;
|
|
131
|
+
if (!configs || !Array.isArray(configs) || configs.length === 0) break;
|
|
132
|
+
allConfigs.push(...configs);
|
|
133
|
+
|
|
134
|
+
if (!shouldContinuePagination(data, page)) break;
|
|
135
|
+
page++;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return JSON.stringify(allConfigs, null, 2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function getScoreConfig(configId: string): Promise<string> {
|
|
142
|
+
const client = getClient();
|
|
143
|
+
const result = await client.request<unknown>('GET', `/api/public/score-configs/${configId}`);
|
|
144
|
+
return JSON.stringify(result, null, 2);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function createScoreConfig(params: {
|
|
148
|
+
name: string;
|
|
149
|
+
dataType: 'NUMERIC' | 'CATEGORICAL' | 'BOOLEAN';
|
|
150
|
+
description?: string;
|
|
151
|
+
categories?: Array<{ label: string; value: number }>;
|
|
152
|
+
minValue?: number;
|
|
153
|
+
maxValue?: number;
|
|
154
|
+
}): Promise<string> {
|
|
155
|
+
const client = getClient();
|
|
156
|
+
const data: Record<string, unknown> = {
|
|
157
|
+
name: params.name,
|
|
158
|
+
dataType: params.dataType,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (params.description) data.description = params.description;
|
|
162
|
+
if (params.categories) data.categories = params.categories;
|
|
163
|
+
if (params.minValue !== undefined) data.minValue = params.minValue;
|
|
164
|
+
if (params.maxValue !== undefined) data.maxValue = params.maxValue;
|
|
165
|
+
|
|
166
|
+
const result = await client.request<unknown>('POST', '/api/public/score-configs', data);
|
|
167
|
+
return JSON.stringify(result, null, 2);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function exportScoreConfigs(outputFile: string): Promise<string> {
|
|
171
|
+
const configsRaw = await listScoreConfigs();
|
|
172
|
+
const configs = JSON.parse(configsRaw) as Record<string, unknown>[];
|
|
173
|
+
|
|
174
|
+
const exported = configs.map((c) => ({
|
|
175
|
+
name: c.name,
|
|
176
|
+
dataType: c.dataType,
|
|
177
|
+
description: c.description,
|
|
178
|
+
categories: c.categories,
|
|
179
|
+
minValue: c.minValue,
|
|
180
|
+
maxValue: c.maxValue,
|
|
181
|
+
metadata: {
|
|
182
|
+
id: c.id,
|
|
183
|
+
createdAt: c.createdAt,
|
|
184
|
+
updatedAt: c.updatedAt,
|
|
185
|
+
projectId: c.projectId,
|
|
186
|
+
isArchived: c.isArchived,
|
|
187
|
+
},
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
const exportData = {
|
|
191
|
+
version: '1.0',
|
|
192
|
+
exportedAt: nowISO(),
|
|
193
|
+
totalConfigs: exported.length,
|
|
194
|
+
configs: exported,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const json = JSON.stringify(exportData, null, 2);
|
|
198
|
+
if (outputFile) {
|
|
199
|
+
writeFileSync(outputFile, json, 'utf-8');
|
|
200
|
+
}
|
|
201
|
+
return json;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function importScoreConfigs(
|
|
205
|
+
importFile: string,
|
|
206
|
+
options?: { allowDuplicates?: boolean; selectedConfigs?: string[] },
|
|
207
|
+
): Promise<string> {
|
|
208
|
+
let importData: Record<string, unknown>;
|
|
209
|
+
try {
|
|
210
|
+
importData = JSON.parse(readFileSync(importFile, 'utf-8')) as Record<string, unknown>;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
return `❌ Failed to read import file: ${e}`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const configsList = Array.isArray(importData)
|
|
216
|
+
? importData
|
|
217
|
+
: (importData.configs as unknown[]) ?? [];
|
|
218
|
+
|
|
219
|
+
if (!configsList.length) return '❌ No score configs found in import file.';
|
|
220
|
+
|
|
221
|
+
const existingRaw = await listScoreConfigs();
|
|
222
|
+
const existing = JSON.parse(existingRaw) as Record<string, unknown>[];
|
|
223
|
+
const existingNames = new Set(existing.map((c) => String(c.name).toLowerCase()));
|
|
224
|
+
|
|
225
|
+
const selected = options?.selectedConfigs
|
|
226
|
+
? new Set(options.selectedConfigs.map((n) => n.toLowerCase()))
|
|
227
|
+
: null;
|
|
228
|
+
|
|
229
|
+
const results: string[] = [];
|
|
230
|
+
let imported = 0;
|
|
231
|
+
let skipped = 0;
|
|
232
|
+
|
|
233
|
+
for (const raw of configsList) {
|
|
234
|
+
const cfg = raw as Record<string, unknown>;
|
|
235
|
+
const name = String(cfg.name);
|
|
236
|
+
|
|
237
|
+
if (selected && !selected.has(name.toLowerCase())) continue;
|
|
238
|
+
if (existingNames.has(name.toLowerCase()) && !options?.allowDuplicates) {
|
|
239
|
+
results.push(` ⏭️ '${name}' — skipped (duplicate)`);
|
|
240
|
+
skipped++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const result = await createScoreConfig({
|
|
246
|
+
name,
|
|
247
|
+
dataType: cfg.dataType as 'NUMERIC' | 'CATEGORICAL' | 'BOOLEAN',
|
|
248
|
+
description: cfg.description as string | undefined,
|
|
249
|
+
categories: cfg.categories as Array<{ label: string; value: number }> | undefined,
|
|
250
|
+
minValue: cfg.minValue as number | undefined,
|
|
251
|
+
maxValue: cfg.maxValue as number | undefined,
|
|
252
|
+
});
|
|
253
|
+
const parsed = JSON.parse(result);
|
|
254
|
+
results.push(` ✅ '${name}' (ID: ${parsed.id ?? 'created'})`);
|
|
255
|
+
imported++;
|
|
256
|
+
} catch (e) {
|
|
257
|
+
results.push(` ❌ '${name}' — ${e}`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return [
|
|
262
|
+
`📁 Import: ${importFile}`,
|
|
263
|
+
`📊 Summary: ${imported} imported, ${skipped} skipped`,
|
|
264
|
+
...results,
|
|
265
|
+
].join('\n');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ─── Score Config Caching / Apply ───────────────────────────────────
|
|
269
|
+
|
|
270
|
+
export async function applyScoreConfig(
|
|
271
|
+
configNameOrId: string,
|
|
272
|
+
targetType: string,
|
|
273
|
+
targetId: string,
|
|
274
|
+
value: number,
|
|
275
|
+
observationId?: string,
|
|
276
|
+
comment?: string,
|
|
277
|
+
): Promise<string> {
|
|
278
|
+
// Resolve config: try by ID first, then search by name
|
|
279
|
+
let configId: string | undefined;
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
const configJson = await getScoreConfig(configNameOrId);
|
|
283
|
+
const config = JSON.parse(configJson);
|
|
284
|
+
if (config.id) configId = config.id;
|
|
285
|
+
} catch {
|
|
286
|
+
// Try listing and searching by name
|
|
287
|
+
const allJson = await listScoreConfigs();
|
|
288
|
+
const all = JSON.parse(allJson) as Record<string, unknown>[];
|
|
289
|
+
const match = all.find(
|
|
290
|
+
(c) => String(c.name).toLowerCase() === configNameOrId.toLowerCase(),
|
|
291
|
+
);
|
|
292
|
+
if (match) configId = String(match.id);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (!configId) {
|
|
296
|
+
return JSON.stringify({ error: `Score config '${configNameOrId}' not found` });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return createScoreForTarget({
|
|
300
|
+
targetType: targetType as 'trace' | 'session',
|
|
301
|
+
targetId,
|
|
302
|
+
configId,
|
|
303
|
+
value,
|
|
304
|
+
observationId,
|
|
305
|
+
comment,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ─── Presets ────────────────────────────────────────────────────────
|
|
310
|
+
|
|
311
|
+
export function getBuiltInPresets(): ScoreConfig[] {
|
|
312
|
+
return BUILT_IN_PRESETS.map((p) => ({ ...p }));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
export async function installPreset(
|
|
316
|
+
name: string,
|
|
317
|
+
checkDuplicates = true,
|
|
318
|
+
): Promise<string> {
|
|
319
|
+
const preset = BUILT_IN_PRESETS.find((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
320
|
+
if (!preset) {
|
|
321
|
+
return JSON.stringify({ error: `Preset '${name}' not found` });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (checkDuplicates) {
|
|
325
|
+
const existingRaw = await listScoreConfigs();
|
|
326
|
+
const existing = JSON.parse(existingRaw) as Record<string, unknown>[];
|
|
327
|
+
const dup = existing.find((c) => String(c.name).toLowerCase() === preset.name.toLowerCase());
|
|
328
|
+
if (dup) {
|
|
329
|
+
return JSON.stringify({
|
|
330
|
+
warning: `Score config '${preset.name}' already exists (ID: ${dup.id})`,
|
|
331
|
+
existingId: dup.id,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return createScoreConfig({
|
|
337
|
+
name: preset.name,
|
|
338
|
+
dataType: preset.dataType,
|
|
339
|
+
description: preset.description,
|
|
340
|
+
categories: preset.categories,
|
|
341
|
+
minValue: preset.minValue,
|
|
342
|
+
maxValue: preset.maxValue,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ─── Formatters ─────────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
export function formatScoresTable(json: unknown): string {
|
|
349
|
+
try {
|
|
350
|
+
const raw = typeof json === 'string' ? JSON.parse(json) : json;
|
|
351
|
+
const scores: Record<string, unknown>[] = Array.isArray(raw)
|
|
352
|
+
? raw
|
|
353
|
+
: (raw as Record<string, unknown>).data as Record<string, unknown>[] ?? [];
|
|
354
|
+
|
|
355
|
+
if (!scores.length) return 'No scores found.';
|
|
356
|
+
|
|
357
|
+
const headers = ['ID', 'Name', 'Value', 'Created', 'Trace ID'];
|
|
358
|
+
const rows = scores.map((s) => [
|
|
359
|
+
trunc(String(s.id ?? 'N/A'), 20),
|
|
360
|
+
trunc(String(s.name ?? 'N/A'), 20),
|
|
361
|
+
String(s.value ?? 'N/A'),
|
|
362
|
+
String(s.createdAt ?? 'N/A').slice(0, 16),
|
|
363
|
+
trunc(String(s.traceId ?? 'N/A'), 20),
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
return formatTable(headers, rows) + `\nTotal scores: ${scores.length}`;
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return `Error formatting scores: ${e}`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function formatScoreConfigsTable(json: unknown): string {
|
|
373
|
+
try {
|
|
374
|
+
const raw = typeof json === 'string' ? JSON.parse(json) : json;
|
|
375
|
+
const configs: Record<string, unknown>[] = Array.isArray(raw)
|
|
376
|
+
? raw
|
|
377
|
+
: (raw as Record<string, unknown>).data as Record<string, unknown>[] ?? [];
|
|
378
|
+
|
|
379
|
+
if (!configs.length) return 'No score configs found.';
|
|
380
|
+
|
|
381
|
+
const headers = ['ID', 'Name', 'Data Type', 'Description', 'Created'];
|
|
382
|
+
const rows = configs.map((c) => [
|
|
383
|
+
trunc(String(c.id ?? 'N/A'), 20),
|
|
384
|
+
trunc(String(c.name ?? 'N/A'), 20),
|
|
385
|
+
String(c.dataType ?? 'N/A'),
|
|
386
|
+
trunc(String(c.description ?? 'N/A'), 35),
|
|
387
|
+
String(c.createdAt ?? 'N/A').slice(0, 16),
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
return formatTable(headers, rows) + `\nTotal score configs: ${configs.length}`;
|
|
391
|
+
} catch (e) {
|
|
392
|
+
return `Error formatting score configs: ${e}`;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ─── Built-in Presets ───────────────────────────────────────────────
|
|
397
|
+
|
|
398
|
+
const BUILT_IN_PRESETS: ScoreConfig[] = [
|
|
399
|
+
// Narrative & Storytelling
|
|
400
|
+
{ name: 'Narrative Coherence', dataType: 'CATEGORICAL', description: 'Evaluates how well story elements connect and flow together logically.', categories: [{ label: 'Incoherent', value: 1 }, { label: 'Loosely Connected', value: 2 }, { label: 'Coherent', value: 3 }, { label: 'Well-Structured', value: 4 }, { label: 'Masterfully Woven', value: 5 }] },
|
|
401
|
+
{ name: 'Character Development', dataType: 'CATEGORICAL', description: 'Measures the depth and growth of characters throughout the narrative.', categories: [{ label: 'Flat/Static', value: 1 }, { label: 'Basic Development', value: 2 }, { label: 'Moderate Growth', value: 3 }, { label: 'Rich Development', value: 4 }, { label: 'Complex Evolution', value: 5 }] },
|
|
402
|
+
{ name: 'Emotional Resonance', dataType: 'CATEGORICAL', description: 'Evaluates the emotional impact and connection with the audience.', categories: [{ label: 'No Emotional Impact', value: 1 }, { label: 'Mild Resonance', value: 2 }, { label: 'Moderate Impact', value: 3 }, { label: 'Strong Emotional Connection', value: 4 }, { label: 'Deeply Moving', value: 5 }] },
|
|
403
|
+
{ name: 'Originality', dataType: 'CATEGORICAL', description: 'Evaluates the uniqueness and freshness of the ideas or expression.', categories: [{ label: 'Unoriginal', value: 1 }, { label: 'Low Originality', value: 2 }, { label: 'Moderate Originality', value: 3 }, { label: 'High Originality', value: 4 }, { label: 'Highly Original', value: 5 }] },
|
|
404
|
+
{ name: 'Thematic Depth', dataType: 'CATEGORICAL', description: 'Measures the depth and sophistication of underlying themes.', categories: [{ label: 'Surface Level', value: 1 }, { label: 'Basic Themes', value: 2 }, { label: 'Developed Themes', value: 3 }, { label: 'Rich Thematic Content', value: 4 }, { label: 'Profound Depth', value: 5 }] },
|
|
405
|
+
// AI Response Evaluation
|
|
406
|
+
{ name: 'Helpfulness', dataType: 'CATEGORICAL', description: 'Measures how well the response addresses the user\'s needs.', categories: [{ label: 'Not Helpful', value: 1 }, { label: 'Slightly Helpful', value: 2 }, { label: 'Moderately Helpful', value: 3 }, { label: 'Very Helpful', value: 4 }, { label: 'Extremely Helpful', value: 5 }] },
|
|
407
|
+
{ name: 'Accuracy', dataType: 'CATEGORICAL', description: 'Evaluates the factual correctness and precision of information.', categories: [{ label: 'Inaccurate', value: 1 }, { label: 'Mostly Inaccurate', value: 2 }, { label: 'Partially Accurate', value: 3 }, { label: 'Mostly Accurate', value: 4 }, { label: 'Completely Accurate', value: 5 }] },
|
|
408
|
+
{ name: 'Safety', dataType: 'CATEGORICAL', description: 'Assesses whether the content is safe and free from harmful elements.', categories: [{ label: 'Unsafe/Harmful', value: 1 }, { label: 'Potentially Unsafe', value: 2 }, { label: 'Neutral/Safe', value: 3 }, { label: 'Very Safe', value: 4 }, { label: 'Exemplarily Safe', value: 5 }] },
|
|
409
|
+
{ name: 'Relevance', dataType: 'CATEGORICAL', description: 'Measures how well the response relates to the specific query.', categories: [{ label: 'Irrelevant', value: 1 }, { label: 'Slightly Relevant', value: 2 }, { label: 'Moderately Relevant', value: 3 }, { label: 'Highly Relevant', value: 4 }, { label: 'Perfectly Relevant', value: 5 }] },
|
|
410
|
+
{ name: 'Completeness', dataType: 'CATEGORICAL', description: 'Evaluates whether the response fully addresses all aspects.', categories: [{ label: 'Incomplete', value: 1 }, { label: 'Partially Complete', value: 2 }, { label: 'Mostly Complete', value: 3 }, { label: 'Very Complete', value: 4 }, { label: 'Comprehensive', value: 5 }] },
|
|
411
|
+
// General Content
|
|
412
|
+
{ name: 'Clarity', dataType: 'CATEGORICAL', description: 'Measures how easy it is to understand the content.', categories: [{ label: 'Unclear', value: 1 }, { label: 'Moderately Clear', value: 2 }, { label: 'Clear', value: 3 }, { label: 'Very Clear', value: 4 }, { label: 'Excellent Clarity', value: 5 }] },
|
|
413
|
+
{ name: 'Engagement', dataType: 'CATEGORICAL', description: 'Evaluates how well the content captures attention.', categories: [{ label: 'Disengaging', value: 1 }, { label: 'Slightly Engaging', value: 2 }, { label: 'Engaging', value: 3 }, { label: 'Very Engaging', value: 4 }, { label: 'Highly Engaging', value: 5 }] },
|
|
414
|
+
{ name: 'Tone Appropriateness', dataType: 'CATEGORICAL', description: 'Assesses whether the tone matches context and audience expectations.', categories: [{ label: 'Inappropriate Tone', value: 1 }, { label: 'Somewhat Inappropriate', value: 2 }, { label: 'Acceptable Tone', value: 3 }, { label: 'Well-Matched Tone', value: 4 }, { label: 'Perfect Tone', value: 5 }] },
|
|
415
|
+
{ name: 'Conciseness', dataType: 'CATEGORICAL', description: 'Evaluates the efficiency of communication.', categories: [{ label: 'Verbose/Wordy', value: 1 }, { label: 'Somewhat Verbose', value: 2 }, { label: 'Balanced', value: 3 }, { label: 'Concise', value: 4 }, { label: 'Perfectly Concise', value: 5 }] },
|
|
416
|
+
// Technical Quality
|
|
417
|
+
{ name: 'Structure & Organization', dataType: 'CATEGORICAL', description: 'Evaluates the logical organization and structural quality.', categories: [{ label: 'Disorganized', value: 1 }, { label: 'Basic Structure', value: 2 }, { label: 'Well-Organized', value: 3 }, { label: 'Excellent Structure', value: 4 }, { label: 'Masterful Organization', value: 5 }] },
|
|
418
|
+
{ name: 'Language Quality', dataType: 'CATEGORICAL', description: 'Assesses grammar, vocabulary usage, and linguistic competence.', categories: [{ label: 'Poor Language', value: 1 }, { label: 'Fair Language', value: 2 }, { label: 'Good Language', value: 3 }, { label: 'Excellent Language', value: 4 }, { label: 'Exceptional Language', value: 5 }] },
|
|
419
|
+
// Specialized
|
|
420
|
+
{ name: 'Critical Thinking', dataType: 'CATEGORICAL', description: 'Measures the depth of analysis, reasoning, and intellectual rigor.', categories: [{ label: 'No Critical Analysis', value: 1 }, { label: 'Basic Analysis', value: 2 }, { label: 'Sound Reasoning', value: 3 }, { label: 'Strong Critical Thinking', value: 4 }, { label: 'Exceptional Analysis', value: 5 }] },
|
|
421
|
+
{ name: 'Evidence Support', dataType: 'CATEGORICAL', description: 'Evaluates the use and quality of supporting evidence.', categories: [{ label: 'No Evidence', value: 1 }, { label: 'Weak Evidence', value: 2 }, { label: 'Adequate Evidence', value: 3 }, { label: 'Strong Evidence', value: 4 }, { label: 'Compelling Evidence', value: 5 }] },
|
|
422
|
+
{ name: 'Innovation', dataType: 'CATEGORICAL', description: 'Assesses creativity and novel approaches in problem-solving.', categories: [{ label: 'Conventional', value: 1 }, { label: 'Slightly Creative', value: 2 }, { label: 'Moderately Innovative', value: 3 }, { label: 'Highly Innovative', value: 4 }, { label: 'Groundbreaking', value: 5 }] },
|
|
423
|
+
// Numeric
|
|
424
|
+
{ name: 'Overall Quality', dataType: 'NUMERIC', description: 'General numeric assessment of overall content quality.', minValue: 0, maxValue: 10 },
|
|
425
|
+
{ name: 'Performance Score', dataType: 'NUMERIC', description: 'Numeric performance evaluation score.', minValue: 0, maxValue: 100 },
|
|
426
|
+
// Boolean
|
|
427
|
+
{ name: 'Meets Requirements', dataType: 'BOOLEAN', description: 'Binary assessment of whether content meets specified requirements.' },
|
|
428
|
+
];
|
|
429
|
+
|
|
430
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
431
|
+
|
|
432
|
+
function shouldContinuePagination(data: Record<string, unknown>, currentPage: number): boolean {
|
|
433
|
+
const meta = data.meta as Record<string, unknown> | undefined;
|
|
434
|
+
if (meta?.totalPages) {
|
|
435
|
+
return (meta.page as number ?? currentPage) < (meta.totalPages as number);
|
|
436
|
+
}
|
|
437
|
+
if (data.hasNextPage) return true;
|
|
438
|
+
if (data.totalPages && currentPage < (data.totalPages as number)) return true;
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function trunc(s: string, len: number): string {
|
|
443
|
+
return s.length > len ? s.slice(0, len - 3) + '...' : s;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function formatTable(headers: string[], rows: string[][]): string {
|
|
447
|
+
const widths = headers.map((h, i) =>
|
|
448
|
+
Math.max(h.length, ...rows.map((r) => (r[i] ?? '').length)),
|
|
449
|
+
);
|
|
450
|
+
const sep = '+' + widths.map((w) => '-'.repeat(w + 2)).join('+') + '+';
|
|
451
|
+
const hdr = '| ' + headers.map((h, i) => h.padEnd(widths[i])).join(' | ') + ' |';
|
|
452
|
+
const body = rows.map(
|
|
453
|
+
(r) => '| ' + r.map((c, i) => (c ?? '').padEnd(widths[i])).join(' | ') + ' |',
|
|
454
|
+
);
|
|
455
|
+
return [sep, hdr, sep, ...body, sep].join('\n');
|
|
456
|
+
}
|