@indexnetwork/protocol 11.0.1-rc.455.1 → 11.0.2-rc.456.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -51,6 +51,7 @@ pin a supported release, use `latest`.
|
|
|
51
51
|
enrichment are unchanged.
|
|
52
52
|
|
|
53
53
|
### Added
|
|
54
|
+
- Add a live answer-first signal-intake eval with unrelated, relevant, and no-bridge profile cases plus provider-free corpus, runner, and scorer checks.
|
|
54
55
|
- Add the protocol-only Guided Atlas, deterministic architecture inventory
|
|
55
56
|
generator, and source-evidenced Configuration Lab. The atlas explains
|
|
56
57
|
normative concepts, the current `packages/protocol` reference implementation,
|
|
@@ -69,6 +70,7 @@ pin a supported release, use `latest`.
|
|
|
69
70
|
`SynthesisResult` types. Minor bump: additive surface only.
|
|
70
71
|
|
|
71
72
|
### Changed
|
|
73
|
+
- Make fast signal-intake follow-ups answer-first with a two-stage model boundary: an answer-only core call chooses the missing axis and supplies two or three concrete domain options, then an isolated bridge call may append at most one premise-derived profile option so an existing profile theme cannot dominate or reorder a newly stated intent.
|
|
72
74
|
- Remove unsupported deprecated source/deep forwarding shims after migrating repository consumers to canonical modules; stable package-root exports are unchanged.
|
|
73
75
|
- Add a fail-closed isolated provider-free test gate (10.1.1). Tooling-only
|
|
74
76
|
safety foundation; no runtime or public API behavior changes.
|
|
@@ -56,9 +56,10 @@ export interface SynthesisResult {
|
|
|
56
56
|
}
|
|
57
57
|
declare const followUpPlanSchema: z.ZodObject<{
|
|
58
58
|
questions: z.ZodArray<z.ZodObject<{
|
|
59
|
+
missingAxis: z.ZodEnum<["purpose", "desired_attributes", "exchange", "constraint"]>;
|
|
59
60
|
title: z.ZodString;
|
|
60
61
|
prompt: z.ZodString;
|
|
61
|
-
|
|
62
|
+
answerGroundedOptions: z.ZodArray<z.ZodObject<{
|
|
62
63
|
label: z.ZodString;
|
|
63
64
|
description: z.ZodString;
|
|
64
65
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -71,45 +72,92 @@ declare const followUpPlanSchema: z.ZodObject<{
|
|
|
71
72
|
multiSelect: z.ZodBoolean;
|
|
72
73
|
}, "strip", z.ZodTypeAny, {
|
|
73
74
|
prompt: string;
|
|
74
|
-
|
|
75
|
+
title: string;
|
|
76
|
+
multiSelect: boolean;
|
|
77
|
+
missingAxis: "purpose" | "desired_attributes" | "exchange" | "constraint";
|
|
78
|
+
answerGroundedOptions: {
|
|
75
79
|
label: string;
|
|
76
80
|
description: string;
|
|
77
81
|
}[];
|
|
78
|
-
title: string;
|
|
79
|
-
multiSelect: boolean;
|
|
80
82
|
}, {
|
|
81
83
|
prompt: string;
|
|
82
|
-
|
|
84
|
+
title: string;
|
|
85
|
+
multiSelect: boolean;
|
|
86
|
+
missingAxis: "purpose" | "desired_attributes" | "exchange" | "constraint";
|
|
87
|
+
answerGroundedOptions: {
|
|
83
88
|
label: string;
|
|
84
89
|
description: string;
|
|
85
90
|
}[];
|
|
86
|
-
title: string;
|
|
87
|
-
multiSelect: boolean;
|
|
88
91
|
}>, "many">;
|
|
89
92
|
plannedFollowUpCount: z.ZodNumber;
|
|
90
93
|
}, "strip", z.ZodTypeAny, {
|
|
91
94
|
questions: {
|
|
92
95
|
prompt: string;
|
|
93
|
-
|
|
96
|
+
title: string;
|
|
97
|
+
multiSelect: boolean;
|
|
98
|
+
missingAxis: "purpose" | "desired_attributes" | "exchange" | "constraint";
|
|
99
|
+
answerGroundedOptions: {
|
|
94
100
|
label: string;
|
|
95
101
|
description: string;
|
|
96
102
|
}[];
|
|
97
|
-
title: string;
|
|
98
|
-
multiSelect: boolean;
|
|
99
103
|
}[];
|
|
100
104
|
plannedFollowUpCount: number;
|
|
101
105
|
}, {
|
|
102
106
|
questions: {
|
|
103
107
|
prompt: string;
|
|
104
|
-
|
|
108
|
+
title: string;
|
|
109
|
+
multiSelect: boolean;
|
|
110
|
+
missingAxis: "purpose" | "desired_attributes" | "exchange" | "constraint";
|
|
111
|
+
answerGroundedOptions: {
|
|
105
112
|
label: string;
|
|
106
113
|
description: string;
|
|
107
114
|
}[];
|
|
108
|
-
title: string;
|
|
109
|
-
multiSelect: boolean;
|
|
110
115
|
}[];
|
|
111
116
|
plannedFollowUpCount: number;
|
|
112
117
|
}>;
|
|
118
|
+
declare const profileBridgePlanSchema: z.ZodObject<{
|
|
119
|
+
bridges: z.ZodArray<z.ZodObject<{
|
|
120
|
+
questionIndex: z.ZodNumber;
|
|
121
|
+
profileBridgeOption: z.ZodNullable<z.ZodObject<{
|
|
122
|
+
label: z.ZodString;
|
|
123
|
+
description: z.ZodString;
|
|
124
|
+
}, "strip", z.ZodTypeAny, {
|
|
125
|
+
label: string;
|
|
126
|
+
description: string;
|
|
127
|
+
}, {
|
|
128
|
+
label: string;
|
|
129
|
+
description: string;
|
|
130
|
+
}>>;
|
|
131
|
+
}, "strip", z.ZodTypeAny, {
|
|
132
|
+
questionIndex: number;
|
|
133
|
+
profileBridgeOption: {
|
|
134
|
+
label: string;
|
|
135
|
+
description: string;
|
|
136
|
+
} | null;
|
|
137
|
+
}, {
|
|
138
|
+
questionIndex: number;
|
|
139
|
+
profileBridgeOption: {
|
|
140
|
+
label: string;
|
|
141
|
+
description: string;
|
|
142
|
+
} | null;
|
|
143
|
+
}>, "many">;
|
|
144
|
+
}, "strip", z.ZodTypeAny, {
|
|
145
|
+
bridges: {
|
|
146
|
+
questionIndex: number;
|
|
147
|
+
profileBridgeOption: {
|
|
148
|
+
label: string;
|
|
149
|
+
description: string;
|
|
150
|
+
} | null;
|
|
151
|
+
}[];
|
|
152
|
+
}, {
|
|
153
|
+
bridges: {
|
|
154
|
+
questionIndex: number;
|
|
155
|
+
profileBridgeOption: {
|
|
156
|
+
label: string;
|
|
157
|
+
description: string;
|
|
158
|
+
} | null;
|
|
159
|
+
}[];
|
|
160
|
+
}>;
|
|
113
161
|
/** Static round-1 question used only when pack generation fails outright. */
|
|
114
162
|
export declare const FALLBACK_WHO_QUESTION: IntakePackQuestion;
|
|
115
163
|
/** Static round-2 question used when live generation fails. */
|
|
@@ -124,12 +172,14 @@ export declare function answerLabel(answer: IntakeAnswer): string;
|
|
|
124
172
|
/** Runs the two live stages of the fast intake funnel: follow-up planning and synthesis. */
|
|
125
173
|
export declare class SignalIntakeOrchestrator {
|
|
126
174
|
private readonly plannerModel;
|
|
175
|
+
private readonly profileBridgeModel;
|
|
127
176
|
private readonly synthesisModel;
|
|
128
177
|
/**
|
|
129
178
|
* @param models - Optional injected structured models. Tests pass stubs.
|
|
130
179
|
*/
|
|
131
180
|
constructor(models?: {
|
|
132
181
|
planner?: Runnable<BaseLanguageModelInput, z.infer<typeof followUpPlanSchema>>;
|
|
182
|
+
profileBridge?: Runnable<BaseLanguageModelInput, z.infer<typeof profileBridgePlanSchema>>;
|
|
133
183
|
synthesis?: Runnable<BaseLanguageModelInput, SynthesisResult>;
|
|
134
184
|
});
|
|
135
185
|
/**
|
|
@@ -10,16 +10,35 @@ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
|
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import { createStructuredModel } from "../../shared/agent/model.config.js";
|
|
12
12
|
import { normalizeIntakePack } from "./intake.pack.generator.js";
|
|
13
|
-
const
|
|
13
|
+
const optionSchema = z.object({
|
|
14
|
+
label: z.string().min(1),
|
|
15
|
+
description: z.string(),
|
|
16
|
+
});
|
|
17
|
+
const answerFirstQuestionSchema = z.object({
|
|
18
|
+
missingAxis: z
|
|
19
|
+
.enum(["purpose", "desired_attributes", "exchange", "constraint"])
|
|
20
|
+
.describe("The unanswered decision axis selected only from the answered intake rounds."),
|
|
14
21
|
title: z.string(),
|
|
15
22
|
prompt: z.string().min(1),
|
|
16
|
-
|
|
23
|
+
answerGroundedOptions: z
|
|
24
|
+
.array(optionSchema)
|
|
25
|
+
.min(2)
|
|
26
|
+
.max(3)
|
|
27
|
+
.describe("Two or three distinct choices derived only from the answered intake rounds."),
|
|
17
28
|
multiSelect: z.boolean(),
|
|
18
29
|
});
|
|
19
30
|
const followUpPlanSchema = z.object({
|
|
20
|
-
questions: z.array(
|
|
31
|
+
questions: z.array(answerFirstQuestionSchema),
|
|
21
32
|
plannedFollowUpCount: z.number().int().min(0),
|
|
22
33
|
});
|
|
34
|
+
const profileBridgePlanSchema = z.object({
|
|
35
|
+
bridges: z.array(z.object({
|
|
36
|
+
questionIndex: z.number().int().min(0),
|
|
37
|
+
profileBridgeOption: optionSchema
|
|
38
|
+
.nullable()
|
|
39
|
+
.describe("One natural profile intersection, or null when the bridge would be forced."),
|
|
40
|
+
})),
|
|
41
|
+
});
|
|
23
42
|
const synthesisSchema = z.object({
|
|
24
43
|
description: z.string().min(1),
|
|
25
44
|
lookingFor: z.string().min(1),
|
|
@@ -49,18 +68,37 @@ export const FALLBACK_BRING_QUESTION = {
|
|
|
49
68
|
],
|
|
50
69
|
multiSelect: false,
|
|
51
70
|
};
|
|
52
|
-
const PLAN_SYSTEM_PROMPT = `You plan and write follow-up intake questions for a networking product.
|
|
71
|
+
const PLAN_SYSTEM_PROMPT = `You plan and write answer-first follow-up intake questions for a networking product.
|
|
72
|
+
|
|
73
|
+
You receive ONLY the answered intake rounds. Use them to choose the next missing
|
|
74
|
+
axis, write a standalone prompt that names the newly stated person or domain, and
|
|
75
|
+
create 2-3 meaningfully distinct answerGroundedOptions. Choose the most useful
|
|
76
|
+
unanswered axis from: purpose, desired_attributes, exchange, or constraint. Never
|
|
77
|
+
re-ask an axis the rounds already answer. Do not infer a professional background,
|
|
78
|
+
industry, capability, or commercial goal that the rounds do not state.
|
|
79
|
+
|
|
80
|
+
Every answerGroundedOption must be visibly anchored to the stated person or domain
|
|
81
|
+
in its label or description and represent a concrete, distinct path. Generic labels
|
|
82
|
+
such as "Learn", "Share", "Collaborate", or "Networking" are invalid when they do
|
|
83
|
+
not say what the user would learn, share, collaborate on, or network about.
|
|
84
|
+
- Scuba divers: "Learn diving techniques", "Find dive buddies", "Share dive stories".
|
|
85
|
+
- Climate founders: "Compare climate sectors", "Find climate peers", "Discuss adaptation".
|
|
53
86
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
Each option needs a short label and a one-line description. Set multiSelect true
|
|
88
|
+
only when several options can genuinely apply together. Never expose raw JSON,
|
|
89
|
+
IDs, or internal vocabulary. plannedFollowUpCount is the TOTAL number of follow-up
|
|
90
|
+
questions the interview should contain, including any returned now; when the input
|
|
91
|
+
already fixes it, echo that value unchanged.`;
|
|
92
|
+
const PROFILE_BRIDGE_SYSTEM_PROMPT = `You may append one optional profile bridge to each already-written signal-intake question.
|
|
93
|
+
|
|
94
|
+
The question, missing axis, and answer-grounded options are immutable. For each
|
|
95
|
+
questionIndex, compare them with the profile brief and return either one genuinely
|
|
96
|
+
useful profileBridgeOption or null. A bridge is useful only when it creates a
|
|
97
|
+
natural additional path that is not already represented. Never rewrite, replace,
|
|
98
|
+
remove, or reclassify a core option. Never return more than one bridge per question.
|
|
99
|
+
When the profile theme is unrelated or the intersection would feel forced, return
|
|
100
|
+
null. Running club + investor may support one sponsorship bridge; scuba divers +
|
|
101
|
+
pianist should return null.`;
|
|
64
102
|
const SYNTHESIS_SYSTEM_PROMPT = `You write one clear signal for a networking product.
|
|
65
103
|
|
|
66
104
|
Combine the brief and the person's answers into a specific description of who they
|
|
@@ -79,6 +117,37 @@ community constraint. Never invent facts beyond the brief and the answers.`;
|
|
|
79
117
|
export function answerLabel(answer) {
|
|
80
118
|
return [...answer.selectedOptions, answer.freeText?.trim() ?? ""].filter(Boolean).join(", ");
|
|
81
119
|
}
|
|
120
|
+
function normalizeFollowUpQuestion(question, profileBridge, brief) {
|
|
121
|
+
const seen = new Set();
|
|
122
|
+
const normalizeOption = (option) => {
|
|
123
|
+
const label = option.label.trim();
|
|
124
|
+
const key = label.toLocaleLowerCase();
|
|
125
|
+
if (!label || seen.has(key))
|
|
126
|
+
return null;
|
|
127
|
+
seen.add(key);
|
|
128
|
+
return { label, description: option.description.trim() };
|
|
129
|
+
};
|
|
130
|
+
const answerGroundedOptions = question.answerGroundedOptions
|
|
131
|
+
.map(normalizeOption)
|
|
132
|
+
.filter((option) => option !== null)
|
|
133
|
+
.slice(0, 3);
|
|
134
|
+
if (answerGroundedOptions.length < 2) {
|
|
135
|
+
throw new Error("Follow-up question needs at least two answer-grounded options.");
|
|
136
|
+
}
|
|
137
|
+
const profileBridgeOption = profileBridge ? normalizeOption(profileBridge) : null;
|
|
138
|
+
return normalizeIntakePack({
|
|
139
|
+
brief,
|
|
140
|
+
question: {
|
|
141
|
+
title: question.title,
|
|
142
|
+
prompt: question.prompt,
|
|
143
|
+
options: [
|
|
144
|
+
...answerGroundedOptions,
|
|
145
|
+
...(profileBridgeOption ? [profileBridgeOption] : []),
|
|
146
|
+
],
|
|
147
|
+
multiSelect: question.multiSelect,
|
|
148
|
+
},
|
|
149
|
+
}).question;
|
|
150
|
+
}
|
|
82
151
|
/** Runs the two live stages of the fast intake funnel: follow-up planning and synthesis. */
|
|
83
152
|
export class SignalIntakeOrchestrator {
|
|
84
153
|
/**
|
|
@@ -87,6 +156,8 @@ export class SignalIntakeOrchestrator {
|
|
|
87
156
|
constructor(models) {
|
|
88
157
|
this.plannerModel = models?.planner
|
|
89
158
|
?? createStructuredModel("signalIntakePack", followUpPlanSchema);
|
|
159
|
+
this.profileBridgeModel = models?.profileBridge
|
|
160
|
+
?? createStructuredModel("signalIntakePack", profileBridgePlanSchema, { name: "signal_intake_profile_bridges" });
|
|
90
161
|
this.synthesisModel = models?.synthesis
|
|
91
162
|
?? createStructuredModel("signalIntakePack", synthesisSchema);
|
|
92
163
|
}
|
|
@@ -107,11 +178,29 @@ export class SignalIntakeOrchestrator {
|
|
|
107
178
|
try {
|
|
108
179
|
const raw = await this.plannerModel.invoke([
|
|
109
180
|
new SystemMessage(PLAN_SYSTEM_PROMPT),
|
|
110
|
-
new HumanMessage(`
|
|
181
|
+
new HumanMessage(`ANSWERED ROUNDS:\n${roundsText}\n\nWrite up to ${input.maxFollowUps} follow-up question(s).${lockedLine}`),
|
|
111
182
|
]);
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
183
|
+
const coreQuestions = raw.questions.slice(0, input.maxFollowUps);
|
|
184
|
+
const bridgeByQuestion = new Map();
|
|
185
|
+
if (coreQuestions.length > 0 && input.brief.trim()) {
|
|
186
|
+
try {
|
|
187
|
+
const bridgePlan = await this.profileBridgeModel.invoke([
|
|
188
|
+
new SystemMessage(PROFILE_BRIDGE_SYSTEM_PROMPT),
|
|
189
|
+
new HumanMessage(`ANSWERED ROUNDS:\n${roundsText}\n\nPROFILE BRIEF:\n${input.brief}\n\nIMMUTABLE CORE QUESTIONS:\n${JSON.stringify(coreQuestions, null, 2)}\n\nReturn one bridge decision for each questionIndex.`),
|
|
190
|
+
]);
|
|
191
|
+
for (const bridge of bridgePlan.bridges) {
|
|
192
|
+
if (bridge.questionIndex < coreQuestions.length
|
|
193
|
+
&& !bridgeByQuestion.has(bridge.questionIndex)) {
|
|
194
|
+
bridgeByQuestion.set(bridge.questionIndex, bridge.profileBridgeOption);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Profile personalization is optional. A bridge-model failure must not
|
|
200
|
+
// discard valid answer-grounded questions from the primary model.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const questions = coreQuestions.map((question, index) => normalizeFollowUpQuestion(question, bridgeByQuestion.get(index) ?? null, input.brief));
|
|
115
204
|
// Backstop: while budget remains, a successful empty plan must not
|
|
116
205
|
// silently shrink the interview; serve the static fallback instead.
|
|
117
206
|
if (questions.length === 0 && input.maxFollowUps > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indexnetwork/protocol",
|
|
3
|
-
"version": "11.0.
|
|
3
|
+
"version": "11.0.2-rc.456.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"eval:profile": "bun --env-file=../../.env.test ./eval/profile/profile.eval.ts",
|
|
41
41
|
"eval:opportunity": "bun --env-file=../../.env.test ./eval/opportunity/opportunity.eval.ts",
|
|
42
42
|
"eval:clarification": "bun --env-file=../../.env.test ./eval/clarification/clarification.eval.ts",
|
|
43
|
+
"eval:intake": "bun --env-file=../../.env.test ./eval/intake/intake.eval.ts",
|
|
43
44
|
"eval:discovery-retrieval": "bun --env-file=../../.env.test ./eval/discovery-retrieval/discovery-retrieval.eval.ts",
|
|
44
45
|
"eval:canary": "bun --env-file=../../.env.test ./eval/canary/canary.eval.ts",
|
|
45
46
|
"eval:stance": "bun --env-file=../../.env.test ./eval/stance/stance.eval.ts",
|