chattercatcher 0.1.16 → 0.1.17
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/dist/cli.js +256 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +34 -2
- package/dist/index.js +256 -8
- package/dist/index.js.map +1 -1
- package/docs/superpowers/plans/2026-05-02-agentic-rag.md +1013 -0
- package/docs/superpowers/specs/2026-05-02-agentic-rag-design.md +143 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import fs13 from "fs/promises";
|
|
|
8
8
|
// package.json
|
|
9
9
|
var package_default = {
|
|
10
10
|
name: "chattercatcher",
|
|
11
|
-
version: "0.1.
|
|
11
|
+
version: "0.1.17",
|
|
12
12
|
description: "\u672C\u5730\u4F18\u5148\u7684\u98DE\u4E66/Lark \u5BB6\u5EAD\u7FA4\u77E5\u8BC6\u5E93\u673A\u5668\u4EBA",
|
|
13
13
|
type: "module",
|
|
14
14
|
main: "dist/index.js",
|
|
@@ -894,6 +894,40 @@ function getGatewayStatus(config, secrets) {
|
|
|
894
894
|
function normalizeBaseUrl(baseUrl) {
|
|
895
895
|
return baseUrl.replace(/\/+$/, "");
|
|
896
896
|
}
|
|
897
|
+
function toOpenAIMessage(message) {
|
|
898
|
+
return {
|
|
899
|
+
role: message.role,
|
|
900
|
+
content: message.content,
|
|
901
|
+
...message.toolCallId ? { tool_call_id: message.toolCallId } : {},
|
|
902
|
+
...message.toolCalls ? {
|
|
903
|
+
tool_calls: message.toolCalls.map((toolCall) => ({
|
|
904
|
+
id: toolCall.id,
|
|
905
|
+
type: "function",
|
|
906
|
+
function: {
|
|
907
|
+
name: toolCall.name,
|
|
908
|
+
arguments: JSON.stringify(toolCall.input)
|
|
909
|
+
}
|
|
910
|
+
}))
|
|
911
|
+
} : {}
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function toOpenAITool(tool) {
|
|
915
|
+
return {
|
|
916
|
+
type: "function",
|
|
917
|
+
function: {
|
|
918
|
+
name: tool.name,
|
|
919
|
+
description: tool.description,
|
|
920
|
+
parameters: tool.inputSchema
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
function parseToolCalls(message) {
|
|
925
|
+
return message?.tool_calls?.map((toolCall) => ({
|
|
926
|
+
id: toolCall.id,
|
|
927
|
+
name: toolCall.function.name,
|
|
928
|
+
input: JSON.parse(toolCall.function.arguments)
|
|
929
|
+
})) ?? [];
|
|
930
|
+
}
|
|
897
931
|
var OpenAICompatibleChatModel = class {
|
|
898
932
|
constructor(options) {
|
|
899
933
|
this.options = options;
|
|
@@ -911,7 +945,7 @@ var OpenAICompatibleChatModel = class {
|
|
|
911
945
|
},
|
|
912
946
|
body: JSON.stringify({
|
|
913
947
|
model: this.options.model,
|
|
914
|
-
messages,
|
|
948
|
+
messages: messages.map(toOpenAIMessage),
|
|
915
949
|
temperature: this.options.temperature ?? 0.2
|
|
916
950
|
})
|
|
917
951
|
});
|
|
@@ -926,6 +960,35 @@ var OpenAICompatibleChatModel = class {
|
|
|
926
960
|
}
|
|
927
961
|
return content;
|
|
928
962
|
}
|
|
963
|
+
async completeWithTools(messages, tools) {
|
|
964
|
+
if (!this.options.baseUrl || !this.options.apiKey || !this.options.model) {
|
|
965
|
+
throw new Error("LLM \u914D\u7F6E\u4E0D\u5B8C\u6574\u3002\u8BF7\u8FD0\u884C chattercatcher setup \u6216 chattercatcher settings\u3002");
|
|
966
|
+
}
|
|
967
|
+
const response = await fetch(`${normalizeBaseUrl(this.options.baseUrl)}/chat/completions`, {
|
|
968
|
+
method: "POST",
|
|
969
|
+
headers: {
|
|
970
|
+
authorization: `Bearer ${this.options.apiKey}`,
|
|
971
|
+
"content-type": "application/json"
|
|
972
|
+
},
|
|
973
|
+
body: JSON.stringify({
|
|
974
|
+
model: this.options.model,
|
|
975
|
+
messages: messages.map(toOpenAIMessage),
|
|
976
|
+
tools: tools.map(toOpenAITool),
|
|
977
|
+
tool_choice: "auto",
|
|
978
|
+
temperature: this.options.temperature ?? 0.2
|
|
979
|
+
})
|
|
980
|
+
});
|
|
981
|
+
if (!response.ok) {
|
|
982
|
+
const body = await response.text();
|
|
983
|
+
throw new Error(`LLM \u8BF7\u6C42\u5931\u8D25\uFF1A${response.status} ${body}`);
|
|
984
|
+
}
|
|
985
|
+
const data2 = await response.json();
|
|
986
|
+
const message = data2.choices?.[0]?.message;
|
|
987
|
+
return {
|
|
988
|
+
content: message?.content ?? "",
|
|
989
|
+
toolCalls: parseToolCalls(message)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
929
992
|
};
|
|
930
993
|
var OpenAICompatibleEmbeddingModel = class {
|
|
931
994
|
constructor(options) {
|
|
@@ -1612,6 +1675,73 @@ var MessageFtsRetriever = class {
|
|
|
1612
1675
|
}
|
|
1613
1676
|
};
|
|
1614
1677
|
|
|
1678
|
+
// src/rag/search-tools.ts
|
|
1679
|
+
var searchInputSchema = {
|
|
1680
|
+
type: "object",
|
|
1681
|
+
properties: {
|
|
1682
|
+
query: { type: "string", description: "Search query written by the model." },
|
|
1683
|
+
limit: { type: "number", description: "Maximum number of evidence blocks to return." }
|
|
1684
|
+
},
|
|
1685
|
+
required: ["query"],
|
|
1686
|
+
additionalProperties: false
|
|
1687
|
+
};
|
|
1688
|
+
function parseSearchInput(input2) {
|
|
1689
|
+
const rawQuery = typeof input2 === "object" && input2 !== null && "query" in input2 ? input2.query : void 0;
|
|
1690
|
+
if (typeof rawQuery !== "string") {
|
|
1691
|
+
throw new Error("\u641C\u7D22 query \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002");
|
|
1692
|
+
}
|
|
1693
|
+
const query = rawQuery.trim();
|
|
1694
|
+
if (!query) {
|
|
1695
|
+
throw new Error("\u641C\u7D22 query \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32\u3002");
|
|
1696
|
+
}
|
|
1697
|
+
const rawLimit = typeof input2 === "object" && input2 !== null && "limit" in input2 ? input2.limit : void 0;
|
|
1698
|
+
const numericLimit = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? rawLimit : 5;
|
|
1699
|
+
const limit = Math.min(12, Math.max(1, Math.floor(numericLimit)));
|
|
1700
|
+
return { query, limit };
|
|
1701
|
+
}
|
|
1702
|
+
async function runRetriever(retriever, input2) {
|
|
1703
|
+
const { query, limit } = parseSearchInput(input2);
|
|
1704
|
+
const results = await retriever.retrieve(query);
|
|
1705
|
+
return results.slice(0, limit);
|
|
1706
|
+
}
|
|
1707
|
+
function createSearchTool(name, description, retriever) {
|
|
1708
|
+
return {
|
|
1709
|
+
name,
|
|
1710
|
+
description,
|
|
1711
|
+
inputSchema: searchInputSchema,
|
|
1712
|
+
execute: (input2) => runRetriever(retriever, input2)
|
|
1713
|
+
};
|
|
1714
|
+
}
|
|
1715
|
+
function createRagSearchTools(input2) {
|
|
1716
|
+
const tools = [
|
|
1717
|
+
createSearchTool(
|
|
1718
|
+
"hybrid_search",
|
|
1719
|
+
"Search across all indexed RAG evidence using the default hybrid retrieval strategy.",
|
|
1720
|
+
input2.hybrid
|
|
1721
|
+
),
|
|
1722
|
+
createSearchTool(
|
|
1723
|
+
"search_messages",
|
|
1724
|
+
"Search chat messages only when the answer likely depends on message-level evidence.",
|
|
1725
|
+
input2.messages
|
|
1726
|
+
),
|
|
1727
|
+
createSearchTool(
|
|
1728
|
+
"search_episodes",
|
|
1729
|
+
"Search episode summaries only when the answer likely depends on longer-running context.",
|
|
1730
|
+
input2.episodes
|
|
1731
|
+
)
|
|
1732
|
+
];
|
|
1733
|
+
if (input2.semantic) {
|
|
1734
|
+
tools.push(
|
|
1735
|
+
createSearchTool(
|
|
1736
|
+
"semantic_search",
|
|
1737
|
+
"Search semantic vector evidence only when broader conceptual recall is needed.",
|
|
1738
|
+
input2.semantic
|
|
1739
|
+
)
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
return tools;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1615
1745
|
// src/rag/embedding.ts
|
|
1616
1746
|
function cosineSimilarity(left, right) {
|
|
1617
1747
|
if (left.length === 0 || right.length === 0 || left.length !== right.length) {
|
|
@@ -1766,6 +1896,20 @@ async function createHybridRetriever(input2) {
|
|
|
1766
1896
|
}
|
|
1767
1897
|
};
|
|
1768
1898
|
}
|
|
1899
|
+
async function createAgenticRagSearchTools(input2) {
|
|
1900
|
+
const episodes = new EpisodeFtsRetriever(new EpisodeRepository(input2.database));
|
|
1901
|
+
const messages = new MessageFtsRetriever(input2.messages, { excludeMessageIds: input2.excludeMessageIds });
|
|
1902
|
+
const semantic = hasEmbeddingConfig(input2.config, input2.secrets) ? new VectorRetriever(
|
|
1903
|
+
createEmbeddingModel(input2.config, input2.secrets),
|
|
1904
|
+
new SqliteVectorStore(input2.database, { model: input2.config.embedding.model })
|
|
1905
|
+
) : void 0;
|
|
1906
|
+
const hybrid = new HybridRetriever(semantic ? [episodes, messages, semantic] : [episodes, messages]);
|
|
1907
|
+
return {
|
|
1908
|
+
tools: createRagSearchTools({ hybrid, messages, episodes, semantic }),
|
|
1909
|
+
close: () => {
|
|
1910
|
+
}
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1769
1913
|
|
|
1770
1914
|
// src/doctor/checks.ts
|
|
1771
1915
|
function pass(name, message) {
|
|
@@ -2454,12 +2598,99 @@ async function generateGroundedAnswer(input2) {
|
|
|
2454
2598
|
};
|
|
2455
2599
|
}
|
|
2456
2600
|
|
|
2457
|
-
// src/rag/qa-service.ts
|
|
2458
|
-
|
|
2459
|
-
|
|
2601
|
+
// src/rag/agentic-qa-service.ts
|
|
2602
|
+
var DEFAULT_MAX_MODEL_TURNS = 4;
|
|
2603
|
+
var DEFAULT_MAX_TOOL_CALLS = 8;
|
|
2604
|
+
var DEFAULT_MAX_EVIDENCE = 12;
|
|
2605
|
+
var NO_EVIDENCE_ANSWER = "\u4E0D\u77E5\u9053\u3002\u5F53\u524D\u672C\u5730\u77E5\u8BC6\u5E93\u6CA1\u6709\u68C0\u7D22\u5230\u8DB3\u591F\u8BC1\u636E\u3002";
|
|
2606
|
+
var AGENTIC_SYSTEM_PROMPT = "\u4F60\u662F\u672C\u5730\u77E5\u8BC6\u4FE1\u606F\u6536\u96C6\u4EE3\u7406\u3002\u4F60\u7684\u804C\u8D23\u662F\u56F4\u7ED5\u7528\u6237\u95EE\u9898\u51B3\u5B9A\u662F\u5426\u8C03\u7528\u641C\u7D22\u5DE5\u5177\u3001\u9009\u62E9\u5408\u9002\u7684\u5DE5\u5177\u548C\u67E5\u8BE2\u8BCD\uFF0C\u5E76\u6839\u636E\u5F53\u524D\u7ED3\u679C\u51B3\u5B9A\u662F\u5426\u7EE7\u7EED\u641C\u7D22\u3002\u4E0D\u8981\u7F16\u9020\u4EFB\u4F55\u8BC1\u636E\u6216\u58F0\u79F0\u770B\u8FC7\u672A\u68C0\u7D22\u5230\u7684\u5185\u5BB9\u3002\u4F60\u7684\u8F93\u51FA\u53EA\u7528\u4E8E\u6536\u96C6\u8BC1\u636E\uFF0C\u6700\u7EC8\u7B54\u6848\u4F1A\u7531\u53E6\u4E00\u4E2A\u57FA\u4E8E\u8BC1\u636E\u7684\u6B65\u9AA4\u751F\u6210\u3002";
|
|
2607
|
+
function toToolResultContent(results) {
|
|
2608
|
+
return JSON.stringify(
|
|
2609
|
+
results.map((item) => ({
|
|
2610
|
+
id: item.id,
|
|
2611
|
+
text: item.text,
|
|
2612
|
+
score: item.score,
|
|
2613
|
+
source: item.source
|
|
2614
|
+
}))
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
function toToolErrorContent(message) {
|
|
2618
|
+
return JSON.stringify({ error: message });
|
|
2619
|
+
}
|
|
2620
|
+
function dedupeEvidence(evidence, maxEvidence) {
|
|
2621
|
+
const deduped = [];
|
|
2622
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2623
|
+
for (const item of evidence) {
|
|
2624
|
+
if (seen.has(item.id)) {
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
seen.add(item.id);
|
|
2628
|
+
deduped.push(item);
|
|
2629
|
+
if (deduped.length >= maxEvidence) {
|
|
2630
|
+
break;
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
return deduped;
|
|
2634
|
+
}
|
|
2635
|
+
async function askWithAgenticRag(input2) {
|
|
2636
|
+
if (!input2.model.completeWithTools) {
|
|
2637
|
+
throw new Error("\u5F53\u524D LLM \u5BA2\u6237\u7AEF\u4E0D\u652F\u6301\u5DE5\u5177\u8C03\u7528\u3002");
|
|
2638
|
+
}
|
|
2639
|
+
const maxModelTurns = input2.maxModelTurns ?? DEFAULT_MAX_MODEL_TURNS;
|
|
2640
|
+
const maxToolCalls = input2.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS;
|
|
2641
|
+
const maxEvidence = input2.maxEvidence ?? DEFAULT_MAX_EVIDENCE;
|
|
2642
|
+
const messages = [
|
|
2643
|
+
{ role: "system", content: AGENTIC_SYSTEM_PROMPT },
|
|
2644
|
+
{ role: "user", content: input2.question }
|
|
2645
|
+
];
|
|
2646
|
+
const toolsByName = new Map(input2.tools.map((tool) => [tool.name, tool]));
|
|
2647
|
+
let evidence = [];
|
|
2648
|
+
let toolCallsUsed = 0;
|
|
2649
|
+
for (let turn = 0; turn < maxModelTurns; turn += 1) {
|
|
2650
|
+
const assistantResult = await input2.model.completeWithTools(messages, input2.tools);
|
|
2651
|
+
messages.push({
|
|
2652
|
+
role: "assistant",
|
|
2653
|
+
content: assistantResult.content,
|
|
2654
|
+
toolCalls: assistantResult.toolCalls
|
|
2655
|
+
});
|
|
2656
|
+
if (assistantResult.toolCalls.length === 0) {
|
|
2657
|
+
break;
|
|
2658
|
+
}
|
|
2659
|
+
for (const toolCall of assistantResult.toolCalls) {
|
|
2660
|
+
if (toolCallsUsed >= maxToolCalls) {
|
|
2661
|
+
break;
|
|
2662
|
+
}
|
|
2663
|
+
toolCallsUsed += 1;
|
|
2664
|
+
const tool = toolsByName.get(toolCall.name);
|
|
2665
|
+
if (!tool) {
|
|
2666
|
+
messages.push({
|
|
2667
|
+
role: "tool",
|
|
2668
|
+
toolCallId: toolCall.id,
|
|
2669
|
+
content: toToolErrorContent(`\u672A\u77E5\u5DE5\u5177\uFF1A${toolCall.name}`)
|
|
2670
|
+
});
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
try {
|
|
2674
|
+
const results = await tool.execute(toolCall.input);
|
|
2675
|
+
evidence = dedupeEvidence([...evidence, ...results], maxEvidence);
|
|
2676
|
+
messages.push({
|
|
2677
|
+
role: "tool",
|
|
2678
|
+
toolCallId: toolCall.id,
|
|
2679
|
+
content: toToolResultContent(results)
|
|
2680
|
+
});
|
|
2681
|
+
} catch (error) {
|
|
2682
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2683
|
+
messages.push({
|
|
2684
|
+
role: "tool",
|
|
2685
|
+
toolCallId: toolCall.id,
|
|
2686
|
+
content: toToolErrorContent(message)
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2460
2691
|
if (evidence.length === 0) {
|
|
2461
2692
|
return {
|
|
2462
|
-
answer:
|
|
2693
|
+
answer: NO_EVIDENCE_ANSWER,
|
|
2463
2694
|
citations: []
|
|
2464
2695
|
};
|
|
2465
2696
|
}
|
|
@@ -2577,7 +2808,7 @@ var FeishuQuestionHandler = class {
|
|
|
2577
2808
|
}
|
|
2578
2809
|
const questionMessageId = payload.event?.message?.message_id;
|
|
2579
2810
|
await this.acknowledgeQuestion(decision.chatId, questionMessageId);
|
|
2580
|
-
const {
|
|
2811
|
+
const { tools, close } = await createAgenticRagSearchTools({
|
|
2581
2812
|
config: this.options.config,
|
|
2582
2813
|
secrets: this.options.secrets,
|
|
2583
2814
|
database: this.options.database,
|
|
@@ -2586,9 +2817,9 @@ var FeishuQuestionHandler = class {
|
|
|
2586
2817
|
});
|
|
2587
2818
|
try {
|
|
2588
2819
|
try {
|
|
2589
|
-
const result = await
|
|
2820
|
+
const result = await askWithAgenticRag({
|
|
2590
2821
|
question: decision.question,
|
|
2591
|
-
|
|
2822
|
+
tools,
|
|
2592
2823
|
model: this.options.model
|
|
2593
2824
|
});
|
|
2594
2825
|
const citations = formatCitations(result.citations);
|
|
@@ -3433,6 +3664,22 @@ async function processMessagesNow(input2) {
|
|
|
3433
3664
|
};
|
|
3434
3665
|
}
|
|
3435
3666
|
|
|
3667
|
+
// src/rag/qa-service.ts
|
|
3668
|
+
async function askWithRag(input2) {
|
|
3669
|
+
const evidence = await input2.retriever.retrieve(input2.question);
|
|
3670
|
+
if (evidence.length === 0) {
|
|
3671
|
+
return {
|
|
3672
|
+
answer: "\u4E0D\u77E5\u9053\u3002\u5F53\u524D\u672C\u5730\u77E5\u8BC6\u5E93\u6CA1\u6709\u68C0\u7D22\u5230\u8DB3\u591F\u8BC1\u636E\u3002",
|
|
3673
|
+
citations: []
|
|
3674
|
+
};
|
|
3675
|
+
}
|
|
3676
|
+
return generateGroundedAnswer({
|
|
3677
|
+
question: input2.question,
|
|
3678
|
+
evidence,
|
|
3679
|
+
model: input2.model
|
|
3680
|
+
});
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3436
3683
|
// src/update/npm-updater.ts
|
|
3437
3684
|
import { execFile } from "child_process";
|
|
3438
3685
|
import { promisify } from "util";
|