@turingpulse/sdk 1.0.1
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/.github/dependabot.yml +38 -0
- package/.github/workflows/ci.yml +246 -0
- package/.github/workflows/framework-compat.yml +169 -0
- package/.github/workflows/security.yml +336 -0
- package/CHANGELOG.md +29 -0
- package/LICENSE +13 -0
- package/MIGRATION.md +30 -0
- package/README.md +221 -0
- package/dist/attachments.d.ts +28 -0
- package/dist/attachments.d.ts.map +1 -0
- package/dist/attachments.js +59 -0
- package/dist/attachments.js.map +1 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +126 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +163 -0
- package/dist/context.js.map +1 -0
- package/dist/decorators.d.ts +6 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +52 -0
- package/dist/decorators.js.map +1 -0
- package/dist/deploy.d.ts +89 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +203 -0
- package/dist/deploy.js.map +1 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/eventBuilder.d.ts +21 -0
- package/dist/eventBuilder.d.ts.map +1 -0
- package/dist/eventBuilder.js +127 -0
- package/dist/eventBuilder.js.map +1 -0
- package/dist/fingerprint.d.ts +158 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +339 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/governance.d.ts +47 -0
- package/dist/governance.d.ts.map +1 -0
- package/dist/governance.js +104 -0
- package/dist/governance.js.map +1 -0
- package/dist/http.d.ts +62 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +181 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/instrumentation.d.ts +40 -0
- package/dist/instrumentation.d.ts.map +1 -0
- package/dist/instrumentation.js +31 -0
- package/dist/instrumentation.js.map +1 -0
- package/dist/integrations/mastra.d.ts +64 -0
- package/dist/integrations/mastra.d.ts.map +1 -0
- package/dist/integrations/mastra.js +256 -0
- package/dist/integrations/mastra.js.map +1 -0
- package/dist/kpi.d.ts +21 -0
- package/dist/kpi.d.ts.map +1 -0
- package/dist/kpi.js +83 -0
- package/dist/kpi.js.map +1 -0
- package/dist/llmDetector.d.ts +22 -0
- package/dist/llmDetector.d.ts.map +1 -0
- package/dist/llmDetector.js +269 -0
- package/dist/llmDetector.js.map +1 -0
- package/dist/plugin.d.ts +33 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +312 -0
- package/dist/plugin.js.map +1 -0
- package/dist/registry.d.ts +13 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +18 -0
- package/dist/registry.js.map +1 -0
- package/dist/tracing.d.ts +10 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +30 -0
- package/dist/tracing.js.map +1 -0
- package/dist/triggerState.d.ts +5 -0
- package/dist/triggerState.d.ts.map +1 -0
- package/dist/triggerState.js +19 -0
- package/dist/triggerState.js.map +1 -0
- package/dist/utils.d.ts +27 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +72 -0
- package/dist/utils.js.map +1 -0
- package/package.json +37 -0
- package/packages/anthropic/package.json +16 -0
- package/packages/anthropic/src/index.ts +5 -0
- package/packages/anthropic/src/wrapper.ts +102 -0
- package/packages/anthropic/tsconfig.build.json +20 -0
- package/packages/langchain/package.json +16 -0
- package/packages/langchain/src/index.ts +7 -0
- package/packages/langchain/src/wrapper.ts +51 -0
- package/packages/mastra/package.json +17 -0
- package/packages/mastra/src/index.ts +8 -0
- package/packages/mastra/src/wrapper.ts +301 -0
- package/packages/openai/package.json +16 -0
- package/packages/openai/src/index.ts +8 -0
- package/packages/openai/src/wrapper.ts +103 -0
- package/packages/openai/tsconfig.build.json +20 -0
- package/packages/openclaw/openclaw.plugin.json +100 -0
- package/packages/openclaw/package.json +41 -0
- package/packages/openclaw/src/buffer.ts +99 -0
- package/packages/openclaw/src/config.ts +139 -0
- package/packages/openclaw/src/hooks/governance.ts +267 -0
- package/packages/openclaw/src/hooks/lifecycle.ts +75 -0
- package/packages/openclaw/src/hooks/telemetry.ts +207 -0
- package/packages/openclaw/src/index.ts +91 -0
- package/packages/openclaw/src/mapper.ts +233 -0
- package/packages/openclaw/src/session-tracker.ts +181 -0
- package/packages/openclaw/src/types.ts +220 -0
- package/packages/openclaw/tests/buffer.test.ts +148 -0
- package/packages/openclaw/tests/config.test.ts +122 -0
- package/packages/openclaw/tests/governance.test.ts +232 -0
- package/packages/openclaw/tests/mapper.test.ts +242 -0
- package/packages/openclaw/tests/session-tracker.test.ts +124 -0
- package/packages/openclaw/tsconfig.json +18 -0
- package/packages/openclaw/vitest.config.ts +8 -0
- package/packages/vercel-ai/package.json +16 -0
- package/packages/vercel-ai/src/index.ts +5 -0
- package/packages/vercel-ai/src/wrapper.ts +49 -0
- package/scripts/bump-version.sh +58 -0
- package/scripts/update-readme-compat.mjs +151 -0
- package/src/__tests__/fingerprint.test.ts +328 -0
- package/src/attachments.ts +88 -0
- package/src/config.ts +164 -0
- package/src/context.ts +258 -0
- package/src/decorators.ts +61 -0
- package/src/deploy.ts +260 -0
- package/src/errors.ts +44 -0
- package/src/eventBuilder.ts +153 -0
- package/src/fingerprint.ts +421 -0
- package/src/governance.ts +156 -0
- package/src/http.ts +241 -0
- package/src/index.ts +57 -0
- package/src/instrumentation.ts +68 -0
- package/src/integrations/mastra.ts +335 -0
- package/src/kpi.ts +112 -0
- package/src/llmDetector.ts +330 -0
- package/src/plugin.ts +384 -0
- package/src/registry.ts +27 -0
- package/src/tracing.ts +39 -0
- package/src/triggerState.ts +27 -0
- package/src/utils.ts +78 -0
- package/tests/compat/anthropic.test.ts +61 -0
- package/tests/compat/cohere.test.ts +57 -0
- package/tests/compat/google-genai.test.ts +61 -0
- package/tests/compat/langchain-openai.test.ts +41 -0
- package/tests/compat/langchain.test.ts +64 -0
- package/tests/compat/mistral.test.ts +58 -0
- package/tests/compat/openai.test.ts +71 -0
- package/tests/compat/vercel-ai.test.ts +56 -0
- package/tests/plugins/anthropic-wrapper.test.ts +120 -0
- package/tests/plugins/langchain-wrapper.test.ts +128 -0
- package/tests/plugins/openai-wrapper.test.ts +165 -0
- package/tsconfig.json +21 -0
- package/vitest.config.ts +9 -0
package/src/kpi.ts
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { ExecutionContext } from './context';
|
|
2
|
+
|
|
3
|
+
export type KPIComparator = 'gt' | 'gte' | 'lt' | 'lte' | 'eq';
|
|
4
|
+
|
|
5
|
+
export type KPIValueFn = (context: ExecutionContext) => number | undefined | null;
|
|
6
|
+
|
|
7
|
+
export interface KPIConfig {
|
|
8
|
+
kpiId: string;
|
|
9
|
+
value?: number | KPIValueFn;
|
|
10
|
+
useDuration?: boolean;
|
|
11
|
+
fromResultPath?: string;
|
|
12
|
+
labels?: Record<string, string>;
|
|
13
|
+
alertThreshold?: number;
|
|
14
|
+
comparator?: KPIComparator;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface KPIResult {
|
|
19
|
+
config: KPIConfig;
|
|
20
|
+
value: number;
|
|
21
|
+
alert: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function evaluateKpis(configs: ReadonlyArray<KPIConfig>, context: ExecutionContext): KPIResult[] {
|
|
25
|
+
const results: KPIResult[] = [];
|
|
26
|
+
for (const cfg of configs) {
|
|
27
|
+
const value = evaluateKpi(cfg, context);
|
|
28
|
+
if (value === undefined || Number.isNaN(value)) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
results.push({
|
|
32
|
+
config: cfg,
|
|
33
|
+
value,
|
|
34
|
+
alert: isAlert(cfg, value),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function evaluateKpi(config: KPIConfig, context: ExecutionContext): number | undefined {
|
|
41
|
+
if (config.useDuration && typeof context.durationMs === 'number') {
|
|
42
|
+
return context.durationMs;
|
|
43
|
+
}
|
|
44
|
+
if (typeof config.value === 'number') {
|
|
45
|
+
return config.value;
|
|
46
|
+
}
|
|
47
|
+
if (typeof config.value === 'function') {
|
|
48
|
+
const output = config.value(context);
|
|
49
|
+
return output === undefined || output === null ? undefined : Number(output);
|
|
50
|
+
}
|
|
51
|
+
if (config.fromResultPath) {
|
|
52
|
+
const extracted = extractFromResult(context.result, config.fromResultPath);
|
|
53
|
+
return extracted === undefined || extracted === null ? undefined : Number(extracted);
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isAlert(config: KPIConfig, value: number): boolean {
|
|
59
|
+
if (config.alertThreshold === undefined) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
const threshold = config.alertThreshold;
|
|
63
|
+
const comparator = config.comparator ?? 'gt';
|
|
64
|
+
switch (comparator) {
|
|
65
|
+
case 'gt':
|
|
66
|
+
return value > threshold;
|
|
67
|
+
case 'gte':
|
|
68
|
+
return value >= threshold;
|
|
69
|
+
case 'lt':
|
|
70
|
+
return value < threshold;
|
|
71
|
+
case 'lte':
|
|
72
|
+
return value <= threshold;
|
|
73
|
+
case 'eq':
|
|
74
|
+
return value === threshold;
|
|
75
|
+
default:
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
import { FORBIDDEN_KEYS } from './utils';
|
|
81
|
+
|
|
82
|
+
function extractFromResult(result: unknown, path: string): unknown {
|
|
83
|
+
if (!result) {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
return path.split('.').reduce<unknown>((current, key) => {
|
|
87
|
+
if (current == null || FORBIDDEN_KEYS.has(key)) {
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
if (typeof current === 'object' && key in (current as Record<string, unknown>)) {
|
|
91
|
+
return (current as Record<string, unknown>)[key];
|
|
92
|
+
}
|
|
93
|
+
return undefined;
|
|
94
|
+
}, result);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function kpiMetadata(result: KPIResult): Record<string, string> {
|
|
98
|
+
const metadata: Record<string, string> = {
|
|
99
|
+
[`metric.${result.config.kpiId}`]: String(result.value),
|
|
100
|
+
};
|
|
101
|
+
if (result.alert) {
|
|
102
|
+
metadata[`metric.alert.${result.config.kpiId}`] = 'true';
|
|
103
|
+
}
|
|
104
|
+
if (result.config.labels) {
|
|
105
|
+
for (const [key, value] of Object.entries(result.config.labels)) {
|
|
106
|
+
metadata[`metric.${result.config.kpiId}.${key}`] = String(value);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return metadata;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automatic detection of LLM calls and extraction of metadata.
|
|
3
|
+
*
|
|
4
|
+
* Uses a data-driven provider registry instead of per-provider methods,
|
|
5
|
+
* making it trivial to add new providers without modifying detection logic.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface LLMCallInfo {
|
|
9
|
+
provider: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
prompt?: string;
|
|
12
|
+
config: Record<string, unknown>;
|
|
13
|
+
nodeType: string;
|
|
14
|
+
streaming: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Provider registry — add new providers by appending to PROVIDERS
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
interface ProviderSpec {
|
|
22
|
+
name: string;
|
|
23
|
+
qualnamePatterns: string[];
|
|
24
|
+
configKeys: string[];
|
|
25
|
+
methodPatterns?: string[];
|
|
26
|
+
methodContext?: string;
|
|
27
|
+
systemParam?: string;
|
|
28
|
+
/** messages | firstArg | inputKwarg | stateMessages | inputsKwarg | messageKwarg | queryKwarg | bedrock */
|
|
29
|
+
promptStrategy?: string;
|
|
30
|
+
modelKey?: string;
|
|
31
|
+
altModelKey?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PROVIDERS: ProviderSpec[] = [
|
|
35
|
+
{
|
|
36
|
+
name: 'openai',
|
|
37
|
+
qualnamePatterns: ['chat.completions.create', 'completions.create',
|
|
38
|
+
'chatcompletion.create', 'completion.create', 'openai.chat'],
|
|
39
|
+
configKeys: ['model', 'temperature', 'max_tokens', 'top_p',
|
|
40
|
+
'frequency_penalty', 'presence_penalty', 'stop'],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'anthropic',
|
|
44
|
+
qualnamePatterns: ['messages.create', 'anthropic.messages', 'claude'],
|
|
45
|
+
configKeys: ['model', 'temperature', 'max_tokens', 'top_p', 'stop_sequences'],
|
|
46
|
+
systemParam: 'system',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'google',
|
|
50
|
+
qualnamePatterns: ['generatecontent', 'generativemodel', 'google.generativeai', 'gemini'],
|
|
51
|
+
configKeys: ['model', 'temperature', 'maxOutputTokens', 'topP', 'topK'],
|
|
52
|
+
promptStrategy: 'firstArg',
|
|
53
|
+
altModelKey: 'modelName',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'vertexai',
|
|
57
|
+
qualnamePatterns: ['vertexai', 'palm'],
|
|
58
|
+
configKeys: ['model', 'temperature', 'maxOutputTokens', 'topP', 'topK', 'candidateCount'],
|
|
59
|
+
promptStrategy: 'firstArg',
|
|
60
|
+
altModelKey: 'modelName',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'langchain',
|
|
64
|
+
qualnamePatterns: ['langchain', 'chatmodel', 'llmchain', 'runnablesequence'],
|
|
65
|
+
configKeys: ['modelName', 'temperature', 'maxTokens', 'modelKwargs'],
|
|
66
|
+
methodPatterns: ['invoke', 'ainvoke', 'batch', 'stream'],
|
|
67
|
+
methodContext: 'chain',
|
|
68
|
+
promptStrategy: 'inputKwarg',
|
|
69
|
+
modelKey: 'modelName',
|
|
70
|
+
altModelKey: 'model',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'langgraph',
|
|
74
|
+
qualnamePatterns: ['langgraph', 'stategraph', 'compiledgraph', 'messagesgraph'],
|
|
75
|
+
configKeys: ['modelName', 'temperature', 'maxTokens', 'recursionLimit', 'configurable'],
|
|
76
|
+
methodPatterns: ['invoke', 'ainvoke', 'stream', 'astream', 'batch'],
|
|
77
|
+
methodContext: 'graph',
|
|
78
|
+
promptStrategy: 'stateMessages',
|
|
79
|
+
modelKey: 'modelName',
|
|
80
|
+
altModelKey: 'model',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'crewai',
|
|
84
|
+
qualnamePatterns: ['crewai', 'crew.kickoff', 'agent.execute', 'task.execute'],
|
|
85
|
+
configKeys: ['model', 'temperature', 'maxTokens', 'verbose', 'memory'],
|
|
86
|
+
methodPatterns: ['kickoff', 'kickoffAsync', 'executeTask'],
|
|
87
|
+
promptStrategy: 'inputsKwarg',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: 'autogen',
|
|
91
|
+
qualnamePatterns: ['autogen', 'conversableagent', 'assistantagent', 'useragent', 'groupchat'],
|
|
92
|
+
configKeys: ['model', 'temperature', 'maxTokens', 'timeout', 'cacheSeed'],
|
|
93
|
+
methodPatterns: ['initiateChat', 'generateReply', 'run'],
|
|
94
|
+
promptStrategy: 'messageKwarg',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: 'llamaindex',
|
|
98
|
+
qualnamePatterns: ['llamaindex', 'llama_index', 'queryengine', 'chatengine', 'vectorstoreindex'],
|
|
99
|
+
configKeys: ['model', 'temperature', 'maxTokens', 'contextWindow'],
|
|
100
|
+
methodPatterns: ['query', 'chat', 'retrieve', 'asQueryEngine'],
|
|
101
|
+
methodContext: 'index|engine',
|
|
102
|
+
promptStrategy: 'queryKwarg',
|
|
103
|
+
altModelKey: 'llm',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: 'bedrock',
|
|
107
|
+
qualnamePatterns: ['bedrock', 'invokemodel', 'invokemodelwithresponsestream', 'converse', 'conversestream'],
|
|
108
|
+
configKeys: ['modelId', 'temperature', 'maxTokens', 'topP', 'stopSequences'],
|
|
109
|
+
promptStrategy: 'bedrock',
|
|
110
|
+
modelKey: 'modelId',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'cohere',
|
|
114
|
+
qualnamePatterns: ['cohere.chat', 'cohere.generate', 'cohereclient'],
|
|
115
|
+
configKeys: ['model', 'temperature', 'max_tokens', 'p', 'k'],
|
|
116
|
+
systemParam: 'preamble',
|
|
117
|
+
promptStrategy: 'messageKwarg',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'mistral',
|
|
121
|
+
qualnamePatterns: ['mistral.chat', 'mistral.complete', 'mistralclient'],
|
|
122
|
+
configKeys: ['model', 'temperature', 'max_tokens', 'top_p'],
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Detection engine
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function matchesProvider(spec: ProviderSpec, qualname: string, funcName: string): boolean {
|
|
131
|
+
if (spec.qualnamePatterns.some((p) => qualname.includes(p))) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (spec.methodPatterns?.includes(funcName)) {
|
|
135
|
+
if (spec.methodContext) {
|
|
136
|
+
return spec.methodContext.split('|').some((ctx) => qualname.includes(ctx));
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
import {
|
|
144
|
+
extractConfigSubset as extractConfig,
|
|
145
|
+
extractPromptFromMessages as extractSystemPrompt,
|
|
146
|
+
} from './fingerprint';
|
|
147
|
+
|
|
148
|
+
function extractPrompt(
|
|
149
|
+
spec: ProviderSpec,
|
|
150
|
+
args: unknown[],
|
|
151
|
+
kwargs: Record<string, unknown>,
|
|
152
|
+
): string | undefined {
|
|
153
|
+
const strategy = spec.promptStrategy ?? 'messages';
|
|
154
|
+
|
|
155
|
+
if (strategy === 'messages') {
|
|
156
|
+
if (spec.systemParam) {
|
|
157
|
+
const sys = kwargs[spec.systemParam];
|
|
158
|
+
if (typeof sys === 'string') return sys;
|
|
159
|
+
}
|
|
160
|
+
return extractSystemPrompt(
|
|
161
|
+
(kwargs.messages as Array<{ role?: string; content?: string | unknown[] }>) ?? [],
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (strategy === 'firstArg') {
|
|
166
|
+
if (args.length > 0) {
|
|
167
|
+
if (typeof args[0] === 'string') return args[0];
|
|
168
|
+
if (Array.isArray(args[0])) return String(args[0]);
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (strategy === 'inputKwarg') {
|
|
174
|
+
if ('input' in kwargs) {
|
|
175
|
+
const val = kwargs.input;
|
|
176
|
+
if (typeof val === 'object' && val !== null && 'question' in val) {
|
|
177
|
+
return (val as { question: string }).question;
|
|
178
|
+
}
|
|
179
|
+
if (typeof val === 'string') return val;
|
|
180
|
+
}
|
|
181
|
+
if (args.length > 0 && typeof args[0] === 'string') return args[0];
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (strategy === 'stateMessages') {
|
|
186
|
+
if ('messages' in kwargs) {
|
|
187
|
+
return extractSystemPrompt(kwargs.messages as Array<{ role?: string; content?: string | unknown[] }>);
|
|
188
|
+
}
|
|
189
|
+
if (args.length > 0 && typeof args[0] === 'object' && args[0] !== null) {
|
|
190
|
+
const state = args[0] as Record<string, unknown>;
|
|
191
|
+
if ('messages' in state) {
|
|
192
|
+
return extractSystemPrompt(state.messages as Array<{ role?: string; content?: string | unknown[] }>);
|
|
193
|
+
}
|
|
194
|
+
if ('input' in state) return state.input as string;
|
|
195
|
+
}
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (strategy === 'inputsKwarg') {
|
|
200
|
+
if ('inputs' in kwargs) {
|
|
201
|
+
const inputs = kwargs.inputs;
|
|
202
|
+
if (typeof inputs === 'object' && inputs !== null) {
|
|
203
|
+
const obj = inputs as Record<string, unknown>;
|
|
204
|
+
return (obj.topic as string) || (obj.question as string) || String(inputs);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (args.length > 0 && typeof args[0] === 'object') return String(args[0]);
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (strategy === 'messageKwarg') {
|
|
212
|
+
if ('message' in kwargs && typeof kwargs.message === 'string') return kwargs.message;
|
|
213
|
+
if (args.length > 0 && typeof args[0] === 'string') return args[0];
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (strategy === 'queryKwarg') {
|
|
218
|
+
if (args.length > 0 && typeof args[0] === 'string') return args[0];
|
|
219
|
+
return (kwargs.query as string) ?? (kwargs.message as string) ?? undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (strategy === 'bedrock') {
|
|
223
|
+
return extractBedrockPrompt(kwargs);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function extractBedrockPrompt(kwargs: Record<string, unknown>): string | undefined {
|
|
230
|
+
const modelId = kwargs.modelId as string | undefined;
|
|
231
|
+
const body = kwargs.body;
|
|
232
|
+
if (body) {
|
|
233
|
+
try {
|
|
234
|
+
const bodyData: Record<string, unknown> =
|
|
235
|
+
typeof body === 'string' ? JSON.parse(body) : (body as Record<string, unknown>);
|
|
236
|
+
if (modelId?.toLowerCase().includes('anthropic')) {
|
|
237
|
+
return extractSystemPrompt(
|
|
238
|
+
bodyData.messages as Array<{ role?: string; content?: string | unknown[] }>,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return (bodyData.prompt as string) || (bodyData.inputText as string);
|
|
242
|
+
} catch {
|
|
243
|
+
// ignore parse errors
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if ('messages' in kwargs) {
|
|
247
|
+
const messages = kwargs.messages as Array<{ role?: string; content?: Array<{ text?: string }> }>;
|
|
248
|
+
if (messages?.length > 0) {
|
|
249
|
+
for (const msg of messages) {
|
|
250
|
+
if (msg.role === 'user' && msg.content?.length) {
|
|
251
|
+
return msg.content[0].text;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function extractModel(spec: ProviderSpec, kwargs: Record<string, unknown>): string | undefined {
|
|
260
|
+
const key = spec.modelKey ?? 'model';
|
|
261
|
+
return (kwargs[key] as string) ?? (spec.altModelKey ? (kwargs[spec.altModelKey] as string) : undefined);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ---------------------------------------------------------------------------
|
|
265
|
+
// Public API
|
|
266
|
+
// ---------------------------------------------------------------------------
|
|
267
|
+
|
|
268
|
+
export class LLMDetector {
|
|
269
|
+
static detectLLMCall(
|
|
270
|
+
funcName: string,
|
|
271
|
+
funcQualname: string,
|
|
272
|
+
args: unknown[],
|
|
273
|
+
kwargs: Record<string, unknown>,
|
|
274
|
+
): LLMCallInfo | undefined {
|
|
275
|
+
const qualnameLower = funcQualname.toLowerCase();
|
|
276
|
+
const funcNameLower = funcName.toLowerCase();
|
|
277
|
+
|
|
278
|
+
for (const spec of PROVIDERS) {
|
|
279
|
+
if (matchesProvider(spec, qualnameLower, funcNameLower)) {
|
|
280
|
+
const isStreaming = Boolean(
|
|
281
|
+
kwargs.stream || kwargs.stream_mode || kwargs.response_mode === 'streaming',
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
provider: spec.name,
|
|
285
|
+
model: extractModel(spec, kwargs),
|
|
286
|
+
prompt: extractPrompt(spec, args, kwargs),
|
|
287
|
+
config: extractConfig(kwargs, spec.configKeys),
|
|
288
|
+
nodeType: 'llm',
|
|
289
|
+
streaming: isStreaming,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const TOOL_PATTERNS = ['search', 'query', 'retrieve', 'fetch', 'callapi',
|
|
298
|
+
'execute', 'runtool', 'invoketool'];
|
|
299
|
+
|
|
300
|
+
export class ToolDetector {
|
|
301
|
+
static detectToolCall(
|
|
302
|
+
funcName: string,
|
|
303
|
+
funcQualname: string,
|
|
304
|
+
_args: unknown[],
|
|
305
|
+
_kwargs: Record<string, unknown>,
|
|
306
|
+
): string | undefined {
|
|
307
|
+
const fn = funcName.toLowerCase();
|
|
308
|
+
const qn = funcQualname.toLowerCase();
|
|
309
|
+
|
|
310
|
+
for (const pattern of TOOL_PATTERNS) {
|
|
311
|
+
if (fn.includes(pattern) || qn.includes(pattern)) return 'tool';
|
|
312
|
+
}
|
|
313
|
+
if (qn.includes('retriever') || qn.includes('vectorstore')) return 'retriever';
|
|
314
|
+
if (qn.includes('database') || qn.includes('sql')) return 'database';
|
|
315
|
+
return undefined;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export function detectNodeType(
|
|
320
|
+
funcName: string,
|
|
321
|
+
funcQualname: string,
|
|
322
|
+
args: unknown[],
|
|
323
|
+
kwargs: Record<string, unknown>,
|
|
324
|
+
): [string, LLMCallInfo | undefined] {
|
|
325
|
+
const llmInfo = LLMDetector.detectLLMCall(funcName, funcQualname, args, kwargs);
|
|
326
|
+
if (llmInfo) return ['llm', llmInfo];
|
|
327
|
+
const toolType = ToolDetector.detectToolCall(funcName, funcQualname, args, kwargs);
|
|
328
|
+
if (toolType) return [toolType, undefined];
|
|
329
|
+
return ['function', undefined];
|
|
330
|
+
}
|