llm-agent-runtime 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/README.md +5 -0
- package/dist/agent.d.ts +20 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +37 -0
- package/dist/create-runtime.d.ts +17 -0
- package/dist/create-runtime.d.ts.map +1 -0
- package/dist/create-runtime.js +197 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/llm.d.ts +17 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +65 -0
- package/dist/redis.d.ts +9 -0
- package/dist/redis.d.ts.map +1 -0
- package/dist/redis.js +18 -0
- package/dist/services/agent-runner.d.ts +17 -0
- package/dist/services/agent-runner.d.ts.map +1 -0
- package/dist/services/agent-runner.js +18 -0
- package/dist/services/chat-cache.d.ts +16 -0
- package/dist/services/chat-cache.d.ts.map +1 -0
- package/dist/services/chat-cache.js +70 -0
- package/dist/services/conversation.d.ts +7 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +56 -0
- package/dist/services/extract.d.ts +33 -0
- package/dist/services/extract.d.ts.map +1 -0
- package/dist/services/extract.js +64 -0
- package/dist/services/flow-router.d.ts +42 -0
- package/dist/services/flow-router.d.ts.map +1 -0
- package/dist/services/flow-router.js +167 -0
- package/dist/services/intent-embeddings.d.ts +17 -0
- package/dist/services/intent-embeddings.d.ts.map +1 -0
- package/dist/services/intent-embeddings.js +46 -0
- package/dist/services/intent-router.d.ts +21 -0
- package/dist/services/intent-router.d.ts.map +1 -0
- package/dist/services/intent-router.js +98 -0
- package/dist/types.d.ts +177 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/message.d.ts +9 -0
- package/dist/utils/message.d.ts.map +1 -0
- package/dist/utils/message.js +13 -0
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.d.ts.map +1 -0
- package/dist/utils/normalize.js +8 -0
- package/package.json +55 -0
package/README.md
ADDED
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
|
2
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
3
|
+
import type { BaseCheckpointSaver } from "@langchain/langgraph-checkpoint";
|
|
4
|
+
import type { IntentRegistry } from "./types.js";
|
|
5
|
+
export type AgentFactory = {
|
|
6
|
+
checkpointer: BaseCheckpointSaver;
|
|
7
|
+
createAgent: (phoneKey: string, intent?: string) => ReturnType<typeof createReactAgent>;
|
|
8
|
+
getAgentState: (threadId: string) => Promise<{
|
|
9
|
+
values: {
|
|
10
|
+
messages?: unknown[];
|
|
11
|
+
};
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
export declare function createAgentFactory(options: {
|
|
15
|
+
llm: BaseChatModel;
|
|
16
|
+
intentRegistry: IntentRegistry;
|
|
17
|
+
redisUrl?: string;
|
|
18
|
+
checkpointer?: BaseCheckpointSaver;
|
|
19
|
+
}): Promise<AgentFactory>;
|
|
20
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAGjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE,mBAAmB,CAAC;IAClC,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;IACxF,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,MAAM,EAAE;YAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAA;SAAE,CAAA;KAAE,CAAC,CAAC;CACpF,CAAC;AAEF,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,GAAG,EAAE,aAAa,CAAC;IACnB,cAAc,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,mBAAmB,CAAC;CACpC,GAAG,OAAO,CAAC,YAAY,CAAC,CAsCxB"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createReactAgent } from "@langchain/langgraph/prebuilt";
|
|
2
|
+
import { MemorySaver } from "@langchain/langgraph-checkpoint";
|
|
3
|
+
import { RedisSaver } from "@langchain/langgraph-checkpoint-redis";
|
|
4
|
+
export async function createAgentFactory(options) {
|
|
5
|
+
const checkpointer = options.checkpointer ??
|
|
6
|
+
(options.redisUrl
|
|
7
|
+
? await RedisSaver.fromUrl(options.redisUrl)
|
|
8
|
+
: new MemorySaver());
|
|
9
|
+
const fallbackTools = () => {
|
|
10
|
+
const first = Object.values(options.intentRegistry)[0];
|
|
11
|
+
return first?.tools("") ?? [];
|
|
12
|
+
};
|
|
13
|
+
const stateReader = createReactAgent({
|
|
14
|
+
llm: options.llm,
|
|
15
|
+
tools: fallbackTools(),
|
|
16
|
+
checkpointSaver: checkpointer,
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
checkpointer,
|
|
20
|
+
createAgent: (phoneKey, intent) => {
|
|
21
|
+
const tools = intent
|
|
22
|
+
? (options.intentRegistry[intent]?.tools(phoneKey) ?? [])
|
|
23
|
+
: fallbackTools();
|
|
24
|
+
return createReactAgent({
|
|
25
|
+
llm: options.llm,
|
|
26
|
+
tools,
|
|
27
|
+
checkpointSaver: checkpointer,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
getAgentState: async (threadId) => {
|
|
31
|
+
const state = await stateReader.getState({
|
|
32
|
+
configurable: { thread_id: threadId },
|
|
33
|
+
});
|
|
34
|
+
return state;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentRuntimeConfig, InvokeInput, InvokeResult } from "./types.js";
|
|
2
|
+
export type AgentRuntime = {
|
|
3
|
+
invoke: (input: InvokeInput) => Promise<InvokeResult>;
|
|
4
|
+
connect: () => Promise<void>;
|
|
5
|
+
disconnect: () => Promise<void>;
|
|
6
|
+
clearFlow: (threadId: string) => Promise<void>;
|
|
7
|
+
getCachedAnswer: (message: string) => Promise<string | undefined>;
|
|
8
|
+
setCachedAnswer: (message: string, answer: string) => Promise<void>;
|
|
9
|
+
/** Invalidate embedding cache after changing intent examples at runtime. */
|
|
10
|
+
invalidateIntentEmbeddings: () => void;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Factory for a reusable agent runtime.
|
|
14
|
+
* Engine pipeline: resolveFlow → runAgent → getTurnMessages → extract → finalize
|
|
15
|
+
*/
|
|
16
|
+
export declare function createAgentRuntime(config: AgentRuntimeConfig): Promise<AgentRuntime>;
|
|
17
|
+
//# sourceMappingURL=create-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-runtime.d.ts","sourceRoot":"","sources":["../src/create-runtime.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,YAAY,EAEb,MAAM,YAAY,CAAC;AAepB,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IACtD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAClE,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,4EAA4E;IAC5E,0BAA0B,EAAE,MAAM,IAAI,CAAC;CACxC,CAAC;AAEF;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,YAAY,CAAC,CA6MvB"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createAgentFactory } from "./agent.js";
|
|
2
|
+
import { createModels } from "./llm.js";
|
|
3
|
+
import { createRedis } from "./redis.js";
|
|
4
|
+
import { createAgentRunner } from "./services/agent-runner.js";
|
|
5
|
+
import { createChatCache } from "./services/chat-cache.js";
|
|
6
|
+
import { createConversationService } from "./services/conversation.js";
|
|
7
|
+
import { createToolOutputExtractor, extractRawToolOutputs } from "./services/extract.js";
|
|
8
|
+
import { createFlowRouter, stickySetFromIntents, } from "./services/flow-router.js";
|
|
9
|
+
import { createIntentEmbeddings } from "./services/intent-embeddings.js";
|
|
10
|
+
import { createIntentRouter } from "./services/intent-router.js";
|
|
11
|
+
import { getLastMessageContent } from "./utils/message.js";
|
|
12
|
+
/** Messages for this user turn only (used to extract tool outputs). */
|
|
13
|
+
const getTurnMessages = (allMessages, userMessage) => {
|
|
14
|
+
const normalized = userMessage.trim();
|
|
15
|
+
const list = allMessages;
|
|
16
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
17
|
+
const content = String(list[i]?.content ?? "").trim();
|
|
18
|
+
if (content === normalized) {
|
|
19
|
+
return list.slice(i);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return list.slice(-6);
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Factory for a reusable agent runtime.
|
|
26
|
+
* Engine pipeline: resolveFlow → runAgent → getTurnMessages → extract → finalize
|
|
27
|
+
*/
|
|
28
|
+
export async function createAgentRuntime(config) {
|
|
29
|
+
const defaultIntent = config.defaultIntent ?? config.intents[0]?.name ?? "greetings";
|
|
30
|
+
const intentNames = new Set(config.intents.map((i) => i.name));
|
|
31
|
+
const aliases = config.intentAliases ?? {};
|
|
32
|
+
const normalizeIntent = (intent) => {
|
|
33
|
+
const key = intent
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[\s-]+/g, "_");
|
|
37
|
+
if (intentNames.has(key))
|
|
38
|
+
return key;
|
|
39
|
+
return aliases[key] ?? defaultIntent;
|
|
40
|
+
};
|
|
41
|
+
const stickyFlows = stickySetFromIntents(config.intents);
|
|
42
|
+
const menuMap = config.menuMap ?? {};
|
|
43
|
+
const created = createModels(config.llm);
|
|
44
|
+
const chat = config.models?.chat ?? created.chat;
|
|
45
|
+
const intentRouterModel = config.models?.intentRouter ?? created.intentRouter;
|
|
46
|
+
const embeddings = config.models?.embeddings ?? created.embeddings;
|
|
47
|
+
// Redis client when redisUrl is set (flow state + optional answer cache)
|
|
48
|
+
const redis = config.redisUrl
|
|
49
|
+
? createRedis(config.redisUrl)
|
|
50
|
+
: undefined;
|
|
51
|
+
const chatCache = createChatCache({
|
|
52
|
+
redis: redis?.client,
|
|
53
|
+
config: {
|
|
54
|
+
enabled: config.cache?.enabled ?? false,
|
|
55
|
+
ttlSeconds: config.cache?.ttlSeconds,
|
|
56
|
+
flowTtlSeconds: config.cache?.flowTtlSeconds,
|
|
57
|
+
scope: config.cache?.scope,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
const agentFactory = await createAgentFactory({
|
|
61
|
+
llm: chat,
|
|
62
|
+
intentRegistry: config.intentRegistry,
|
|
63
|
+
redisUrl: config.redisUrl,
|
|
64
|
+
checkpointer: config.checkpointer,
|
|
65
|
+
});
|
|
66
|
+
const conversation = createConversationService(agentFactory);
|
|
67
|
+
const { runAgent } = createAgentRunner({
|
|
68
|
+
agentFactory,
|
|
69
|
+
recursionLimit: config.recursionLimit,
|
|
70
|
+
});
|
|
71
|
+
const intentEmbeddings = createIntentEmbeddings({
|
|
72
|
+
embeddings,
|
|
73
|
+
intents: config.intents,
|
|
74
|
+
normalizeIntent,
|
|
75
|
+
});
|
|
76
|
+
const intentRouter = createIntentRouter({
|
|
77
|
+
model: intentRouterModel,
|
|
78
|
+
intents: config.intents,
|
|
79
|
+
normalizeIntent,
|
|
80
|
+
defaultIntent,
|
|
81
|
+
routerSystemPrompt: config.routerSystemPrompt,
|
|
82
|
+
llmConfidence: config.thresholds?.llmConfidence,
|
|
83
|
+
embeddingConfidence: config.thresholds?.embeddingConfidence,
|
|
84
|
+
});
|
|
85
|
+
const { resolveFlow, finalizeAgentFlow } = createFlowRouter({
|
|
86
|
+
stickyFlows,
|
|
87
|
+
menuMap,
|
|
88
|
+
normalizeIntent,
|
|
89
|
+
getEmbeddedIntents: intentEmbeddings.getEmbeddedIntents,
|
|
90
|
+
scoreIntentsForMessage: intentEmbeddings.scoreIntentsForMessage,
|
|
91
|
+
classifyIntentWithLlm: intentRouter.classifyIntentWithLlm,
|
|
92
|
+
mergeIntentResults: intentRouter.mergeIntentResults,
|
|
93
|
+
chatCache,
|
|
94
|
+
finalizeFlow: config.finalizeFlow,
|
|
95
|
+
thresholds: config.thresholds,
|
|
96
|
+
});
|
|
97
|
+
const extractToolOutputs = config.extractToolOutputs ??
|
|
98
|
+
(config.toolExtractors
|
|
99
|
+
? createToolOutputExtractor(config.toolExtractors, config.initialToolOutputs, { includeUnknown: config.includeUnknownToolOutputs ?? false })
|
|
100
|
+
: extractRawToolOutputs);
|
|
101
|
+
const buildSystemPrompt = config.buildSystemPrompt ??
|
|
102
|
+
((ctx) => {
|
|
103
|
+
const binding = config.intentRegistry[ctx.intent];
|
|
104
|
+
if (!binding) {
|
|
105
|
+
const fallback = Object.values(config.intentRegistry)[0];
|
|
106
|
+
return fallback?.prompt(ctx) ?? "";
|
|
107
|
+
}
|
|
108
|
+
return binding.prompt(ctx);
|
|
109
|
+
});
|
|
110
|
+
const resolvePhoneKey = config.resolvePhoneKey ??
|
|
111
|
+
(({ threadId, phoneKey }) => phoneKey ?? threadId);
|
|
112
|
+
const invoke = async (input) => {
|
|
113
|
+
if (chatCache.enabled) {
|
|
114
|
+
const cached = await chatCache.getCachedAnswer(input.message);
|
|
115
|
+
if (cached) {
|
|
116
|
+
return {
|
|
117
|
+
messages: [],
|
|
118
|
+
answer: cached,
|
|
119
|
+
intent: { intent: defaultIntent, score: 1 },
|
|
120
|
+
flow: { active: false, intent: null },
|
|
121
|
+
toolOutputs: {},
|
|
122
|
+
flowCompleted: false,
|
|
123
|
+
extras: { cacheHit: true },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const cachedFlowState = await chatCache.getChatCacheFlowState(input.threadId);
|
|
128
|
+
const [messages, conversationContext] = await Promise.all([
|
|
129
|
+
conversation.getManagedConversationMessages(input.threadId),
|
|
130
|
+
conversation.getConversationContextForIntent(input.threadId),
|
|
131
|
+
]);
|
|
132
|
+
const { messageIntent, flow, promptIntent, llmResult } = await resolveFlow({
|
|
133
|
+
message: input.message,
|
|
134
|
+
explicitIntent: input.explicitIntent,
|
|
135
|
+
cachedFlowState,
|
|
136
|
+
conversationContext,
|
|
137
|
+
});
|
|
138
|
+
const phoneKey = resolvePhoneKey({
|
|
139
|
+
threadId: input.threadId,
|
|
140
|
+
phoneKey: input.phoneKey,
|
|
141
|
+
context: input.context,
|
|
142
|
+
});
|
|
143
|
+
const systemPrompt = buildSystemPrompt({
|
|
144
|
+
hasConversationHistory: messages.length > 0,
|
|
145
|
+
intent: promptIntent,
|
|
146
|
+
intentScore: flow.active ? 1 : messageIntent.score,
|
|
147
|
+
message: input.message,
|
|
148
|
+
context: input.context,
|
|
149
|
+
});
|
|
150
|
+
const result = await runAgent({
|
|
151
|
+
message: input.message,
|
|
152
|
+
threadId: input.threadId,
|
|
153
|
+
messages,
|
|
154
|
+
systemPrompt,
|
|
155
|
+
phoneKey,
|
|
156
|
+
intent: promptIntent,
|
|
157
|
+
});
|
|
158
|
+
const resultMessages = result.messages;
|
|
159
|
+
const turnMessages = getTurnMessages(resultMessages, input.message);
|
|
160
|
+
const toolOutputs = extractToolOutputs(turnMessages);
|
|
161
|
+
const finalized = await finalizeAgentFlow({
|
|
162
|
+
threadId: input.threadId,
|
|
163
|
+
promptIntent,
|
|
164
|
+
flow,
|
|
165
|
+
toolOutputs,
|
|
166
|
+
});
|
|
167
|
+
const answer = getLastMessageContent(resultMessages);
|
|
168
|
+
if (chatCache.enabled && answer) {
|
|
169
|
+
await chatCache.setCachedAnswer(input.message, answer);
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
messages: resultMessages,
|
|
173
|
+
answer,
|
|
174
|
+
intent: messageIntent,
|
|
175
|
+
flow: finalized.flow,
|
|
176
|
+
toolOutputs,
|
|
177
|
+
flowCompleted: finalized.flowCompleted,
|
|
178
|
+
extras: finalized.extras,
|
|
179
|
+
...(llmResult
|
|
180
|
+
? { router: { action: llmResult.action, reason: llmResult.reason } }
|
|
181
|
+
: {}),
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
return {
|
|
185
|
+
invoke,
|
|
186
|
+
connect: async () => {
|
|
187
|
+
await redis?.connect();
|
|
188
|
+
},
|
|
189
|
+
disconnect: async () => {
|
|
190
|
+
await redis?.disconnect();
|
|
191
|
+
},
|
|
192
|
+
clearFlow: chatCache.clearChatCacheFlowState,
|
|
193
|
+
getCachedAnswer: chatCache.getCachedAnswer,
|
|
194
|
+
setCachedAnswer: chatCache.setCachedAnswer,
|
|
195
|
+
invalidateIntentEmbeddings: intentEmbeddings.invalidate,
|
|
196
|
+
};
|
|
197
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createAgentRuntime, type AgentRuntime } from "./create-runtime.js";
|
|
2
|
+
export { createModels } from "./llm.js";
|
|
3
|
+
export { createRedis } from "./redis.js";
|
|
4
|
+
export { extractRawToolOutputs, extractToolCallArgs, getToolCallsFromMessages, createToolOutputExtractor, } from "./services/extract.js";
|
|
5
|
+
export { isWizardInput } from "./services/flow-router.js";
|
|
6
|
+
export { getLastMessageContent } from "./utils/message.js";
|
|
7
|
+
export { normalize } from "./utils/normalize.js";
|
|
8
|
+
export type { AgentRuntimeConfig, CacheConfig, ChatFlowState, DetectedIntentResult, FinalizeFlowInput, FinalizeFlowResult, FlowResolution, FlowState, IntentBinding, IntentDefinition, IntentRegistry, InvokeInput, InvokeResult, LlmConfig, LlmIntentResult, LlmProvider, PromptContext, ToolCall, ToolExtractor, ToolExtractorMap, TracingConfig, } from "./types.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,WAAW,EACX,YAAY,EACZ,SAAS,EACT,eAAe,EACf,WAAW,EACX,aAAa,EACb,QAAQ,EACR,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createAgentRuntime } from "./create-runtime.js";
|
|
2
|
+
export { createModels } from "./llm.js";
|
|
3
|
+
export { createRedis } from "./redis.js";
|
|
4
|
+
export { extractRawToolOutputs, extractToolCallArgs, getToolCallsFromMessages, createToolOutputExtractor, } from "./services/extract.js";
|
|
5
|
+
export { isWizardInput } from "./services/flow-router.js";
|
|
6
|
+
export { getLastMessageContent } from "./utils/message.js";
|
|
7
|
+
export { normalize } from "./utils/normalize.js";
|
package/dist/llm.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
|
|
2
|
+
import type { Embeddings } from "@langchain/core/embeddings";
|
|
3
|
+
import type { LlmConfig, LlmProvider } from "./types.js";
|
|
4
|
+
export type CreatedModels = {
|
|
5
|
+
chat: BaseChatModel;
|
|
6
|
+
intentRouter: BaseChatModel;
|
|
7
|
+
embeddings: Embeddings;
|
|
8
|
+
config: {
|
|
9
|
+
provider: LlmProvider;
|
|
10
|
+
embeddingsProvider: LlmProvider;
|
|
11
|
+
model: string;
|
|
12
|
+
intentRouterModel: string;
|
|
13
|
+
embeddingsModel: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function createModels(llm: LlmConfig): CreatedModels;
|
|
17
|
+
//# sourceMappingURL=llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6CAA6C,CAAC;AACjF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAG7D,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAczD,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,aAAa,CAAC;IACpB,YAAY,EAAE,aAAa,CAAC;IAC5B,UAAU,EAAE,UAAU,CAAC;IACvB,MAAM,EAAE;QACN,QAAQ,EAAE,WAAW,CAAC;QACtB,kBAAkB,EAAE,WAAW,CAAC;QAChC,KAAK,EAAE,MAAM,CAAC;QACd,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;CACH,CAAC;AAEF,wBAAgB,YAAY,CAAC,GAAG,EAAE,SAAS,GAAG,aAAa,CAgE1D"}
|
package/dist/llm.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ChatOpenAI, OpenAIEmbeddings } from "@langchain/openai";
|
|
2
|
+
import { ChatOllama, OllamaEmbeddings } from "@langchain/ollama";
|
|
3
|
+
const normalizeProvider = (value) => {
|
|
4
|
+
const normalized = value?.trim().toLowerCase();
|
|
5
|
+
if (normalized === "openai" ||
|
|
6
|
+
normalized === "gpt" ||
|
|
7
|
+
normalized === "chatgpt") {
|
|
8
|
+
return "openai";
|
|
9
|
+
}
|
|
10
|
+
return "ollama";
|
|
11
|
+
};
|
|
12
|
+
export function createModels(llm) {
|
|
13
|
+
const provider = normalizeProvider(llm.provider);
|
|
14
|
+
const embeddingsProvider = normalizeProvider(llm.embeddingsProvider ?? llm.provider);
|
|
15
|
+
const temperature = llm.temperature ?? 0;
|
|
16
|
+
const createChat = (modelName) => {
|
|
17
|
+
if (provider === "openai") {
|
|
18
|
+
if (!llm.apiKey) {
|
|
19
|
+
throw new Error("llm.apiKey is required when provider is openai.");
|
|
20
|
+
}
|
|
21
|
+
return new ChatOpenAI({
|
|
22
|
+
model: modelName,
|
|
23
|
+
temperature,
|
|
24
|
+
apiKey: llm.apiKey,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return new ChatOllama({
|
|
28
|
+
model: modelName,
|
|
29
|
+
temperature,
|
|
30
|
+
baseUrl: llm.baseUrl,
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
const createEmbeddings = () => {
|
|
34
|
+
if (embeddingsProvider === "openai") {
|
|
35
|
+
if (!llm.apiKey) {
|
|
36
|
+
throw new Error("llm.apiKey is required when embeddingsProvider is openai.");
|
|
37
|
+
}
|
|
38
|
+
return new OpenAIEmbeddings({
|
|
39
|
+
model: llm.embeddingsModel ?? "text-embedding-3-small",
|
|
40
|
+
apiKey: llm.apiKey,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return new OllamaEmbeddings({
|
|
44
|
+
model: llm.embeddingsModel ?? "mxbai-embed-large:latest",
|
|
45
|
+
baseUrl: llm.baseUrl,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const intentRouterModelName = llm.intentRouterModel ?? llm.model;
|
|
49
|
+
const embeddingsModelName = llm.embeddingsModel ??
|
|
50
|
+
(embeddingsProvider === "openai"
|
|
51
|
+
? "text-embedding-3-small"
|
|
52
|
+
: "mxbai-embed-large:latest");
|
|
53
|
+
return {
|
|
54
|
+
chat: createChat(llm.model),
|
|
55
|
+
intentRouter: createChat(intentRouterModelName),
|
|
56
|
+
embeddings: createEmbeddings(),
|
|
57
|
+
config: {
|
|
58
|
+
provider,
|
|
59
|
+
embeddingsProvider,
|
|
60
|
+
model: llm.model,
|
|
61
|
+
intentRouterModel: intentRouterModelName,
|
|
62
|
+
embeddingsModel: embeddingsModelName,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
package/dist/redis.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type RedisClientType } from "redis";
|
|
2
|
+
export type RuntimeRedis = {
|
|
3
|
+
client: RedisClientType;
|
|
4
|
+
connect: () => Promise<void>;
|
|
5
|
+
disconnect: () => Promise<void>;
|
|
6
|
+
};
|
|
7
|
+
/** Creates a Redis client for flow state + optional answer cache. */
|
|
8
|
+
export declare function createRedis(redisUrl: string): RuntimeRedis;
|
|
9
|
+
//# sourceMappingURL=redis.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,OAAO,CAAC;AAE3D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,eAAe,CAAC;IACxB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,CAAC;AAEF,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAgB1D"}
|
package/dist/redis.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createClient } from "redis";
|
|
2
|
+
/** Creates a Redis client for flow state + optional answer cache. */
|
|
3
|
+
export function createRedis(redisUrl) {
|
|
4
|
+
const client = createClient({ url: redisUrl });
|
|
5
|
+
return {
|
|
6
|
+
client,
|
|
7
|
+
connect: async () => {
|
|
8
|
+
if (!client.isOpen) {
|
|
9
|
+
await client.connect();
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
disconnect: async () => {
|
|
13
|
+
if (client.isOpen) {
|
|
14
|
+
await client.disconnect();
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AgentFactory } from "../agent.js";
|
|
2
|
+
export type AgentRunInput = {
|
|
3
|
+
message: string;
|
|
4
|
+
threadId: string;
|
|
5
|
+
messages: unknown[];
|
|
6
|
+
systemPrompt: string;
|
|
7
|
+
phoneKey: string;
|
|
8
|
+
intent?: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function createAgentRunner(options: {
|
|
11
|
+
agentFactory: AgentFactory;
|
|
12
|
+
recursionLimit?: number;
|
|
13
|
+
}): {
|
|
14
|
+
runAgent: ({ message, threadId, messages, systemPrompt, phoneKey, intent, }: AgentRunInput) => Promise<any>;
|
|
15
|
+
};
|
|
16
|
+
export type AgentRunResult = Awaited<ReturnType<ReturnType<typeof createAgentRunner>["runAgent"]>>;
|
|
17
|
+
//# sourceMappingURL=agent-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../../src/services/agent-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;iFAUI,aAAa;EAmBjB;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,CAClC,UAAU,CAAC,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC,UAAU,CAAC,CAAC,CAC7D,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
2
|
+
export function createAgentRunner(options) {
|
|
3
|
+
const recursionLimit = options.recursionLimit ?? 6;
|
|
4
|
+
const runAgent = async ({ message, threadId, messages, systemPrompt, phoneKey, intent, }) => {
|
|
5
|
+
const agent = options.agentFactory.createAgent(phoneKey, intent);
|
|
6
|
+
return agent.invoke({
|
|
7
|
+
messages: [
|
|
8
|
+
new SystemMessage(systemPrompt),
|
|
9
|
+
...messages,
|
|
10
|
+
new HumanMessage(message),
|
|
11
|
+
],
|
|
12
|
+
}, {
|
|
13
|
+
configurable: { thread_id: threadId },
|
|
14
|
+
recursionLimit,
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
return { runAgent };
|
|
18
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RedisClientType } from "redis";
|
|
2
|
+
import type { CacheConfig, ChatFlowState } from "../types.js";
|
|
3
|
+
export declare function createChatCache(options: {
|
|
4
|
+
redis?: RedisClientType;
|
|
5
|
+
config?: CacheConfig;
|
|
6
|
+
}): {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
getCachedAnswer: (message: string) => Promise<string | undefined>;
|
|
9
|
+
setCachedAnswer: (message: string, answer: string) => Promise<void>;
|
|
10
|
+
deleteCachedAnswer: (message: string) => Promise<void>;
|
|
11
|
+
setChatCacheFlowState: (threadId: string, flowState: ChatFlowState) => Promise<void>;
|
|
12
|
+
getChatCacheFlowState: (threadId: string) => Promise<ChatFlowState | null>;
|
|
13
|
+
clearChatCacheFlowState: (threadId: string) => Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export type ChatCache = ReturnType<typeof createChatCache>;
|
|
16
|
+
//# sourceMappingURL=chat-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-cache.d.ts","sourceRoot":"","sources":["../../src/services/chat-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAG7C,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK9D,wBAAgB,eAAe,CAAC,OAAO,EAAE;IACvC,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;;+BAUyC,MAAM,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;+BAmBpC,MAAM,UAAU,MAAM;kCAOnB,MAAM;sCAMrC,MAAM,aACL,aAAa;sCASd,MAAM,KACf,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;wCAOiB,MAAM;EAcxD;AAED,MAAM,MAAM,SAAS,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { normalize } from "../utils/normalize.js";
|
|
2
|
+
import { getLastMessageContent } from "../utils/message.js";
|
|
3
|
+
const DEFAULT_ANSWER_TTL = 60 * 60 * 24;
|
|
4
|
+
const DEFAULT_FLOW_TTL = 60 * 30;
|
|
5
|
+
export function createChatCache(options) {
|
|
6
|
+
const enabled = Boolean(options.config?.enabled && options.redis);
|
|
7
|
+
const answerTtl = options.config?.ttlSeconds ?? DEFAULT_ANSWER_TTL;
|
|
8
|
+
const flowTtl = options.config?.flowTtlSeconds ?? DEFAULT_FLOW_TTL;
|
|
9
|
+
const scope = options.config?.scope ?? "qa";
|
|
10
|
+
const redis = options.redis;
|
|
11
|
+
const getCacheKey = (message) => `${scope}:${normalize(message)}`;
|
|
12
|
+
const getFlowStateKey = (threadId) => `flow:${threadId}`;
|
|
13
|
+
const getCachedAnswer = async (message) => {
|
|
14
|
+
if (!enabled || !redis)
|
|
15
|
+
return undefined;
|
|
16
|
+
const cacheKey = getCacheKey(message);
|
|
17
|
+
const cached = await redis.get(cacheKey);
|
|
18
|
+
if (!cached)
|
|
19
|
+
return undefined;
|
|
20
|
+
const cachedResult = JSON.parse(cached);
|
|
21
|
+
const cachedAnswer = typeof cachedResult?.answer === "string"
|
|
22
|
+
? cachedResult.answer
|
|
23
|
+
: getLastMessageContent(cachedResult?.messages);
|
|
24
|
+
if (cachedAnswer)
|
|
25
|
+
return cachedAnswer;
|
|
26
|
+
await redis.del(cacheKey);
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
29
|
+
const setCachedAnswer = async (message, answer) => {
|
|
30
|
+
if (!enabled || !redis)
|
|
31
|
+
return;
|
|
32
|
+
await redis.set(getCacheKey(message), JSON.stringify({ answer }), {
|
|
33
|
+
EX: answerTtl,
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
const deleteCachedAnswer = async (message) => {
|
|
37
|
+
if (!redis)
|
|
38
|
+
return;
|
|
39
|
+
await redis.del(getCacheKey(message));
|
|
40
|
+
};
|
|
41
|
+
const setChatCacheFlowState = async (threadId, flowState) => {
|
|
42
|
+
if (!redis)
|
|
43
|
+
return;
|
|
44
|
+
await redis.set(getFlowStateKey(threadId), JSON.stringify(flowState), {
|
|
45
|
+
EX: flowTtl,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
const getChatCacheFlowState = async (threadId) => {
|
|
49
|
+
if (!redis)
|
|
50
|
+
return null;
|
|
51
|
+
const cached = await redis.get(getFlowStateKey(threadId));
|
|
52
|
+
if (!cached)
|
|
53
|
+
return null;
|
|
54
|
+
return JSON.parse(cached);
|
|
55
|
+
};
|
|
56
|
+
const clearChatCacheFlowState = async (threadId) => {
|
|
57
|
+
if (!redis)
|
|
58
|
+
return;
|
|
59
|
+
await redis.del(getFlowStateKey(threadId));
|
|
60
|
+
};
|
|
61
|
+
return {
|
|
62
|
+
enabled,
|
|
63
|
+
getCachedAnswer,
|
|
64
|
+
setCachedAnswer,
|
|
65
|
+
deleteCachedAnswer,
|
|
66
|
+
setChatCacheFlowState,
|
|
67
|
+
getChatCacheFlowState,
|
|
68
|
+
clearChatCacheFlowState,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AgentFactory } from "../agent.js";
|
|
2
|
+
export declare function createConversationService(agentFactory: AgentFactory): {
|
|
3
|
+
getManagedConversationMessages: (threadId: string) => Promise<unknown[]>;
|
|
4
|
+
formatConversationForIntentRouter: (messages: unknown[]) => string;
|
|
5
|
+
getConversationContextForIntent: (threadId: string) => Promise<string>;
|
|
6
|
+
};
|
|
7
|
+
//# sourceMappingURL=conversation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation.d.ts","sourceRoot":"","sources":["../../src/services/conversation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,YAAY;+CACV,MAAM;kDAsCT,OAAO,EAAE,KAAG,MAAM;gDAe3D,MAAM,KACf,OAAO,CAAC,MAAM,CAAC;EAUnB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function createConversationService(agentFactory) {
|
|
2
|
+
const getManagedConversationMessages = async (threadId) => {
|
|
3
|
+
const state = await agentFactory.getAgentState(threadId);
|
|
4
|
+
const messages = state.values.messages ?? [];
|
|
5
|
+
return messages
|
|
6
|
+
.filter((message) => {
|
|
7
|
+
const content = String(message?.content ?? "").toLowerCase();
|
|
8
|
+
return (!content.includes("tool failed") &&
|
|
9
|
+
!content.includes("error") &&
|
|
10
|
+
!content.includes("undefined"));
|
|
11
|
+
})
|
|
12
|
+
.slice(-8);
|
|
13
|
+
};
|
|
14
|
+
const getMessageRole = (message) => {
|
|
15
|
+
const type = message._getType?.() ?? message.type ?? "";
|
|
16
|
+
if (type === "human")
|
|
17
|
+
return "User";
|
|
18
|
+
if (type === "ai")
|
|
19
|
+
return "Assistant";
|
|
20
|
+
return "System";
|
|
21
|
+
};
|
|
22
|
+
const getMessageText = (message) => {
|
|
23
|
+
const content = message.content;
|
|
24
|
+
if (typeof content === "string")
|
|
25
|
+
return content.trim();
|
|
26
|
+
if (Array.isArray(content)) {
|
|
27
|
+
return content
|
|
28
|
+
.map((part) => (typeof part === "string" ? part : (part?.text ?? "")))
|
|
29
|
+
.join(" ")
|
|
30
|
+
.trim();
|
|
31
|
+
}
|
|
32
|
+
return String(content ?? "").trim();
|
|
33
|
+
};
|
|
34
|
+
const formatConversationForIntentRouter = (messages) => {
|
|
35
|
+
if (messages.length === 0)
|
|
36
|
+
return "(no prior messages)";
|
|
37
|
+
return messages
|
|
38
|
+
.slice(-6)
|
|
39
|
+
.map((message) => {
|
|
40
|
+
const role = getMessageRole(message);
|
|
41
|
+
const text = getMessageText(message).slice(0, 300);
|
|
42
|
+
return text ? `${role}: ${text}` : null;
|
|
43
|
+
})
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join("\n");
|
|
46
|
+
};
|
|
47
|
+
const getConversationContextForIntent = async (threadId) => {
|
|
48
|
+
const messages = await getManagedConversationMessages(threadId);
|
|
49
|
+
return formatConversationForIntentRouter(messages);
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
getManagedConversationMessages,
|
|
53
|
+
formatConversationForIntentRouter,
|
|
54
|
+
getConversationContextForIntent,
|
|
55
|
+
};
|
|
56
|
+
}
|