soulbridge 0.0.101 → 0.0.103
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/packages/core/src/consolidate.js +7 -3
- package/dist/packages/core/src/extract.js +15 -2
- package/dist/packages/core/src/index.d.ts +1 -1
- package/dist/packages/core/src/index.js +17 -9
- package/dist/packages/core/src/profile-prompt.js +3 -3
- package/dist/packages/core/src/prompts.generated.d.ts +1 -1
- package/dist/packages/core/src/prompts.generated.js +1 -1
- package/dist/packages/core/src/sqlite.d.ts +2 -1
- package/dist/packages/core/src/sqlite.js +44 -14
- package/dist/packages/core/src/store.d.ts +12 -3
- package/dist/packages/core/src/types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -245,8 +245,10 @@ export function parseProfileMerge(raw, allowed, expected) {
|
|
|
245
245
|
// model mixed two lenses back together, which is the welding the split prevents.
|
|
246
246
|
if (expected && dimension !== expected)
|
|
247
247
|
continue;
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
// One name for the field now; 来源 is accepted only so an older prompt in flight
|
|
249
|
+
// still parses.
|
|
250
|
+
const sourceField = fields.find((field) => /^(证据|来源)\s*[==]/.test(field));
|
|
251
|
+
const sources = (sourceField?.replace(/^(证据|来源)\s*[==]\s*/, '') ?? '')
|
|
250
252
|
.split(/[,,、;;\s]+/)
|
|
251
253
|
.map((id) => id.trim())
|
|
252
254
|
.filter((id) => allowed.has(id));
|
|
@@ -277,7 +279,9 @@ export function parseProfileMerge(raw, allowed, expected) {
|
|
|
277
279
|
* so both are read — a line that names its sources can never be merge input.
|
|
278
280
|
*/
|
|
279
281
|
export function isDerivedProfileLine(fact) {
|
|
280
|
-
|
|
282
|
+
// 证据 is the one name for "this line cites its sources"; 来源 was the old spelling and 合并
|
|
283
|
+
// marks a merged line that has no sources of its own.
|
|
284
|
+
return (fact.slots ?? []).some((slot) => slot.slot === '证据' || slot.slot === '合并');
|
|
281
285
|
}
|
|
282
286
|
/**
|
|
283
287
|
* Cut the merge input along dimensions before any model sees it.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renderExtractionPrompt } from './prompts.js';
|
|
2
|
-
import { SLOT_ENTITY_MARKER } from './config/domains.js';
|
|
2
|
+
import { SLOT_ENTITY_MARKER, FACT_ROLES } from './config/domains.js';
|
|
3
3
|
/**
|
|
4
4
|
* What a fact's confidence is when the model did not give one.
|
|
5
5
|
*
|
|
@@ -176,6 +176,9 @@ export function extractByRules(text, at = Date.now()) {
|
|
|
176
176
|
continue;
|
|
177
177
|
facts.push({
|
|
178
178
|
statement: toStatement(sentence),
|
|
179
|
+
// The floor's own decision: a rule can recognise a sentence but cannot judge what it
|
|
180
|
+
// is for, so it stores the minimal claim. The model path may say otherwise.
|
|
181
|
+
role: 'observation',
|
|
179
182
|
typeName: verb?.word,
|
|
180
183
|
at: time.at,
|
|
181
184
|
atText: time.atText,
|
|
@@ -320,6 +323,7 @@ function extractJson(raw) {
|
|
|
320
323
|
}
|
|
321
324
|
const ENTITY_TYPES = new Set(['person', 'thing', 'place', 'self']);
|
|
322
325
|
const PRECISIONS = new Set(['day', 'range', 'relative', 'unknown']);
|
|
326
|
+
const ROLES = new Set(FACT_ROLES);
|
|
323
327
|
const SOURCES = new Set(['experienced', 'told', 'inferred']);
|
|
324
328
|
const OPS = new Set(['add', 'supersede', 'update', 'noop']);
|
|
325
329
|
/** One fact payload, shared by the legacy `facts` array and the operations. */
|
|
@@ -355,8 +359,12 @@ function parseFactDraft(obj) {
|
|
|
355
359
|
: atText
|
|
356
360
|
? 'relative'
|
|
357
361
|
: 'unknown';
|
|
362
|
+
const roleRaw = typeof obj.role === 'string' ? obj.role.trim() : '';
|
|
358
363
|
return {
|
|
359
364
|
statement,
|
|
365
|
+
// The model's structural answer. An unrecognised value is dropped rather than guessed:
|
|
366
|
+
// the writer's neutral default is observation, never a guess from the type label.
|
|
367
|
+
...(roleRaw && ROLES.has(roleRaw) ? { role: roleRaw } : {}),
|
|
360
368
|
// `world` unless the model named a mind; the commit gate resolves the name.
|
|
361
369
|
holder: holderRaw || 'world',
|
|
362
370
|
typeName: typeof obj.type_name === 'string' ? obj.type_name : undefined,
|
|
@@ -525,12 +533,17 @@ export function parseLineDraft(raw) {
|
|
|
525
533
|
let supersedes = '';
|
|
526
534
|
let wantsSupersede = false;
|
|
527
535
|
for (const part of rest) {
|
|
528
|
-
const keyed = /^(
|
|
536
|
+
const keyed = /^(持有|精度|原则候选|槽位|操作|角色)\s*[==]\s*(.*)$/.exec(part);
|
|
529
537
|
if (keyed) {
|
|
530
538
|
if (keyed[1] === '持有')
|
|
531
539
|
fact.holder = keyed[2].trim();
|
|
532
540
|
else if (keyed[1] === '精度')
|
|
533
541
|
fact.at_precision = keyed[2].trim();
|
|
542
|
+
else if (keyed[1] === '角色') {
|
|
543
|
+
const role = keyed[2].trim();
|
|
544
|
+
if (ROLES.has(role))
|
|
545
|
+
fact.role = role;
|
|
546
|
+
}
|
|
534
547
|
else if (keyed[1] === '原则候选') {
|
|
535
548
|
// Only 是 promotes. "否" and anything else is the safe default.
|
|
536
549
|
if (/^(是|yes|true)$/i.test(keyed[2].trim()))
|
|
@@ -277,7 +277,7 @@ export declare class Soul {
|
|
|
277
277
|
*
|
|
278
278
|
* Neither bucket is safe for it: as a rule it would fire every turn, and as a
|
|
279
279
|
* profile line it would put an imperative into the description of a person. It
|
|
280
|
-
*
|
|
280
|
+
* becomes a candidate — held in the store, injected nowhere — and shows up in a review count.
|
|
281
281
|
*/
|
|
282
282
|
markUnclassified(factId: string): void;
|
|
283
283
|
/** The judgments about the person that are waiting to be merged. */
|
|
@@ -181,7 +181,9 @@ export class Soul {
|
|
|
181
181
|
* code would be the guess ADR 0027 forbids.
|
|
182
182
|
*/
|
|
183
183
|
promotionCandidates(turnId) {
|
|
184
|
-
return this.store
|
|
184
|
+
return this.store
|
|
185
|
+
.factsByTurns([turnId], { includeCandidates: true })
|
|
186
|
+
.filter((fact) => fact.role === 'lesson' && fact.status === 'candidate');
|
|
185
187
|
}
|
|
186
188
|
/**
|
|
187
189
|
* Store one exchange (RFC 0007). Idempotent on `(sessionId, turn)`: a repeat is
|
|
@@ -494,11 +496,11 @@ export class Soul {
|
|
|
494
496
|
skipped.push({ what: statement, reason: 'duplicate' });
|
|
495
497
|
return null;
|
|
496
498
|
}
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
|
|
499
|
+
// An unclassified lesson is a candidate: a lesson the model flagged but did not
|
|
500
|
+
// place in a role. It is held and shown to the classifier, injected nowhere.
|
|
501
|
+
// This replaced the old `原则候选` slot, which was the same decision stored as text.
|
|
502
|
+
const role = factDraft.principle && !factDraft.role ? 'lesson' : factDraft.role;
|
|
503
|
+
const status = factDraft.principle && !factDraft.role ? 'candidate' : undefined;
|
|
502
504
|
// ADR 0035: a fact from my side of the exchange is `inferred`, and inferred
|
|
503
505
|
// claims must carry their evidence. The turn id is the evidence available here —
|
|
504
506
|
// claims that need a tool or test result are kept out by the prompt instead.
|
|
@@ -510,6 +512,8 @@ export class Soul {
|
|
|
510
512
|
userId: this.userId,
|
|
511
513
|
statement,
|
|
512
514
|
holder,
|
|
515
|
+
role,
|
|
516
|
+
status,
|
|
513
517
|
typeName: factDraft.typeName,
|
|
514
518
|
at: factDraft.at,
|
|
515
519
|
atText: factDraft.atText,
|
|
@@ -638,6 +642,7 @@ export class Soul {
|
|
|
638
642
|
soulId: this.soulId,
|
|
639
643
|
userId: this.userId,
|
|
640
644
|
statement: gate.statement,
|
|
645
|
+
role: 'judgment',
|
|
641
646
|
typeName: '结论',
|
|
642
647
|
holder: 'self',
|
|
643
648
|
confidence: gate.confidence,
|
|
@@ -683,6 +688,7 @@ export class Soul {
|
|
|
683
688
|
* have to be always-on, which is exactly what this split exists to avoid.
|
|
684
689
|
*/
|
|
685
690
|
markRule(factId, classification) {
|
|
691
|
+
this.store.setFactRole(factId, 'rule');
|
|
686
692
|
this.store.setFactTypeName(factId, '规则');
|
|
687
693
|
const slots = [
|
|
688
694
|
{ slot: '触发', value: { kind: 'text', value: classification.trigger } },
|
|
@@ -693,6 +699,7 @@ export class Soul {
|
|
|
693
699
|
}
|
|
694
700
|
/** Apply a profile classification: it is about the person, not a triggered rule. */
|
|
695
701
|
markProfile(factId, options = {}) {
|
|
702
|
+
this.store.setFactRole(factId, 'profile');
|
|
696
703
|
this.store.setFactTypeName(factId, '画像');
|
|
697
704
|
// The dimension is written here because this is the moment the model decided
|
|
698
705
|
// which lens the judgment belongs to. The merge step cuts its batches along
|
|
@@ -708,10 +715,10 @@ export class Soul {
|
|
|
708
715
|
*
|
|
709
716
|
* Neither bucket is safe for it: as a rule it would fire every turn, and as a
|
|
710
717
|
* profile line it would put an imperative into the description of a person. It
|
|
711
|
-
*
|
|
718
|
+
* becomes a candidate — held in the store, injected nowhere — and shows up in a review count.
|
|
712
719
|
*/
|
|
713
720
|
markUnclassified(factId) {
|
|
714
|
-
this.store.
|
|
721
|
+
this.store.setFactStatus(factId, 'candidate');
|
|
715
722
|
}
|
|
716
723
|
/** The judgments about the person that are waiting to be merged. */
|
|
717
724
|
profileItems(limit = 60) {
|
|
@@ -737,12 +744,13 @@ export class Soul {
|
|
|
737
744
|
soulId: this.soulId,
|
|
738
745
|
userId: this.userId,
|
|
739
746
|
statement: line.statement,
|
|
747
|
+
role: 'profile',
|
|
740
748
|
typeName: '画像',
|
|
741
749
|
holder: 'self',
|
|
742
750
|
// More sources means more observations behind the line, so it ranks first.
|
|
743
751
|
confidence: Math.min(0.95, 0.6 + 0.05 * line.sources.length),
|
|
744
752
|
slots: [
|
|
745
|
-
...line.sources.map((id) => ({ slot: '
|
|
753
|
+
...line.sources.map((id) => ({ slot: '证据', value: { kind: 'fact', factId: id } })),
|
|
746
754
|
// A merged line is not an original: re-merging merged lines loses detail.
|
|
747
755
|
{ slot: '合并', value: { kind: 'text', value: '1' } },
|
|
748
756
|
{ slot: '维度', value: { kind: 'text', value: line.dimension } },
|
|
@@ -41,7 +41,7 @@ const PROFILE_RULES = `<rules>
|
|
|
41
41
|
- 不许出现"一切/所有/总是/从不",除非来源原文里就有。
|
|
42
42
|
- 每行给一个维度:${dimensionChoices()}
|
|
43
43
|
- 一行只讲一个维度。不同维度必须分行——跨维度拼成一句就是没拆。
|
|
44
|
-
-
|
|
44
|
+
- 每条必须给证据:证据=fc_xxx,fc_yyy,只能引用 <input> 里出现过的 id;没有证据的整行不要。
|
|
45
45
|
- 合并后 5–8 条;宁少不凑。
|
|
46
46
|
- 输入里没有的新判断,不许出现。
|
|
47
47
|
</rules>`;
|
|
@@ -68,8 +68,8 @@ const PROFILE_EXAMPLE = `<example>
|
|
|
68
68
|
- fc_c | 用户很讨厌一件事反复讲,换工具又要重新交代
|
|
69
69
|
</input>
|
|
70
70
|
<output>
|
|
71
|
-
画像: 要机制,不要靠 prompt 的自觉 | 维度=${PROFILE_DIMENSIONS[1]} |
|
|
72
|
-
画像: 讨厌重复交代,工具换了也不该重讲 | 维度=${PROFILE_DIMENSIONS[2]} |
|
|
71
|
+
画像: 要机制,不要靠 prompt 的自觉 | 维度=${PROFILE_DIMENSIONS[1]} | 证据=fc_a,fc_b
|
|
72
|
+
画像: 讨厌重复交代,工具换了也不该重讲 | 维度=${PROFILE_DIMENSIONS[2]} | 证据=fc_c
|
|
73
73
|
</output>
|
|
74
74
|
</example>`;
|
|
75
75
|
/**
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
export declare const extractionExamples = "<examples>\n<example>\n<source>\u6211\u540C\u4E8B\u5C0F\u6797\u4E0A\u5468\u88AB\u88C1\u5458\u4E86\uFF0C\u633A\u96BE\u53D7\u7684</source>\n<output>\u5B9E\u4F53: \u5C0F\u6797 | person | \u540C\u4E8B\n\u4E8B\u5B9E: \u5C0F\u6797\u5728 2026-10-24 \u90A3\u5468\u88AB\u88C1\u5458 | \u88C1\u5458 | 2026-10-24 | \u4E0A\u5468 | told | 0.8 | \u6301\u6709=world | \u7CBE\u5EA6=day | \u88AB\u88C1\u5458\u8005=@\u5C0F\u6797</output>\n</example>\n<example>\n<source>\u8FD9\u4E2A bug \u6211\u4E0A\u5468\u5C31\u4FEE\u597D\u4E86</source>\n<output>\u4E8B\u5B9E: \u7528\u6237\u8BF4\u4ED6\u5728 2026-10-24 \u90A3\u5468\u4FEE\u597D\u4E86\u8FD9\u4E2A bug | \u4FEE\u590D | 2026-10-24 | \u4E0A\u5468 | experienced | 0.7 | \u6301\u6709=world | \u7CBE\u5EA6=day | \u4FEE\u590D\u8005=\u7528\u6237</output>\n</example>\n<example>\n<source>\u597D\u7684\uFF0C\u8C22\u8C22</source>\n<output>\u65E0</output>\n</example>\n<example>\n<source>\u4F60\u5E2E\u6211\u628A\u90A3\u4E2A\u8868\u91CD\u65B0\u8DD1\u4E00\u904D</source>\n<output>\u65E0</output>\n</example>\n<example>\n<source>\u6211\u8BA8\u538C\u540C\u4E00\u4EF6\u4E8B\u53CD\u590D\u8BB2\uFF0C\u6362\u4E2A\u5DE5\u5177\u53C8\u8981\u91CD\u65B0\u8BF4\u4E00\u904D</source>\n<output>\u4E8B\u5B9E: \u7528\u6237\u8BA8\u538C\u540C\u4E00\u4EF6\u4E8B\u53CD\u590D\u8BB2\uFF0C\u6362\u5DE5\u5177\u4E5F\u4E0D\u60F3\u91CD\u590D\u4EA4\u4EE3 | \u504F\u597D | | | experienced | 0.9 | \u6301\u6709=\u7528\u6237 | \u7CBE\u5EA6=unknown | \u504F\u597D\u4E3B\u4F53=\u7528\u6237</output>\n</example>\n</examples>";
|
|
6
6
|
export declare const extractionRole = "<role>\n\u4F60\u662F\u8BB0\u5FC6\u62BD\u53D6\u5668\u3002\u4F60\u53EA\u4ECE\u5BF9\u8BDD\u91CC\u62BD\u53D6\u53EF\u9A8C\u8BC1\u7684\u4E8B\u5B9E\uFF0C\u4E0D\u505A\u5224\u65AD\u3001\u4E0D\u7ED9\u5EFA\u8BAE\u3001\u4E0D\u5199\u603B\u7ED3\u3002\n\u4F60\u7684\u8F93\u51FA\u7531\u4E00\u4E2A\u786E\u5B9A\u6027\u7684\u7A0B\u5E8F\u6D88\u8D39\uFF0C\u5B83\u4E0D\u4F1A\u8BFB\u4F60\u7684\u89E3\u91CA\uFF1B\u683C\u5F0F\u9519\u4E00\u4E2A\u5B57\u6BB5\uFF0C\u8FD9\u6761\u8BB0\u5FC6\u5C31\u4E22\u4E86\u3002\n</role>";
|
|
7
7
|
export declare const extractionRules = "<rules>\n\u672F\u8BED\u7684\u542B\u4E49\u89C1 <terms>\uFF1B\u8FD9\u91CC\u53EA\u8BF4\u64CD\u4F5C\u548C\u53D6\u820D\u3002\n- \u5408\u5E76\uFF1A\u4E0E <known-facts> \u8BED\u4E49\u76F8\u540C \u2192 noop\uFF1B\u4E92\u76F8\u77DB\u76FE \u2192 supersede \u5E76\u7ED9 target\uFF1B\u662F\u8865\u5145 \u2192 update\u3002\n \u76F8\u5173\u4F46\u72EC\u7ACB \u2192 add\u3002\u4E0D\u8981\u7528 <known-facts> \u91CC\u6CA1\u6709\u7684\u65B0\u4E8B\u5B9E\u53BB supersede\u3002\n- \u8981\u53D6\u4EE3\u65E7\u4E8B\u5B9E\u65F6\uFF0C\u5728\u4E8B\u5B9E\u884C\u5C3E\u90E8\u5199\u300C\u64CD\u4F5C=\u53D6\u4EE3 <id>\u300D\uFF08id \u53EA\u80FD\u6765\u81EA <known-facts>\uFF09\u3002\n \u4E0D\u5199\u5C31\u662F\u65B0\u589E\u3002\u4EE3\u7801\u4F1A\u5148\u786E\u8BA4\u90A3\u4E2A id \u771F\u5B9E\u5B58\u5728\uFF1B\u7F16\u9020\u7684 id \u4F1A\u9000\u5316\u6210\u65B0\u589E\u5E76\u8BB0\u539F\u56E0\u3002\n- \u69FD\u4F4D\u53EA\u653E\u53C2\u4E0E\u8005\uFF0C\u4E0D\u653E\u8BDD\u9898\uFF1A\u5B81\u53EF\u5C11\u5199\u4E00\u4E2A\u69FD\u4F4D\uFF0C\u4E5F\u4E0D\u8981\u628A\u4E00\u53E5\u8BDD\u585E\u8FDB\u69FD\u4F4D\u3002\n- \u4E0D\u786E\u5B9A\u5C31\u7701\u7565\u5B57\u6BB5\uFF0C\u7EDD\u4E0D\u7F16\u9020\u6570\u5B57\u3001\u65E5\u671F\u3001\u5173\u7CFB\u3002\n- \u4E0D\u8981\u91CD\u590D\u7528\u6237\u521A\u843D\u7684\u8BDD\uFF08\u56DE\u58F0\u4E0D\u7B97\u65B0\u4E8B\u5B9E\uFF09\u3002\n- \u6765\u6E90\u6309\u300C\u8C01\u8BF4\u7684\u300D\u5B9A\uFF08<source> \u91CC\u6807\u4E86\u53CC\u65B9\uFF09\uFF1A\u7528\u6237\u90A3\u4E00\u4FA7\u5199 told \u6216 experienced\uFF1B\n **\u6211\u90A3\u4E00\u4FA7\u5199 inferred**\u3002\u6211\u7684\u627F\u8BFA/\u51B3\u5B9A\u5199 \u7C7B\u578B\u540D=\u627F\u8BFA/\u51B3\u5B9A\uFF1B\n \u6211\u7684\u5B8C\u6210\u58F0\u660E\uFF08\"\u6211\u6539\u597D\u4E86\"\"\u5DF2\u7ECF\u4FEE\u4E86\"\uFF09\u5982\u679C\u6CA1\u6709\u5DE5\u5177\u6216\u6D4B\u8BD5\u7ED3\u679C\u4F5C\u4E3A\u4F9D\u636E\uFF0C**\u4E0D\u8981\u5199**\u3002\n \u81EA\u8FF0\uFF08\"\u6211\u662F\u5C0F\u7075\uFF0C\u4E00\u4E2A\u6301\u7EED\u7684\u4F19\u4F34\"\uFF09\u6C38\u8FDC\u4E0D\u5199\u3002\n- \u7EBF\u7D22\uFF1A\u5019\u9009\u89C1 <open-threads>\uFF0C\u6BCF\u6761\u5E26\u5B83\u6700\u8FD1\u51E0\u8F6E\u7684\u539F\u6587\u3002**\u9ED8\u8BA4\u7EE7\u7EED**\uFF1A\u8FD9\u4E00\u8F6E\u5728\u804A\u5019\u9009\u91CC\u7684\u67D0\u4E00\u6761\uFF0C\n \u5C31\u5199\u300C\u7EE7\u7EED <id>\u300D\u3002\u53EA\u6709\u51FA\u73B0\u660E\u786E\u8F6C\u6298\uFF08\"\u53E6\u5916 / \u6362\u4E2A\u8BDD\u9898 / \u8BF4\u56DE / \u4E0D\u804A\u8FD9\u4E2A\u4E86\"\uFF09\uFF0C\n \u6216\u8FD9\u4E00\u8F6E\u4E0E\u6240\u6709\u5019\u9009\u7684\u4E3B\u9898\u3001\u5B9E\u4F53\u90FD\u4E0D\u76F8\u5E72\u65F6\uFF0C\u624D\u5199\u300C\u7EBF\u7D22: \u65B0 | \u4E00\u53E5\u8BDD\u4E3B\u9898\u300D\u3002\n \u62FF\u4E0D\u51C6\u5C31\u7EE7\u7EED\u6700\u63A5\u8FD1\u7684\u90A3\u4E00\u6761\u2014\u2014\u7EBF\u7D22\u788E\u6389\u4F1A\u8BA9\u540E\u9762\u7684\u5F52\u7EB3\u65E0\u6CD5\u8FDB\u884C\uFF0C\u63A5\u9519\u53EF\u4EE5\u4E8B\u540E\u62C6\u5F00\u3002\n \u7EE7\u7EED\u65F6\uFF0C\u5982\u679C\u8FD9\u4E00\u8F6E\u5DF2\u7ECF\u4E0D\u662F\u5728\u804A\u5019\u9009\u539F\u6765\u7684\u4E3B\u9898\uFF08\u8BDD\u9898\u5728\u540C\u4E00\u7EBF\u7D22\u5185\u5F80\u524D\u8D70\u4E86\uFF09\uFF0C\n \u8865\u4E00\u53E5\u300C| \u4E3B\u9898=\u73B0\u5728\u804A\u7684\u662F\u4EC0\u4E48\u300D\u2014\u2014\u7EBF\u7D22\u7684\u4E3B\u9898\u662F\u7ED9\u540E\u7EED\u8BFB\u7684\u4EBA\u770B\u7684\uFF0C\u4E0D\u66F4\u65B0\u5C31\u4F1A\u4E00\u76F4\u505C\u5728\u7B2C\u4E00\u8F6E\u7684\u8BF4\u6CD5\u3002\n \u6CA1\u6709\u4EFB\u4F55\u5019\u9009\u65F6\u8FD9\u4E00\u884C\u53EA\u53EF\u80FD\u662F\"\u65B0\"\u3002\n</rules>\n\n<when-to-write>\n\u53EA\u5728\u8FD9\u56DB\u79CD\u60C5\u51B5\u4E0B\u5199\u4E8B\u5B9E\uFF1A\n1. \u67D0\u4E2A\u4EBA\u6216\u67D0\u4EF6\u4E8B\u7684\u72B6\u6001\u3001\u7ECF\u5386\uFF08\u201C\u5C0F\u6797\u6362\u4E86\u5DE5\u4F5C\u201D\u201C\u90A3\u4E2A\u9879\u76EE\u9EC4\u4E86\u201D\uFF09\n2. \u4E00\u4E2A\u51B3\u5B9A\uFF0C\u4EE5\u53CA\u505A\u8FD9\u4E2A\u51B3\u5B9A\u7684\u7406\u7531\n3. \u7528\u6237\u8868\u8FBE\u7684\u504F\u597D\u3001\u539F\u5219\u6216\u4E60\u60EF\uFF08\u201C\u6211\u8BA8\u538C\u53CD\u590D\u8BB2\u540C\u4E00\u4EF6\u4E8B\u201D\uFF09\n4. \u6211\u7ED9\u51FA\u7684\u627F\u8BFA\uFF08\u201C\u6211\u660E\u5929\u628A\u8868\u8865\u4E0A\u201D\uFF09\n\n**\u4E0D\u8981\u5199**\uFF08\u8FD9\u662F\u6700\u5E38\u89C1\u7684\u566A\u58F0\uFF09\uFF1A\n- \u7528\u6237\u5BF9\u6211\u7684\u8981\u6C42\u3001\u63D0\u95EE\u3001\u50AC\u4FC3\u3001\u540C\u610F\u3001\u5426\u5B9A\u2014\u2014\u8FD9\u4E9B\u662F\u5BF9\u8BDD\u52A8\u4F5C\uFF0C\u5BF9\u8BDD\u539F\u6587\u5DF2\u7ECF\u4FDD\u5B58\uFF0C\u518D\u5199\u4E00\u4EFD\u53EA\u4F1A\u53D8\u6210\u566A\u58F0\n- \u6211\u95EE\u7528\u6237\u7684\u6F84\u6E05\u95EE\u9898\n- \u5BD2\u6684\u3001\u6211\u7684\u81EA\u6211\u4ECB\u7ECD\u3001\u6211\u5BF9\u80FD\u529B\u7684\u8BF4\u660E\n- \u7528\u6237\u6B63\u5728\u95EE\u6211\u7684\u4E8B\uFF0C\u4E0D\u662F\u201C\u5173\u4E8E\u7528\u6237\u7684\u4E8B\u5B9E\u201D\n</when-to-write>\n\n<gaps>\n\u53EA\u6709\u4E24\u79CD\u60C5\u51B5\u5EFA\u7F3A\u53E3\uFF1A\n1. \u7528\u6237\u8BF4\u81EA\u5DF1\u4E0D\u77E5\u9053\u3001\u6CA1\u6D88\u606F\u3001\u60F3\u77E5\u9053\u67D0\u4EF6\u4E8B\uFF08\u201C\u5979\u540E\u6765\u600E\u4E48\u6837\u4E86\u201D\u201C\u4E0D\u77E5\u9053\u8FD8\u5728\u4E0D\u5728\u201D\uFF09\n2. \u6211\u8BF4\u8FC7\u8981\u505A\u3001\u4F46\u72B6\u6001\u4ECD\u672A\u5B8C\u6210\u7684\u4E8B\n\n\u7528\u6237\u53EA\u662F\u5728\u95EE\u6211\u4E00\u4E2A\u95EE\u9898\u65F6**\u4E0D\u8981**\u5EFA\u7F3A\u53E3\u2014\u2014\u90A3\u662F\u4ED6\u5728\u63D0\u95EE\uFF0C\u4E0D\u662F\u4ED6\u7F3A\u4FE1\u606F\u3002\n</gaps>";
|
|
8
|
-
export declare const extractionSchema = "<output-format>\n\u4E0D\u8981\u8F93\u51FA JSON\u3002\u6309\u4E0B\u9762\u7684\u8868\u683C\u4E00\u884C\u4E00\u6761\u586B\u5199\uFF0C\u5B57\u6BB5\u4E4B\u95F4\u7528 | \u5206\u9694\uFF0C\u6CA1\u6709\u5185\u5BB9\u7684\u5B57\u6BB5\u7559\u7A7A\u3002\n\u53EF\u4EE5\u53EA\u5199\u5176\u4E2D\u51E0\u884C\uFF1B\u4EC0\u4E48\u90FD\u8BB0\u4E0D\u4F4F\u5C31\u53EA\u5199\uFF1A\u65E0\n\n\u5B9E\u4F53: \u540D\u5B57 | \u7C7B\u578B | \u5173\u7CFB\n\u4E8B\u5B9E: \u9648\u8FF0 | \u7C7B\u578B\u540D | \u65E5\u671F(YYYY-MM-DD) | \u65F6\u95F4\u539F\u6587 | \u6765\u6E90 | \u7F6E\u4FE1(0-1) | \u6301\u6709=world\u6216\u4EBA\u540D | \u7CBE\u5EA6=day/range/relative/unknown | \u539F\u5219\u5019\u9009=\u662F | \u69FD\u4F4D | \u64CD\u4F5C=\u53D6\u4EE3 <id>\n\u7F3A\u53E3: \u4E3B\u9898 | \u5F20\u529B(0-1) | \u76F8\u5173\u5B9E\u4F53\n\u7EBF\u7D22: \u7EE7\u7EED <id> \u6216 \u7EBF\u7D22: \u65B0 | \u4E00\u53E5\u8BDD\u4E3B\u9898\n \u8BDD\u9898\u5728\u540C\u4E00\u7EBF\u7D22\u5185\u53D8\u4E86\uFF0C\u5C31\u5199\uFF1A\u7EBF\u7D22: \u7EE7\u7EED <id> | \u4E3B\u9898=\u65B0\u7684\u4E00\u53E5\u8BDD\u4E3B\u9898\n\n\u6BCF\u4E2A\u5B57\u6BB5\u7684\u8FB9\u754C\u89C1 <terms>\uFF0C\u8FD9\u91CC\u53EA\u8BF4\u683C\u5F0F\uFF1A\u65E5\u671F\u5FC5\u987B\u662F YYYY-MM-DD\uFF1B\u69FD\u4F4D\u5199\u6210 \u540D\u79F0=\u503C\uFF0C\u591A\u4E2A\u7528 ; \u9694\u5F00\uFF1B\n\"\u6301\u6709/\u7CBE\u5EA6/\u539F\u5219\u5019\u9009\"\u662F\u952E\u503C\u5BF9\uFF0C\u5199\u5728\u4E8B\u5B9E\u884C\u7684\u5C3E\u90E8\uFF1B\u7EBF\u7D22\u884C\u4E8C\u9009\u4E00\uFF08\u7EE7\u7EED\u65F6\u53EF\u9644 \u4E3B\u9898=\uFF09\u3002\n\u793A\u4F8B\uFF1A\n\u5B9E\u4F53: \u5C0F\u6797 | person | \u540C\u4E8B\n\u4E8B\u5B9E: \u5C0F\u6797\u5728 2026-10-24 \u90A3\u5468\u88AB\u88C1\u5458 | \u88C1\u5458 | 2026-10-24 | \u4E0A\u5468 | told | 0.8 | \u6301\u6709=world | \u7CBE\u5EA6=day | \u88AB\u88C1\u5458\u8005=@\u5C0F\u6797\n\u4E8B\u5B9E: \u7528\u6237\u8981\u6C42\u6539\u52A8\u524D\u5148\u8BF4\u660E\u4E3A\u4EC0\u4E48\u3001\u600E\u4E48\u505A\u3001\u6709\u4EC0\u4E48\u5F71\u54CD | \u8981\u6C42 | | | experienced | 0.9 | \u6301\u6709=\u7528\u6237 | \u7CBE\u5EA6=unknown | \u539F\u5219\u5019\u9009=\u662F | \u5BF9\u8C61=@\u7528\u6237\n\u7F3A\u53E3: \u5C0F\u6797\u540E\u6765\u6709\u7740\u843D\u5417 | 0.6 | \u5C0F\u6797\n\u7EBF\u7D22: \u7EE7\u7EED th_ab12\n</output-format>";
|
|
8
|
+
export declare const extractionSchema = "<output-format>\n\u4E0D\u8981\u8F93\u51FA JSON\u3002\u6309\u4E0B\u9762\u7684\u8868\u683C\u4E00\u884C\u4E00\u6761\u586B\u5199\uFF0C\u5B57\u6BB5\u4E4B\u95F4\u7528 | \u5206\u9694\uFF0C\u6CA1\u6709\u5185\u5BB9\u7684\u5B57\u6BB5\u7559\u7A7A\u3002\n\u53EF\u4EE5\u53EA\u5199\u5176\u4E2D\u51E0\u884C\uFF1B\u4EC0\u4E48\u90FD\u8BB0\u4E0D\u4F4F\u5C31\u53EA\u5199\uFF1A\u65E0\n\n\u5B9E\u4F53: \u540D\u5B57 | \u7C7B\u578B | \u5173\u7CFB\n\u4E8B\u5B9E: \u9648\u8FF0 | \u7C7B\u578B\u540D | \u65E5\u671F(YYYY-MM-DD) | \u65F6\u95F4\u539F\u6587 | \u6765\u6E90 | \u7F6E\u4FE1(0-1) | \u6301\u6709=world\u6216\u4EBA\u540D | \u7CBE\u5EA6=day/range/relative/unknown | \u539F\u5219\u5019\u9009=\u662F | \u89D2\u8272=observation/judgment/rule/profile/promise/lesson | \u69FD\u4F4D | \u64CD\u4F5C=\u53D6\u4EE3 <id>\n\u7F3A\u53E3: \u4E3B\u9898 | \u5F20\u529B(0-1) | \u76F8\u5173\u5B9E\u4F53\n\u7EBF\u7D22: \u7EE7\u7EED <id> \u6216 \u7EBF\u7D22: \u65B0 | \u4E00\u53E5\u8BDD\u4E3B\u9898\n \u8BDD\u9898\u5728\u540C\u4E00\u7EBF\u7D22\u5185\u53D8\u4E86\uFF0C\u5C31\u5199\uFF1A\u7EBF\u7D22: \u7EE7\u7EED <id> | \u4E3B\u9898=\u65B0\u7684\u4E00\u53E5\u8BDD\u4E3B\u9898\n\n\u6BCF\u4E2A\u5B57\u6BB5\u7684\u8FB9\u754C\u89C1 <terms>\uFF0C\u8FD9\u91CC\u53EA\u8BF4\u683C\u5F0F\uFF1A\u65E5\u671F\u5FC5\u987B\u662F YYYY-MM-DD\uFF1B\u69FD\u4F4D\u5199\u6210 \u540D\u79F0=\u503C\uFF0C\u591A\u4E2A\u7528 ; \u9694\u5F00\uFF1B\n\"\u6301\u6709/\u7CBE\u5EA6/\u539F\u5219\u5019\u9009/\u89D2\u8272\"\u662F\u952E\u503C\u5BF9\uFF0C\u5199\u5728\u4E8B\u5B9E\u884C\u7684\u5C3E\u90E8\uFF1B\u7EBF\u7D22\u884C\u4E8C\u9009\u4E00\uFF08\u7EE7\u7EED\u65F6\u53EF\u9644 \u4E3B\u9898=\uFF09\u3002\n\u89D2\u8272\u662F\u8FD9\u4E2A\u4E8B\u5B9E\u4EE5\u540E**\u88AB\u8C01\u7528**\uFF1A\u89C2\u5BDF\u7528 observation\uFF0C\u5224\u65AD/\u51B3\u5B9A\u7528 judgment\uFF0C\u89C4\u5219\u7528 rule\uFF0C\n\u753B\u50CF\u7528 profile\uFF0C\u6211\u7B54\u5E94\u7684\u4E8B\u7528 promise\uFF0C\u6559\u8BAD\u7528 lesson\u3002\u62FF\u4E0D\u51C6\u5C31\u628A\u89D2\u8272\u7559\u7A7A\u2014\u2014\u7A7A\u8868\u793A\u89C2\u5BDF\u3002\n\u793A\u4F8B\uFF1A\n\u5B9E\u4F53: \u5C0F\u6797 | person | \u540C\u4E8B\n\u4E8B\u5B9E: \u5C0F\u6797\u5728 2026-10-24 \u90A3\u5468\u88AB\u88C1\u5458 | \u88C1\u5458 | 2026-10-24 | \u4E0A\u5468 | told | 0.8 | \u6301\u6709=world | \u7CBE\u5EA6=day | \u88AB\u88C1\u5458\u8005=@\u5C0F\u6797\n\u4E8B\u5B9E: \u7528\u6237\u8981\u6C42\u6539\u52A8\u524D\u5148\u8BF4\u660E\u4E3A\u4EC0\u4E48\u3001\u600E\u4E48\u505A\u3001\u6709\u4EC0\u4E48\u5F71\u54CD | \u8981\u6C42 | | | experienced | 0.9 | \u6301\u6709=\u7528\u6237 | \u7CBE\u5EA6=unknown | \u539F\u5219\u5019\u9009=\u662F | \u89D2\u8272=rule | \u5BF9\u8C61=@\u7528\u6237\n\u7F3A\u53E3: \u5C0F\u6797\u540E\u6765\u6709\u7740\u843D\u5417 | 0.6 | \u5C0F\u6797\n\u7EBF\u7D22: \u7EE7\u7EED th_ab12\n</output-format>";
|
|
9
9
|
export declare const extractionTerms = "<terms>\n\u6BCF\u4E2A\u672F\u8BED\u7684\u542B\u4E49\u548C\u8FB9\u754C\u3002\u5199\u4E4B\u524D\u5148\u5224\u65AD\u5B83\u5C5E\u4E8E\u54EA\u4E2A\u672F\u8BED\u2014\u2014\u653E\u9519\u4F4D\u7F6E\u6BD4\u6F0F\u5199\u66F4\u7CDF\u3002\n\n**\u5B9E\u4F53**\uFF1A\u4E00\u4E2A\u5177\u4F53\u7684\u4EBA\u3001\u7269\u3001\u5730\u70B9\u6216\u5E73\u53F0\u3002\u5B83\u662F\"\u8C01/\u4EC0\u4E48\"\uFF0C\u4E0D\u662F\"\u505A\u4E86\u4EC0\u4E48\"\u3002\n \u7B97\uFF1A\u5C0F\u6797\u3001\u7528\u6237\u3001TypeScript\u3001\u6296\u97F3\u3001PowerShell\u3001config \u6587\u4EF6\u5939\n \u4E0D\u7B97\uFF1A\u4E0D\u5220\u9664\u539F\u59CB\u5BF9\u8BDD\u3001\u4ECE\u6839\u4E0A\u89E3\u51B3\u3001\u5FC5\u8981\u3001\u566A\u58F0\u8BB0\u5FC6\u3001\u60C5\u7EEA\u6CE8\u5165\u3001\u9879\u76EE\u8FDB\u5C55\n \u8FB9\u754C\uFF1A\u80FD\u7528\"\u8FD9\u8BF4\u7684\u662F\u8C01\"\u95EE\u51FA\u6765\u7684\u624D\u7B97\u3002\u4E00\u53E5\u8BDD\u3001\u4E00\u4E2A\u89C2\u70B9\u3001\u4E00\u4E2A\u505A\u6CD5\u90FD\u4E0D\u662F\u5B9E\u4F53\u3002\n \u5224\u636E\uFF1A\u8D85\u8FC7 12 \u4E2A\u5B57\uFF0C\u6216\u542B\"\u7684/\u548C/\u4E0E\"\uFF0C\u6216\u80FD\u52A0\u4E0A\"\u8FD9\u4EF6\u4E8B\"\u7684\uFF0C\u57FA\u672C\u90FD\u4E0D\u662F\u5B9E\u4F53\u3002\n \u540C\u4E00\u4E2A\u4EBA\u6216\u7269\u53EA\u51FA\u73B0\u4E00\u6B21\uFF1Btype \u53EA\u80FD\u662F person / thing / place / self\u3002\n\n**\u4E8B\u5B9E**\uFF1A\u4E00\u53E5\u81EA\u5305\u542B\u7684\u9648\u8FF0\uFF0C\u56DE\u7B54\"\u53D1\u751F\u4E86\u4EC0\u4E48\"\u6216\"\u67D0\u4EBA\u662F\u4EC0\u4E48\u6837\"\u3002\n \u7B97\uFF1A\u5C0F\u6797\u5728 2026-10-24 \u90A3\u5468\u88AB\u88C1\u5458\uFF1B\u7528\u6237\u8981\u6C42\u6539\u52A8\u524D\u5148\u8BF4\u660E\u5F71\u54CD\n \u4E0D\u7B97\uFF1A\u7528\u6237\u7684\u63D0\u95EE\u3001\u50AC\u4FC3\u3001\u540C\u610F\u3001\u5426\u5B9A\uFF1B\u5BD2\u6684\uFF1B\u6211\u5BF9\u81EA\u5DF1\u80FD\u529B\u7684\u8BF4\u660E\n \u8FB9\u754C\uFF1A\u628A\u4EE3\u8BCD\u6362\u6210\u540D\u5B57\u540E\u4ECD\u7136\u6210\u7ACB\uFF0C\u4E14\u6362\u4E2A\u4EBA\u590D\u8FF0\u4E0D\u9700\u8981\u52A0\u95EE\u53F7\u300215\u201380 \u5B57\uFF0C\u4FDD\u7559\u6570\u5B57\u548C\u4E13\u6709\u540D\u8BCD\u3002\n\n**\u6301\u6709**\uFF1A\u8FD9\u6761\u4E8B\u5B9E\u5728\u8C01\u7684\u89C6\u89D2\u91CC\u6210\u7ACB\u3002\u53EA\u6709\u4E24\u4E2A\u5408\u6CD5\u503C\u3002\n world\uFF1A\u4E16\u754C\u4E0A\u53D1\u751F\u7684\u4E8B\uFF0C\u7B2C\u4E09\u4E2A\u4EBA\u4E5F\u80FD\u6838\u5B9E\uFF08\u6362\u5DE5\u4F5C\u3001\u5403\u996D\u3001\u4E0B\u96E8\u3001\u7EA6\u5B9A\u6697\u53F7\uFF09\n \u4EBA\u540D\uFF1A\u67D0\u4EBA\u7684\u770B\u6CD5\u3001\u504F\u597D\u3001\u8981\u6C42\u3001\u62C5\u5FC3\uFF08\"\u7528\u6237\u8BA4\u4E3A\u2026\"\uFF09\uFF0C\u53EA\u6709\u90A3\u4E2A\u4EBA\u80FD\u786E\u8BA4\n \u8FB9\u754C\uFF1A\u80FD\u5199\u6210\u65B0\u95FB\u62A5\u9053\u7684 \u2192 world\uFF1B\u53EA\u80FD\u5199\u6210\"X \u8BA4\u4E3A\"\u7684 \u2192 \u4EBA\u540D\u3002\u4EBA\u7269\u5C42\u4E0D\u5199\u65E5\u671F\u3002\n\n**\u69FD\u4F4D**\uFF1A\u8FD9\u6761\u4E8B\u5B9E\u7684\u53C2\u4E0E\u8005\u4E0E\u53C2\u6570\uFF0C\u5199\u6210 \u540D\u79F0=\u503C\uFF0C\u591A\u4E2A\u7528 ; \u9694\u5F00\u3002\n \u53EA\u653E\"\u8C01\u3001\u5BF9\u8C01\u3001\u5728\u54EA\u91CC\u3001\u591A\u5C11\"\u8FD9\u7C7B\u7ED3\u6784\u4F4D\u7F6E\u3002\n \u7B97\uFF1A\u88AB\u88C1\u5458\u8005=@\u5C0F\u6797\uFF1B\u4EBA\u6570=300\uFF1B\u5730\u70B9=\u676D\u5DDE\n \u4E0D\u7B97\uFF1A\u4E8B\u9879=\u4E0D\u5220\u9664\u539F\u59CB\u5BF9\u8BDD\uFF1B\u505A\u6CD5=\u4ECE\u6839\u4E0A\u89E3\u51B3\uFF1B\u7406\u7531=\u5FC5\u8981\n \u8FB9\u754C\uFF1A\u503C\u8981\u4EE5\u5B9E\u4F53\u8EAB\u4EFD\u53C2\u4E0E\u65F6\uFF0C\u5199\u6210 **@\u540D\u5B57**\uFF1B\u4E0D\u5199 @ \u7684\u4E00\u5F8B\u5F53\u6587\u5B57\u3002\n @\u540D\u5B57 \u5FC5\u987B\u5728\u672C\u8F6E\u5B9E\u4F53\u884C\u58F0\u660E\uFF08\u6216\u5DF2\u5728 <known-entities> \u91CC\uFF09\uFF0C\u5426\u5219\u4ECD\u6309\u6587\u5B57\u4FDD\u5B58\u3001\u4E0D\u5EFA\u5B9E\u4F53\u3002\n\n**\u65E5\u671F\u4E0E\u7CBE\u5EA6**\uFF1A\u5148\u6309 <observation-date> \u628A\"\u6628\u5929/\u4E0A\u5468/\u4E0A\u4E2A\u6708\"\u6362\u7B97\u6210 YYYY-MM-DD \u586B\u5165\u65E5\u671F\u3002\n \u7CBE\u5EA6\u56DB\u503C\uFF1Aday \u786E\u5207\u65E5\u671F / range \u53EA\u77E5\u9053\u4E00\u5468\u4E00\u6708 / relative \u53EA\u6709\u76F8\u5BF9\u8BF4\u6CD5\uFF08\u4E0A\u6B21\u3001\u6700\u8FD1\u3001\u521D\u6B21\u89C1\u9762\uFF09/ unknown \u4E0D\u77E5\u9053\u3002\n \u6362\u7B97\u4E0D\u51FA\u6765\u5C31\u7559\u7A7A\u65E5\u671F\uFF0C\u628A\u539F\u8BDD\u5199\u8FDB\"\u65F6\u95F4\u539F\u6587\"\u3002**\u6CA1\u6709\u65F6\u95F4\u5C31\u5199 unknown\uFF0C\u7EDD\u4E0D\u7F16\u65E5\u671F\u3002**\n\n**\u6765\u6E90**\uFF1Aexperienced \u4F60\u4EB2\u5386 / told \u542C\u522B\u4EBA\u8BF4 / inferred \u4F60\u63A8\u65AD\u3002\n**\u7C7B\u578B\u540D**\uFF1A\u4E00\u53E5\u81EA\u7531\u63CF\u8FF0\uFF08\u504F\u597D\u3001\u8981\u6C42\u3001\u6280\u672F\u7EA6\u675F\u3001\u4E8B\u4EF6\uFF09\u3002\u5B83\u4E0D\u662F\u5206\u7C7B\u4F53\u7CFB\uFF0C\u5199\u6700\u8D34\u5207\u7684\u8BCD\u5373\u53EF\uFF0C\u4E0D\u7528\u9020\u5206\u7C7B\u3002\n**\u7F6E\u4FE1**\uFF1A0\u20131\u3002\u62FF\u4E0D\u51C6\u5199\u4F4E\uFF0C\u4E0D\u8981\u7A7A\u7740\u2014\u2014\u7A7A\u7740\u4F1A\u88AB\u5F53\u6210\"\u6CA1\u628A\u63E1\"\u3002\n\n**\u539F\u5219\u5019\u9009**\uFF1A\u8FD9\u6761\u4E8B\u5B9E\u662F\u4E0D\u662F\"\u4EE5\u540E\u8BE5\u600E\u4E48\u505A\u4E8B\u7684\u7ECF\u9A8C\"\uFF1F\u5224\u636E\u662F\u4F1A\u4E0D\u4F1A\u5F71\u54CD\u4EE5\u540E\u7684\u505A\u6CD5\u3002\n \u7B97\uFF1A\u6539\u52A8\u524D\u5148\u8BF4\u660E\u5F71\u54CD\uFF1B\u7528\u6237\u8BA8\u538C\u53CD\u590D\u4EA4\u4EE3\n \u4E0D\u7B97\uFF1A\u4ECA\u5929\u6539\u4E86 A \u6587\u4EF6\uFF1B\u5C0F\u6797\u88AB\u88C1\u5458\n \u62FF\u4E0D\u51C6\u5C31\u7701\u7565\u2014\u2014\u4E00\u6761\u4E0D\u8BE5\u5347\u683C\u7684\u7ECF\u9A8C\u6BD4\u6F0F\u6389\u66F4\u7CDF\u3002\n\n**\u7F3A\u53E3**\uFF1A\u63D0\u8FC7\u4F46\u6CA1\u6709\u7B54\u6848\u7684\u4E8B\uFF08\"\u5979\u540E\u6765\u600E\u4E48\u6837\u4E86\"\uFF09\u3002\n**\u7EBF\u7D22**\uFF1A\u8FD9\u6BB5\u5BF9\u8BDD\u5728\u63A5\u7740\u804A\u54EA\u4E2A\u8BDD\u9898\uFF0C\u89C1 <open-threads>\u3002\n</terms>";
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
export const extractionExamples = "<examples>\n<example>\n<source>我同事小林上周被裁员了,挺难受的</source>\n<output>实体: 小林 | person | 同事\n事实: 小林在 2026-10-24 那周被裁员 | 裁员 | 2026-10-24 | 上周 | told | 0.8 | 持有=world | 精度=day | 被裁员者=@小林</output>\n</example>\n<example>\n<source>这个 bug 我上周就修好了</source>\n<output>事实: 用户说他在 2026-10-24 那周修好了这个 bug | 修复 | 2026-10-24 | 上周 | experienced | 0.7 | 持有=world | 精度=day | 修复者=用户</output>\n</example>\n<example>\n<source>好的,谢谢</source>\n<output>无</output>\n</example>\n<example>\n<source>你帮我把那个表重新跑一遍</source>\n<output>无</output>\n</example>\n<example>\n<source>我讨厌同一件事反复讲,换个工具又要重新说一遍</source>\n<output>事实: 用户讨厌同一件事反复讲,换工具也不想重复交代 | 偏好 | | | experienced | 0.9 | 持有=用户 | 精度=unknown | 偏好主体=用户</output>\n</example>\n</examples>";
|
|
6
6
|
export const extractionRole = "<role>\n你是记忆抽取器。你只从对话里抽取可验证的事实,不做判断、不给建议、不写总结。\n你的输出由一个确定性的程序消费,它不会读你的解释;格式错一个字段,这条记忆就丢了。\n</role>";
|
|
7
7
|
export const extractionRules = "<rules>\n术语的含义见 <terms>;这里只说操作和取舍。\n- 合并:与 <known-facts> 语义相同 → noop;互相矛盾 → supersede 并给 target;是补充 → update。\n 相关但独立 → add。不要用 <known-facts> 里没有的新事实去 supersede。\n- 要取代旧事实时,在事实行尾部写「操作=取代 <id>」(id 只能来自 <known-facts>)。\n 不写就是新增。代码会先确认那个 id 真实存在;编造的 id 会退化成新增并记原因。\n- 槽位只放参与者,不放话题:宁可少写一个槽位,也不要把一句话塞进槽位。\n- 不确定就省略字段,绝不编造数字、日期、关系。\n- 不要重复用户刚落的话(回声不算新事实)。\n- 来源按「谁说的」定(<source> 里标了双方):用户那一侧写 told 或 experienced;\n **我那一侧写 inferred**。我的承诺/决定写 类型名=承诺/决定;\n 我的完成声明(\"我改好了\"\"已经修了\")如果没有工具或测试结果作为依据,**不要写**。\n 自述(\"我是小灵,一个持续的伙伴\")永远不写。\n- 线索:候选见 <open-threads>,每条带它最近几轮的原文。**默认继续**:这一轮在聊候选里的某一条,\n 就写「继续 <id>」。只有出现明确转折(\"另外 / 换个话题 / 说回 / 不聊这个了\"),\n 或这一轮与所有候选的主题、实体都不相干时,才写「线索: 新 | 一句话主题」。\n 拿不准就继续最接近的那一条——线索碎掉会让后面的归纳无法进行,接错可以事后拆开。\n 继续时,如果这一轮已经不是在聊候选原来的主题(话题在同一线索内往前走了),\n 补一句「| 主题=现在聊的是什么」——线索的主题是给后续读的人看的,不更新就会一直停在第一轮的说法。\n 没有任何候选时这一行只可能是\"新\"。\n</rules>\n\n<when-to-write>\n只在这四种情况下写事实:\n1. 某个人或某件事的状态、经历(“小林换了工作”“那个项目黄了”)\n2. 一个决定,以及做这个决定的理由\n3. 用户表达的偏好、原则或习惯(“我讨厌反复讲同一件事”)\n4. 我给出的承诺(“我明天把表补上”)\n\n**不要写**(这是最常见的噪声):\n- 用户对我的要求、提问、催促、同意、否定——这些是对话动作,对话原文已经保存,再写一份只会变成噪声\n- 我问用户的澄清问题\n- 寒暄、我的自我介绍、我对能力的说明\n- 用户正在问我的事,不是“关于用户的事实”\n</when-to-write>\n\n<gaps>\n只有两种情况建缺口:\n1. 用户说自己不知道、没消息、想知道某件事(“她后来怎么样了”“不知道还在不在”)\n2. 我说过要做、但状态仍未完成的事\n\n用户只是在问我一个问题时**不要**建缺口——那是他在提问,不是他缺信息。\n</gaps>";
|
|
8
|
-
export const extractionSchema = "<output-format>\n不要输出 JSON。按下面的表格一行一条填写,字段之间用 | 分隔,没有内容的字段留空。\n可以只写其中几行;什么都记不住就只写:无\n\n实体: 名字 | 类型 | 关系\n事实: 陈述 | 类型名 | 日期(YYYY-MM-DD) | 时间原文 | 来源 | 置信(0-1) | 持有=world或人名 | 精度=day/range/relative/unknown | 原则候选=是 | 槽位 | 操作=取代 <id>\n缺口: 主题 | 张力(0-1) | 相关实体\n线索: 继续 <id> 或 线索: 新 | 一句话主题\n 话题在同一线索内变了,就写:线索: 继续 <id> | 主题=新的一句话主题\n\n每个字段的边界见 <terms>,这里只说格式:日期必须是 YYYY-MM-DD;槽位写成 名称=值,多个用 ; 隔开;\n\"
|
|
8
|
+
export const extractionSchema = "<output-format>\n不要输出 JSON。按下面的表格一行一条填写,字段之间用 | 分隔,没有内容的字段留空。\n可以只写其中几行;什么都记不住就只写:无\n\n实体: 名字 | 类型 | 关系\n事实: 陈述 | 类型名 | 日期(YYYY-MM-DD) | 时间原文 | 来源 | 置信(0-1) | 持有=world或人名 | 精度=day/range/relative/unknown | 原则候选=是 | 角色=observation/judgment/rule/profile/promise/lesson | 槽位 | 操作=取代 <id>\n缺口: 主题 | 张力(0-1) | 相关实体\n线索: 继续 <id> 或 线索: 新 | 一句话主题\n 话题在同一线索内变了,就写:线索: 继续 <id> | 主题=新的一句话主题\n\n每个字段的边界见 <terms>,这里只说格式:日期必须是 YYYY-MM-DD;槽位写成 名称=值,多个用 ; 隔开;\n\"持有/精度/原则候选/角色\"是键值对,写在事实行的尾部;线索行二选一(继续时可附 主题=)。\n角色是这个事实以后**被谁用**:观察用 observation,判断/决定用 judgment,规则用 rule,\n画像用 profile,我答应的事用 promise,教训用 lesson。拿不准就把角色留空——空表示观察。\n示例:\n实体: 小林 | person | 同事\n事实: 小林在 2026-10-24 那周被裁员 | 裁员 | 2026-10-24 | 上周 | told | 0.8 | 持有=world | 精度=day | 被裁员者=@小林\n事实: 用户要求改动前先说明为什么、怎么做、有什么影响 | 要求 | | | experienced | 0.9 | 持有=用户 | 精度=unknown | 原则候选=是 | 角色=rule | 对象=@用户\n缺口: 小林后来有着落吗 | 0.6 | 小林\n线索: 继续 th_ab12\n</output-format>";
|
|
9
9
|
export const extractionTerms = "<terms>\n每个术语的含义和边界。写之前先判断它属于哪个术语——放错位置比漏写更糟。\n\n**实体**:一个具体的人、物、地点或平台。它是\"谁/什么\",不是\"做了什么\"。\n 算:小林、用户、TypeScript、抖音、PowerShell、config 文件夹\n 不算:不删除原始对话、从根上解决、必要、噪声记忆、情绪注入、项目进展\n 边界:能用\"这说的是谁\"问出来的才算。一句话、一个观点、一个做法都不是实体。\n 判据:超过 12 个字,或含\"的/和/与\",或能加上\"这件事\"的,基本都不是实体。\n 同一个人或物只出现一次;type 只能是 person / thing / place / self。\n\n**事实**:一句自包含的陈述,回答\"发生了什么\"或\"某人是什么样\"。\n 算:小林在 2026-10-24 那周被裁员;用户要求改动前先说明影响\n 不算:用户的提问、催促、同意、否定;寒暄;我对自己能力的说明\n 边界:把代词换成名字后仍然成立,且换个人复述不需要加问号。15–80 字,保留数字和专有名词。\n\n**持有**:这条事实在谁的视角里成立。只有两个合法值。\n world:世界上发生的事,第三个人也能核实(换工作、吃饭、下雨、约定暗号)\n 人名:某人的看法、偏好、要求、担心(\"用户认为…\"),只有那个人能确认\n 边界:能写成新闻报道的 → world;只能写成\"X 认为\"的 → 人名。人物层不写日期。\n\n**槽位**:这条事实的参与者与参数,写成 名称=值,多个用 ; 隔开。\n 只放\"谁、对谁、在哪里、多少\"这类结构位置。\n 算:被裁员者=@小林;人数=300;地点=杭州\n 不算:事项=不删除原始对话;做法=从根上解决;理由=必要\n 边界:值要以实体身份参与时,写成 **@名字**;不写 @ 的一律当文字。\n @名字 必须在本轮实体行声明(或已在 <known-entities> 里),否则仍按文字保存、不建实体。\n\n**日期与精度**:先按 <observation-date> 把\"昨天/上周/上个月\"换算成 YYYY-MM-DD 填入日期。\n 精度四值:day 确切日期 / range 只知道一周一月 / relative 只有相对说法(上次、最近、初次见面)/ unknown 不知道。\n 换算不出来就留空日期,把原话写进\"时间原文\"。**没有时间就写 unknown,绝不编日期。**\n\n**来源**:experienced 你亲历 / told 听别人说 / inferred 你推断。\n**类型名**:一句自由描述(偏好、要求、技术约束、事件)。它不是分类体系,写最贴切的词即可,不用造分类。\n**置信**:0–1。拿不准写低,不要空着——空着会被当成\"没把握\"。\n\n**原则候选**:这条事实是不是\"以后该怎么做事的经验\"?判据是会不会影响以后的做法。\n 算:改动前先说明影响;用户讨厌反复交代\n 不算:今天改了 A 文件;小林被裁员\n 拿不准就省略——一条不该升格的经验比漏掉更糟。\n\n**缺口**:提过但没有答案的事(\"她后来怎么样了\")。\n**线索**:这段对话在接着聊哪个话题,见 <open-threads>。\n</terms>";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DatabaseSync } from 'node:sqlite';
|
|
2
2
|
import type { SoulStore } from './store.ts';
|
|
3
|
-
export declare const SCHEMA_VERSION =
|
|
3
|
+
export declare const SCHEMA_VERSION = 11;
|
|
4
4
|
export declare const AFFECT_RETENTION_MS: number;
|
|
5
5
|
export declare class DbNewerThanBinaryError extends Error {
|
|
6
6
|
readonly code = "DB_NEWER_THAN_BINARY";
|
|
@@ -13,6 +13,7 @@ export declare class DbNewerThanBinaryError extends Error {
|
|
|
13
13
|
* old table stays as a read-only legacy copy.
|
|
14
14
|
*/
|
|
15
15
|
/** v6 → v7: turn threads (a topic's turns need not be adjacent) plus turns.thread_id. */
|
|
16
|
+
export declare function upgradeToV11(db: DatabaseSync): void;
|
|
16
17
|
export declare function upgradeToV10(db: DatabaseSync): void;
|
|
17
18
|
export declare function upgradeToV9(db: DatabaseSync): void;
|
|
18
19
|
export declare function upgradeToV8(db: DatabaseSync): void;
|
|
@@ -10,9 +10,9 @@ import { copyFileSync, existsSync, renameSync } from 'node:fs';
|
|
|
10
10
|
import { DatabaseSync } from 'node:sqlite';
|
|
11
11
|
import { DEFAULT_EMOTION } from './types.js';
|
|
12
12
|
import { FACT_STATUS, MEMORY_STATUS } from './config/status.js';
|
|
13
|
-
import {
|
|
13
|
+
import { FACT_ROLES } from './config/domains.js';
|
|
14
14
|
import { bm25, buildCorpusStats, tokenize } from './scoring.js';
|
|
15
|
-
export const SCHEMA_VERSION =
|
|
15
|
+
export const SCHEMA_VERSION = 11;
|
|
16
16
|
/** How many recent pairs are considered before scoring. Bounded so recall stays O(candidates). */
|
|
17
17
|
const RECALL_CANDIDATES = 500;
|
|
18
18
|
export const AFFECT_RETENTION_MS = 30 * 24 * 60 * 60 * 1000;
|
|
@@ -299,6 +299,21 @@ function upgradeToV3(db) {
|
|
|
299
299
|
* old table stays as a read-only legacy copy.
|
|
300
300
|
*/
|
|
301
301
|
/** v6 → v7: turn threads (a topic's turns need not be adjacent) plus turns.thread_id. */
|
|
302
|
+
export function upgradeToV11(db) {
|
|
303
|
+
// 原则候选 stored a decision as a slot; it is role+status now. The row becomes a lesson
|
|
304
|
+
// candidate — held and shown to the classifier, injected nowhere until it is placed.
|
|
305
|
+
db.exec(`UPDATE facts SET role = 'lesson', status = 'candidate'
|
|
306
|
+
WHERE id IN (SELECT fact_id FROM fact_slots WHERE slot = '原则候选' AND value_text = '是')`);
|
|
307
|
+
db.exec("DELETE FROM fact_slots WHERE slot = '原则候选'");
|
|
308
|
+
// One name for "this cites its sources": 来源 → 证据. Skip where the fact already cites
|
|
309
|
+
// the same row under the new name, then drop what is left of the old spelling.
|
|
310
|
+
db.exec(`UPDATE fact_slots SET slot = '证据'
|
|
311
|
+
WHERE slot = '来源'
|
|
312
|
+
AND NOT EXISTS (SELECT 1 FROM fact_slots x
|
|
313
|
+
WHERE x.fact_id = fact_slots.fact_id AND x.slot = '证据'
|
|
314
|
+
AND x.ref_fact_id = fact_slots.ref_fact_id)`);
|
|
315
|
+
db.exec("DELETE FROM fact_slots WHERE slot = '来源'");
|
|
316
|
+
}
|
|
302
317
|
export function upgradeToV10(db) {
|
|
303
318
|
// The four-state vocabulary (config/status.ts). Everything written under the old names
|
|
304
319
|
// becomes one of the two states the code now reads; after this the fallbacks are gone.
|
|
@@ -448,8 +463,8 @@ export function openSqlite(file) {
|
|
|
448
463
|
upgradeToV7(db);
|
|
449
464
|
if (version < 8)
|
|
450
465
|
upgradeToV8(db);
|
|
451
|
-
if (version <
|
|
452
|
-
|
|
466
|
+
if (version < 11)
|
|
467
|
+
upgradeToV11(db);
|
|
453
468
|
// v6 adds tables only; SCHEMA creates them on every open, nothing to copy.
|
|
454
469
|
setVersion(db, SCHEMA_VERSION);
|
|
455
470
|
db.exec('COMMIT');
|
|
@@ -769,10 +784,13 @@ export function openSqlite(file) {
|
|
|
769
784
|
putFact(spec) {
|
|
770
785
|
const id = spec.id ?? `fc_${Date.now().toString(36)}_${(factSeq += 1).toString(36)}`;
|
|
771
786
|
const createdAt = Date.now();
|
|
772
|
-
//
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
|
|
787
|
+
// Only the caller decides the role. The writer's neutral default is observation — it
|
|
788
|
+
// is the minimal claim — and it never guesses from `type_name`: that guess was the debt
|
|
789
|
+
// the vocabulary rebuild removed. An unknown value is a caller bug, not a guess.
|
|
790
|
+
if (spec.role !== undefined && !FACT_ROLES.includes(spec.role)) {
|
|
791
|
+
throw new Error(`invalid fact role: ${spec.role}`);
|
|
792
|
+
}
|
|
793
|
+
const role = spec.role ?? 'observation';
|
|
776
794
|
withRetry(() => {
|
|
777
795
|
const inserted = db
|
|
778
796
|
.prepare(`INSERT OR IGNORE INTO facts
|
|
@@ -1003,12 +1021,19 @@ export function openSqlite(file) {
|
|
|
1003
1021
|
const text = row ? String(row.user_text ?? '').trim() : '';
|
|
1004
1022
|
return text === '' ? null : text;
|
|
1005
1023
|
},
|
|
1006
|
-
factsByTurns(turnIds) {
|
|
1024
|
+
factsByTurns(turnIds, options = {}) {
|
|
1007
1025
|
if (turnIds.length === 0)
|
|
1008
1026
|
return [];
|
|
1009
1027
|
const placeholders = turnIds.map(() => '?').join(',');
|
|
1028
|
+
// Only live facts. A retracted statement must not become evidence for the next
|
|
1029
|
+
// consolidation; a candidate is held but unconfirmed, so it is evidence for nothing
|
|
1030
|
+
// except the promotion step that decides its role.
|
|
1031
|
+
const live = options.includeCandidates
|
|
1032
|
+
? `f.status IN ('${FACT_STATUS.active}','${FACT_STATUS.candidate}')`
|
|
1033
|
+
: `f.status = '${FACT_STATUS.active}'`;
|
|
1010
1034
|
const rows = db
|
|
1011
|
-
.prepare(`SELECT f.* FROM facts f WHERE f.source_turn_id IN (${placeholders})
|
|
1035
|
+
.prepare(`SELECT f.* FROM facts f WHERE f.source_turn_id IN (${placeholders})
|
|
1036
|
+
AND ${live} ORDER BY f.created_at ASC`)
|
|
1012
1037
|
.all(...turnIds);
|
|
1013
1038
|
const slots = slotsOfRows(db, rows.map((row) => String(row.id)));
|
|
1014
1039
|
return rows.map((row) => rowToFact(row, slots));
|
|
@@ -1039,10 +1064,15 @@ export function openSqlite(file) {
|
|
|
1039
1064
|
},
|
|
1040
1065
|
/** Retag a fact, e.g. a lesson that turned out to be a rule (ADR 0033). */
|
|
1041
1066
|
setFactTypeName(factId, typeName) {
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1067
|
+
withRetry(() => db.prepare('UPDATE facts SET type_name = ? WHERE id = ?').run(typeName, factId));
|
|
1068
|
+
},
|
|
1069
|
+
/** What the fact is for; the caller decides (config/domains.ts FACT_ROLES). */
|
|
1070
|
+
setFactRole(factId, role) {
|
|
1071
|
+
withRetry(() => db.prepare('UPDATE facts SET role = ? WHERE id = ?').run(role, factId));
|
|
1072
|
+
},
|
|
1073
|
+
/** Lifecycle state; code writes it, never the model (config/status.ts). */
|
|
1074
|
+
setFactStatus(factId, status) {
|
|
1075
|
+
withRetry(() => db.prepare('UPDATE facts SET status = ? WHERE id = ?').run(status, factId));
|
|
1046
1076
|
},
|
|
1047
1077
|
setFactConfidence(factId, confidence) {
|
|
1048
1078
|
withRetry(() => db.prepare('UPDATE facts SET confidence = ? WHERE id = ?').run(confidence, factId));
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The only port. Every added method is a storage engine's opinion leaking into
|
|
3
3
|
* the domain, so the surface stays deliberately small.
|
|
4
4
|
*/
|
|
5
|
-
import type { AffectSignal, Correction, Emotion, Entity, EntityQuery, EntitySpec, Fact, FactQuery, FactSpec, Gap, GapSpec, Identity, Memory, QuerySpec, SessionSummary, SlotValue, TurnPair, TurnPairSpec, TurnFindResult, TurnFindSpec, TurnSpec, TurnStep, TurnThread, ThreadSpec } from './types.ts';
|
|
5
|
+
import type { AffectSignal, Correction, Emotion, Entity, EntityQuery, EntitySpec, Fact, FactQuery, FactRole, FactSpec, FactStatus, Gap, GapSpec, Identity, Memory, QuerySpec, SessionSummary, SlotValue, TurnPair, TurnPairSpec, TurnFindResult, TurnFindSpec, TurnSpec, TurnStep, TurnThread, ThreadSpec } from './types.ts';
|
|
6
6
|
export interface SoulStore {
|
|
7
7
|
ensureSoul(identity: Identity): void;
|
|
8
8
|
identity(soulId: string): Identity | null;
|
|
@@ -66,6 +66,10 @@ export interface SoulStore {
|
|
|
66
66
|
value: SlotValue;
|
|
67
67
|
}[]): number;
|
|
68
68
|
setFactTypeName(factId: string, typeName: string): void;
|
|
69
|
+
/** What the fact is for (config/domains.ts FACT_ROLES); the caller decides. */
|
|
70
|
+
setFactRole(factId: string, role: FactRole): void;
|
|
71
|
+
/** Lifecycle state (config/status.ts); code writes it, never the model. */
|
|
72
|
+
setFactStatus(factId: string, status: FactStatus): void;
|
|
69
73
|
setFactConfidence(factId: string, confidence: number): void;
|
|
70
74
|
/** Count that retrieval surfaced these facts (0019: use slows decay). */
|
|
71
75
|
recordRecalls(factIds: string[], at: number): void;
|
|
@@ -84,8 +88,13 @@ export interface SoulStore {
|
|
|
84
88
|
lastClosedThread(soulId: string, userId: string): TurnThread | null;
|
|
85
89
|
/** Every turn of a thread, in order — the chapter, not one page. */
|
|
86
90
|
threadTurns(threadId: string): TurnPair[];
|
|
87
|
-
/**
|
|
88
|
-
|
|
91
|
+
/**
|
|
92
|
+
* Facts whose source turn belongs to the given turns. Live facts only; candidates are
|
|
93
|
+
* included when asked, because promotion needs to see lessons not yet classified.
|
|
94
|
+
*/
|
|
95
|
+
factsByTurns(turnIds: string[], options?: {
|
|
96
|
+
includeCandidates?: boolean;
|
|
97
|
+
}): Fact[];
|
|
89
98
|
/** One turn by id, for expanding a fact back into its conversation. */
|
|
90
99
|
turn(id: string): TurnPair | null;
|
|
91
100
|
/** Retire a fact: the new one replaces it, the old row stays for the record. */
|
|
@@ -218,6 +218,8 @@ export type DraftSlotValue = {
|
|
|
218
218
|
};
|
|
219
219
|
export interface FactDraft {
|
|
220
220
|
statement: string;
|
|
221
|
+
/** Structured role, chosen by the model (config/domains.ts FACT_ROLES). */
|
|
222
|
+
role?: FactRole;
|
|
221
223
|
/** world or a person's name — where the fact holds (ADR 0032). */
|
|
222
224
|
holder?: string;
|
|
223
225
|
typeName?: string;
|