@indexnetwork/protocol 4.14.2-rc.356.1 → 4.16.0-rc.358.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/index.d.ts +13 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +6 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/opportunity/discriminator/discriminator.env.d.ts +15 -0
  7. package/dist/opportunity/discriminator/discriminator.env.d.ts.map +1 -1
  8. package/dist/opportunity/discriminator/discriminator.env.js +15 -0
  9. package/dist/opportunity/discriminator/discriminator.env.js.map +1 -1
  10. package/dist/opportunity/discriminator/discriminator.push.d.ts +16 -0
  11. package/dist/opportunity/discriminator/discriminator.push.d.ts.map +1 -0
  12. package/dist/opportunity/discriminator/discriminator.push.js +26 -0
  13. package/dist/opportunity/discriminator/discriminator.push.js.map +1 -0
  14. package/dist/opportunity/opportunity.graph.d.ts +13 -0
  15. package/dist/opportunity/opportunity.graph.d.ts.map +1 -1
  16. package/dist/opportunity/opportunity.graph.js +5 -1
  17. package/dist/opportunity/opportunity.graph.js.map +1 -1
  18. package/dist/opportunity/opportunity.state.d.ts +3 -0
  19. package/dist/opportunity/opportunity.state.d.ts.map +1 -1
  20. package/dist/opportunity/opportunity.state.js.map +1 -1
  21. package/dist/shared/agent/model.config.d.ts +5 -0
  22. package/dist/shared/agent/model.config.d.ts.map +1 -1
  23. package/dist/shared/agent/model.config.js +1 -0
  24. package/dist/shared/agent/model.config.js.map +1 -1
  25. package/dist/shared/hyde/hyde.documents.d.ts +10 -0
  26. package/dist/shared/hyde/hyde.documents.d.ts.map +1 -0
  27. package/dist/shared/hyde/hyde.documents.js +50 -0
  28. package/dist/shared/hyde/hyde.documents.js.map +1 -0
  29. package/dist/shared/hyde/hyde.env.d.ts +9 -0
  30. package/dist/shared/hyde/hyde.env.d.ts.map +1 -0
  31. package/dist/shared/hyde/hyde.env.js +10 -0
  32. package/dist/shared/hyde/hyde.env.js.map +1 -0
  33. package/dist/shared/hyde/hyde.frame.d.ts +151 -0
  34. package/dist/shared/hyde/hyde.frame.d.ts.map +1 -0
  35. package/dist/shared/hyde/hyde.frame.js +108 -0
  36. package/dist/shared/hyde/hyde.frame.js.map +1 -0
  37. package/dist/shared/hyde/hyde.generator.d.ts +23 -11
  38. package/dist/shared/hyde/hyde.generator.d.ts.map +1 -1
  39. package/dist/shared/hyde/hyde.generator.js +50 -21
  40. package/dist/shared/hyde/hyde.generator.js.map +1 -1
  41. package/dist/shared/hyde/hyde.graph.d.ts +58 -17
  42. package/dist/shared/hyde/hyde.graph.d.ts.map +1 -1
  43. package/dist/shared/hyde/hyde.graph.js +333 -89
  44. package/dist/shared/hyde/hyde.graph.js.map +1 -1
  45. package/dist/shared/hyde/hyde.state.d.ts +19 -2
  46. package/dist/shared/hyde/hyde.state.d.ts.map +1 -1
  47. package/dist/shared/hyde/hyde.state.js +23 -3
  48. package/dist/shared/hyde/hyde.state.js.map +1 -1
  49. package/dist/shared/hyde/hyde.validator.d.ts +79 -0
  50. package/dist/shared/hyde/hyde.validator.d.ts.map +1 -0
  51. package/dist/shared/hyde/hyde.validator.js +49 -0
  52. package/dist/shared/hyde/hyde.validator.js.map +1 -0
  53. package/dist/shared/hyde/lens.inferrer.d.ts +190 -12
  54. package/dist/shared/hyde/lens.inferrer.d.ts.map +1 -1
  55. package/dist/shared/hyde/lens.inferrer.js +93 -38
  56. package/dist/shared/hyde/lens.inferrer.js.map +1 -1
  57. package/dist/shared/schemas/question.schema.d.ts +283 -2
  58. package/dist/shared/schemas/question.schema.d.ts.map +1 -1
  59. package/dist/shared/schemas/question.schema.js +87 -0
  60. package/dist/shared/schemas/question.schema.js.map +1 -1
  61. package/package.json +2 -1
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Lens Inferrer Agent: analyzes source text (intent or query) with optional
3
+ * profile context and infers 1-N search lenses, each tagged with a target corpus.
4
+ * Replaces the hardcoded HydeStrategy enum and regex-based selectStrategiesFromQuery.
5
+ */
6
+ import type { BaseLanguageModelInput } from '@langchain/core/language_models/base';
7
+ import { z } from 'zod';
8
+ import { type HydeSourceFrame } from './hyde.frame.js';
1
9
  export type HydeTargetCorpus = 'profiles' | 'intents' | 'premises';
2
10
  /** A single inferred lens — a search perspective the LLM decided is relevant. */
3
11
  export interface Lens {
@@ -15,23 +23,193 @@ export interface LensInferenceInput {
15
23
  profileContext?: string;
16
24
  /** Maximum number of lenses to infer (default 3). */
17
25
  maxLenses?: number;
26
+ /** Use the source-grounded frame-v1 inference path. */
27
+ frameConstrained?: boolean;
18
28
  }
19
29
  export interface LensInferenceOutput {
20
30
  lenses: Lens[];
31
+ /** Sanitized source frame, present only for frame-constrained inference. */
32
+ sourceFrame?: HydeSourceFrame;
21
33
  }
22
- /**
23
- * Infers search lenses from source text and optional profile context.
24
- * Each lens represents a search perspective tagged with a target corpus
25
- * (profiles or intents) for downstream HyDE document generation.
26
- */
34
+ /** Source-grounded system prompt used only by frame extraction. */
35
+ export declare const FRAME_SYSTEM_PROMPT = "You extract a source-grounded frame for semantic retrieval from sourceText alone.\n\nSource-frame rules:\n- Extract evidence ONLY from sourceText.\n- Every frame element must include an evidence field copied as an exact substring of sourceText.\n- sourceRoles describe roles held or offered by the source side.\n- sourceRoles and counterpartRoles MUST use generic lower-case role labels only (for example, \"founder\", \"investor\", or \"technical advisor\"). Never put a person, organization, product, location, time, number, credential, or exclusivity detail in a role label.\n- counterpartRoles describe reciprocal or complementary target roles. A generic role may be inferred, but its evidence must be an exact sourceText span that supports the inference.\n- hardConstraints contain only explicit constraints and classify each as location, time, numeric, credential, organization, exclusivity, or other.\n- namedEntities contain only proper names explicitly present in sourceText and classify each as person, organization, product, location, event, or other.\n- domainVocabulary contains source domain terms worth preserving.";
36
+ /** Structured-output schema used only by source-frame extraction. */
37
+ export declare const FrameResponseSchema: z.ZodObject<{
38
+ sourceFrame: z.ZodObject<{
39
+ sourceRoles: z.ZodArray<z.ZodObject<{
40
+ role: z.ZodString;
41
+ evidence: z.ZodString;
42
+ }, "strip", z.ZodTypeAny, {
43
+ role: string;
44
+ evidence: string;
45
+ }, {
46
+ role: string;
47
+ evidence: string;
48
+ }>, "many">;
49
+ counterpartRoles: z.ZodArray<z.ZodObject<{
50
+ role: z.ZodString;
51
+ evidence: z.ZodString;
52
+ }, "strip", z.ZodTypeAny, {
53
+ role: string;
54
+ evidence: string;
55
+ }, {
56
+ role: string;
57
+ evidence: string;
58
+ }>, "many">;
59
+ hardConstraints: z.ZodArray<z.ZodObject<{
60
+ type: z.ZodEnum<["location", "time", "numeric", "credential", "organization", "exclusivity", "other"]>;
61
+ value: z.ZodString;
62
+ evidence: z.ZodString;
63
+ }, "strip", z.ZodTypeAny, {
64
+ value: string;
65
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
66
+ evidence: string;
67
+ }, {
68
+ value: string;
69
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
70
+ evidence: string;
71
+ }>, "many">;
72
+ namedEntities: z.ZodArray<z.ZodObject<{
73
+ type: z.ZodEnum<["person", "organization", "product", "location", "event", "other"]>;
74
+ name: z.ZodString;
75
+ evidence: z.ZodString;
76
+ }, "strip", z.ZodTypeAny, {
77
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
78
+ name: string;
79
+ evidence: string;
80
+ }, {
81
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
82
+ name: string;
83
+ evidence: string;
84
+ }>, "many">;
85
+ domainVocabulary: z.ZodArray<z.ZodObject<{
86
+ term: z.ZodString;
87
+ evidence: z.ZodString;
88
+ }, "strip", z.ZodTypeAny, {
89
+ evidence: string;
90
+ term: string;
91
+ }, {
92
+ evidence: string;
93
+ term: string;
94
+ }>, "many">;
95
+ }, "strip", z.ZodTypeAny, {
96
+ sourceRoles: {
97
+ role: string;
98
+ evidence: string;
99
+ }[];
100
+ counterpartRoles: {
101
+ role: string;
102
+ evidence: string;
103
+ }[];
104
+ hardConstraints: {
105
+ value: string;
106
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
107
+ evidence: string;
108
+ }[];
109
+ namedEntities: {
110
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
111
+ name: string;
112
+ evidence: string;
113
+ }[];
114
+ domainVocabulary: {
115
+ evidence: string;
116
+ term: string;
117
+ }[];
118
+ }, {
119
+ sourceRoles: {
120
+ role: string;
121
+ evidence: string;
122
+ }[];
123
+ counterpartRoles: {
124
+ role: string;
125
+ evidence: string;
126
+ }[];
127
+ hardConstraints: {
128
+ value: string;
129
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
130
+ evidence: string;
131
+ }[];
132
+ namedEntities: {
133
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
134
+ name: string;
135
+ evidence: string;
136
+ }[];
137
+ domainVocabulary: {
138
+ evidence: string;
139
+ term: string;
140
+ }[];
141
+ }>;
142
+ }, "strip", z.ZodTypeAny, {
143
+ sourceFrame: {
144
+ sourceRoles: {
145
+ role: string;
146
+ evidence: string;
147
+ }[];
148
+ counterpartRoles: {
149
+ role: string;
150
+ evidence: string;
151
+ }[];
152
+ hardConstraints: {
153
+ value: string;
154
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
155
+ evidence: string;
156
+ }[];
157
+ namedEntities: {
158
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
159
+ name: string;
160
+ evidence: string;
161
+ }[];
162
+ domainVocabulary: {
163
+ evidence: string;
164
+ term: string;
165
+ }[];
166
+ };
167
+ }, {
168
+ sourceFrame: {
169
+ sourceRoles: {
170
+ role: string;
171
+ evidence: string;
172
+ }[];
173
+ counterpartRoles: {
174
+ role: string;
175
+ evidence: string;
176
+ }[];
177
+ hardConstraints: {
178
+ value: string;
179
+ type: "location" | "time" | "numeric" | "credential" | "organization" | "exclusivity" | "other";
180
+ evidence: string;
181
+ }[];
182
+ namedEntities: {
183
+ type: "location" | "event" | "organization" | "other" | "person" | "product";
184
+ name: string;
185
+ evidence: string;
186
+ }[];
187
+ domainVocabulary: {
188
+ evidence: string;
189
+ term: string;
190
+ }[];
191
+ };
192
+ }>;
193
+ type ModelInput = BaseLanguageModelInput;
194
+ /** Minimal structured model contract used for deterministic injection in tests. */
195
+ export interface LensStructuredModel {
196
+ invoke(input: ModelInput, config?: {
197
+ signal?: AbortSignal;
198
+ }): Promise<unknown>;
199
+ }
200
+ export interface LensInferrerOptions {
201
+ legacyModel?: LensStructuredModel;
202
+ frameModel?: LensStructuredModel;
203
+ }
204
+ /** Infers search lenses from source text and optional profile context. */
27
205
  export declare class LensInferrer {
28
- private model;
29
- /**
30
- * Infer search lenses from source text and optional profile context.
31
- *
32
- * @param input - Source text, optional profile context, optional max lenses
33
- * @returns Array of inferred lenses with corpus tags; empty array on failure
34
- */
206
+ private legacyModel?;
207
+ private frameModel?;
208
+ constructor(options?: LensInferrerOptions);
209
+ private getLegacyModel;
210
+ private getFrameModel;
211
+ /** Infer search lenses while preserving the legacy path unless explicitly enabled. */
35
212
  infer(input: LensInferenceInput): Promise<LensInferenceOutput>;
36
213
  }
214
+ export {};
37
215
  //# sourceMappingURL=lens.inferrer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lens.inferrer.d.ts","sourceRoot":"/","sources":["shared/hyde/lens.inferrer.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEnE,iFAAiF;AACjF,MAAM,WAAW,IAAI;IACnB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,gBAAgB,CAAC;IACzB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,IAAI,EAAE,CAAC;CAChB;AA4BD;;;;GAIG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,KAAK,CAEV;IAEH;;;;;OAKG;IAEG,KAAK,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAoCrE"}
1
+ {"version":3,"file":"lens.inferrer.d.ts","sourceRoot":"/","sources":["shared/hyde/lens.inferrer.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAEnF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB,OAAO,EAAkD,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvG,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEnE,iFAAiF;AACjF,MAAM,WAAW,IAAI;IACnB,+DAA+D;IAC/D,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,MAAM,EAAE,gBAAgB,CAAC;IACzB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,IAAI,EAAE,CAAC;IACf,4EAA4E;IAC5E,WAAW,CAAC,EAAE,eAAe,CAAC;CAC/B;AAkBD,mEAAmE;AACnE,eAAO,MAAM,mBAAmB,8mCAUkC,CAAC;AAYnE,qEAAqE;AACrE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAE9B,CAAC;AAEH,KAAK,UAAU,GAAG,sBAAsB,CAAC;AAEzC,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAChF;AAED,MAAM,WAAW,mBAAmB;IAClC,WAAW,CAAC,EAAE,mBAAmB,CAAC;IAClC,UAAU,CAAC,EAAE,mBAAmB,CAAC;CAClC;AAID,0EAA0E;AAC1E,qBAAa,YAAY;IACvB,OAAO,CAAC,WAAW,CAAC,CAAsB;IAC1C,OAAO,CAAC,UAAU,CAAC,CAAsB;gBAE7B,OAAO,GAAE,mBAAwB;IAQ7C,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,aAAa;IAOrB,sFAAsF;IAEhF,KAAK,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CA8ErE"}
@@ -7,17 +7,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  var __metadata = (this && this.__metadata) || function (k, v) {
8
8
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
9
  };
10
- /**
11
- * Lens Inferrer Agent: analyzes source text (intent or query) with optional
12
- * profile context and infers 1-N search lenses, each tagged with a target corpus.
13
- * Replaces the hardcoded HydeStrategy enum and regex-based selectStrategiesFromQuery.
14
- */
15
10
  import { HumanMessage, SystemMessage } from '@langchain/core/messages';
16
11
  import { z } from 'zod';
17
- import { Timed } from "../observability/performance.js";
18
- import { protocolLogger } from '../observability/protocol.logger.js';
19
12
  import { createStructuredModel } from "../agent/model.config.js";
20
13
  import { invokeWithAbortSignal } from "../agent/model-signal.js";
14
+ import { protocolLogger } from '../observability/protocol.logger.js';
15
+ import { Timed } from "../observability/performance.js";
16
+ import { HydeSourceFrameSchema, sanitizeHydeSourceFrame } from './hyde.frame.js';
21
17
  const SYSTEM_PROMPT = `You analyze goals and search queries to identify the most relevant perspectives for finding matching people in a professional network.
22
18
 
23
19
  For each perspective you identify, specify:
@@ -33,37 +29,60 @@ Guidelines:
33
29
  - Use "profiles" when looking for a type of person (expert, advisor, leader). Use "intents" when looking for a complementary goal or need (someone raising, someone hiring, someone seeking collaboration). Use "premises" when looking for someone whose identity, values, or worldview aligns — stable traits rather than transient goals.
34
30
  - Always include at least one "profiles" perspective when the source describes a need that a specific type of professional could fulfill. Most intents benefit from profile-based discovery.
35
31
  - LOCATION AWARENESS: When the source text or user context mentions a specific location (city, region, country), incorporate it into lens descriptions. For example, "investors in San Francisco" should produce a lens like "SF-based early-stage investor" rather than just "early-stage investor". This helps the hypothetical document generator produce location-specific search documents, improving retrieval quality.`;
32
+ /** Source-grounded system prompt used only by frame extraction. */
33
+ export const FRAME_SYSTEM_PROMPT = `You extract a source-grounded frame for semantic retrieval from sourceText alone.
34
+
35
+ Source-frame rules:
36
+ - Extract evidence ONLY from sourceText.
37
+ - Every frame element must include an evidence field copied as an exact substring of sourceText.
38
+ - sourceRoles describe roles held or offered by the source side.
39
+ - sourceRoles and counterpartRoles MUST use generic lower-case role labels only (for example, "founder", "investor", or "technical advisor"). Never put a person, organization, product, location, time, number, credential, or exclusivity detail in a role label.
40
+ - counterpartRoles describe reciprocal or complementary target roles. A generic role may be inferred, but its evidence must be an exact sourceText span that supports the inference.
41
+ - hardConstraints contain only explicit constraints and classify each as location, time, numeric, credential, organization, exclusivity, or other.
42
+ - namedEntities contain only proper names explicitly present in sourceText and classify each as person, organization, product, location, event, or other.
43
+ - domainVocabulary contains source domain terms worth preserving.`;
44
+ const lensSchema = z.object({
45
+ label: z.string().describe('Specific description of the search perspective'),
46
+ corpus: z.enum(['profiles', 'intents', 'premises']).describe('Search user profiles, user intents, or user premises (identity/values)'),
47
+ reasoning: z.string().describe('Why this perspective is relevant'),
48
+ });
36
49
  const responseFormat = z.object({
37
- lenses: z.array(z.object({
38
- label: z.string().describe('Specific description of the search perspective'),
39
- corpus: z.enum(['profiles', 'intents', 'premises']).describe('Search user profiles, user intents, or user premises (identity/values)'),
40
- reasoning: z.string().describe('Why this perspective is relevant'),
41
- })).min(1).max(5).describe('Inferred search lenses'),
50
+ lenses: z.array(lensSchema).min(1).max(5).describe('Inferred search lenses'),
51
+ });
52
+ /** Structured-output schema used only by source-frame extraction. */
53
+ export const FrameResponseSchema = z.object({
54
+ sourceFrame: HydeSourceFrameSchema,
42
55
  });
43
56
  const logger = protocolLogger("LensInferrer");
44
- /**
45
- * Infers search lenses from source text and optional profile context.
46
- * Each lens represents a search perspective tagged with a target corpus
47
- * (profiles or intents) for downstream HyDE document generation.
48
- */
57
+ /** Infers search lenses from source text and optional profile context. */
49
58
  export class LensInferrer {
50
- constructor() {
51
- this.model = createStructuredModel("lensInferrer", responseFormat, {
59
+ constructor(options = {}) {
60
+ // Preserve legacy construction behavior in production. Frame-only model
61
+ // injection deliberately avoids constructing a provider-backed legacy model.
62
+ this.legacyModel = options.legacyModel
63
+ ?? (options.frameModel ? undefined : createStructuredModel("lensInferrer", responseFormat, { name: "lens_inferrer" }));
64
+ this.frameModel = options.frameModel;
65
+ }
66
+ getLegacyModel() {
67
+ this.legacyModel ?? (this.legacyModel = createStructuredModel("lensInferrer", responseFormat, {
52
68
  name: "lens_inferrer",
53
- });
69
+ }));
70
+ return this.legacyModel;
54
71
  }
55
- /**
56
- * Infer search lenses from source text and optional profile context.
57
- *
58
- * @param input - Source text, optional profile context, optional max lenses
59
- * @returns Array of inferred lenses with corpus tags; empty array on failure
60
- */
72
+ getFrameModel() {
73
+ this.frameModel ?? (this.frameModel = createStructuredModel("lensInferrer", FrameResponseSchema, {
74
+ name: "lens_inferrer_frame_v1",
75
+ }));
76
+ return this.frameModel;
77
+ }
78
+ /** Infer search lenses while preserving the legacy path unless explicitly enabled. */
61
79
  async infer(input) {
62
- const { sourceText, profileContext, maxLenses = 3 } = input;
80
+ const { sourceText, profileContext, maxLenses = 3, frameConstrained = false } = input;
63
81
  logger.verbose('Inferring lenses', {
64
82
  sourceTextLength: sourceText.length,
65
83
  hasProfileContext: !!profileContext,
66
84
  maxLenses,
85
+ frameConstrained,
67
86
  });
68
87
  let humanPrompt = `Identify up to ${maxLenses} search perspectives for finding relevant matches.\n\nSource: "${sourceText}"`;
69
88
  if (profileContext) {
@@ -73,20 +92,56 @@ export class LensInferrer {
73
92
  new SystemMessage(SYSTEM_PROMPT),
74
93
  new HumanMessage(humanPrompt),
75
94
  ];
76
- try {
77
- const result = await invokeWithAbortSignal(this.model, messages);
78
- const parsed = responseFormat.parse(result);
79
- const lenses = parsed.lenses.slice(0, maxLenses);
80
- logger.verbose('Lenses inferred', {
81
- count: lenses.length,
82
- lenses: lenses.map(l => ({ label: l.label, corpus: l.corpus })),
83
- });
84
- return { lenses };
95
+ if (!frameConstrained) {
96
+ try {
97
+ const result = await invokeWithAbortSignal(this.getLegacyModel(), messages);
98
+ const parsed = responseFormat.parse(result);
99
+ const lenses = parsed.lenses.slice(0, maxLenses);
100
+ logger.verbose('Lenses inferred', {
101
+ count: lenses.length,
102
+ lenses: lenses.map(l => ({ label: l.label, corpus: l.corpus })),
103
+ });
104
+ return { lenses };
105
+ }
106
+ catch (error) {
107
+ logger.error('Lens inference failed', { error });
108
+ return { lenses: [] };
109
+ }
85
110
  }
86
- catch (error) {
87
- logger.error('Lens inference failed', { error });
111
+ const frameMessages = [
112
+ new SystemMessage(FRAME_SYSTEM_PROMPT),
113
+ new HumanMessage(`Extract the source frame.\n\nSource: "${sourceText}"`),
114
+ ];
115
+ const lensPromise = (async () => {
116
+ const result = await invokeWithAbortSignal(this.getLegacyModel(), messages);
117
+ return responseFormat.parse(result).lenses.slice(0, maxLenses);
118
+ })();
119
+ const framePromise = (async () => {
120
+ const result = await invokeWithAbortSignal(this.getFrameModel(), frameMessages);
121
+ const parsed = FrameResponseSchema.parse(result);
122
+ return sanitizeHydeSourceFrame(sourceText, parsed.sourceFrame);
123
+ })();
124
+ const [lensResult, frameResult] = await Promise.allSettled([lensPromise, framePromise]);
125
+ if (lensResult.status === 'rejected') {
126
+ logger.error('Lens inference failed', { error: lensResult.reason });
88
127
  return { lenses: [] };
89
128
  }
129
+ const lenses = lensResult.value;
130
+ logger.verbose('Frame-constrained lenses inferred', { count: lenses.length });
131
+ if (frameResult.status === 'rejected') {
132
+ logger.error('Source frame extraction failed', { error: frameResult.reason });
133
+ return {
134
+ lenses,
135
+ sourceFrame: {
136
+ sourceRoles: [],
137
+ counterpartRoles: [],
138
+ hardConstraints: [],
139
+ namedEntities: [],
140
+ domainVocabulary: [],
141
+ },
142
+ };
143
+ }
144
+ return { lenses, sourceFrame: frameResult.value };
90
145
  }
91
146
  }
92
147
  __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"lens.inferrer.js","sourceRoot":"/","sources":["shared/hyde/lens.inferrer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lens Inferrer Agent: analyzes source text (intent or query) with optional\n * profile context and infers 1-N search lenses, each tagged with a target corpus.\n * Replaces the hardcoded HydeStrategy enum and regex-based selectStrategiesFromQuery.\n */\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport { z } from 'zod';\nimport { Timed } from \"../observability/performance.js\";\nimport { protocolLogger } from '../observability/protocol.logger.js';\nimport { createStructuredModel } from \"../agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../agent/model-signal.js\";\n\nexport type HydeTargetCorpus = 'profiles' | 'intents' | 'premises';\n\n/** A single inferred lens — a search perspective the LLM decided is relevant. */\nexport interface Lens {\n /** Free-text description (e.g. \"crypto infrastructure VC\"). */\n label: string;\n /** Which vector index to search: user profiles or user intents. */\n corpus: HydeTargetCorpus;\n /** Why this perspective is relevant (for logging/trace). */\n reasoning: string;\n}\n\nexport interface LensInferenceInput {\n /** Intent payload or search query. */\n sourceText: string;\n /** User's profile summary for domain context (optional). */\n profileContext?: string;\n /** Maximum number of lenses to infer (default 3). */\n maxLenses?: number;\n}\n\nexport interface LensInferenceOutput {\n lenses: Lens[];\n}\n\nconst SYSTEM_PROMPT = `You analyze goals and search queries to identify the most relevant perspectives for finding matching people in a professional network.\n\nFor each perspective you identify, specify:\n1. A clear, specific description of who or what to search for\n2. Whether to search \"profiles\" (user bios, expertise, backgrounds), \"intents\" (stated goals, needs, aspirations), or \"premises\" (identity assertions, values, worldview)\n3. A brief reason why this perspective is relevant\n\nGuidelines:\n- Be specific and domain-aware. \"early-stage crypto infrastructure investor\" is better than \"investor\".\n- Consider both sides: who can help the person AND whose goals complement theirs.\n- When user context is provided, tailor perspectives to their domain (e.g. a DePIN founder searching for \"investors\" needs crypto-native infra investors specifically).\n- Generate only perspectives that add distinct search value — don't repeat similar angles.\n- Use \"profiles\" when looking for a type of person (expert, advisor, leader). Use \"intents\" when looking for a complementary goal or need (someone raising, someone hiring, someone seeking collaboration). Use \"premises\" when looking for someone whose identity, values, or worldview aligns — stable traits rather than transient goals.\n- Always include at least one \"profiles\" perspective when the source describes a need that a specific type of professional could fulfill. Most intents benefit from profile-based discovery.\n- LOCATION AWARENESS: When the source text or user context mentions a specific location (city, region, country), incorporate it into lens descriptions. For example, \"investors in San Francisco\" should produce a lens like \"SF-based early-stage investor\" rather than just \"early-stage investor\". This helps the hypothetical document generator produce location-specific search documents, improving retrieval quality.`;\n\nconst responseFormat = z.object({\n lenses: z.array(z.object({\n label: z.string().describe('Specific description of the search perspective'),\n corpus: z.enum(['profiles', 'intents', 'premises']).describe('Search user profiles, user intents, or user premises (identity/values)'),\n reasoning: z.string().describe('Why this perspective is relevant'),\n })).min(1).max(5).describe('Inferred search lenses'),\n});\n\nconst logger = protocolLogger(\"LensInferrer\");\n\n/**\n * Infers search lenses from source text and optional profile context.\n * Each lens represents a search perspective tagged with a target corpus\n * (profiles or intents) for downstream HyDE document generation.\n */\nexport class LensInferrer {\n private model = createStructuredModel(\"lensInferrer\", responseFormat, {\n name: \"lens_inferrer\",\n });\n\n /**\n * Infer search lenses from source text and optional profile context.\n *\n * @param input - Source text, optional profile context, optional max lenses\n * @returns Array of inferred lenses with corpus tags; empty array on failure\n */\n @Timed()\n async infer(input: LensInferenceInput): Promise<LensInferenceOutput> {\n const { sourceText, profileContext, maxLenses = 3 } = input;\n\n logger.verbose('Inferring lenses', {\n sourceTextLength: sourceText.length,\n hasProfileContext: !!profileContext,\n maxLenses,\n });\n\n let humanPrompt = `Identify up to ${maxLenses} search perspectives for finding relevant matches.\\n\\nSource: \"${sourceText}\"`;\n\n if (profileContext) {\n humanPrompt += `\\n\\nUser context: ${profileContext}`;\n }\n\n const messages = [\n new SystemMessage(SYSTEM_PROMPT),\n new HumanMessage(humanPrompt),\n ];\n\n try {\n const result = await invokeWithAbortSignal(this.model, messages);\n const parsed = responseFormat.parse(result);\n const lenses = parsed.lenses.slice(0, maxLenses);\n\n logger.verbose('Lenses inferred', {\n count: lenses.length,\n lenses: lenses.map(l => ({ label: l.label, corpus: l.corpus })),\n });\n\n return { lenses };\n } catch (error: unknown) {\n logger.error('Lens inference failed', { error });\n return { lenses: [] };\n }\n }\n}\n"]}
1
+ {"version":3,"file":"lens.inferrer.js","sourceRoot":"/","sources":["shared/hyde/lens.inferrer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Lens Inferrer Agent: analyzes source text (intent or query) with optional\n * profile context and infers 1-N search lenses, each tagged with a target corpus.\n * Replaces the hardcoded HydeStrategy enum and regex-based selectStrategiesFromQuery.\n */\nimport type { BaseLanguageModelInput } from '@langchain/core/language_models/base';\nimport { HumanMessage, SystemMessage } from '@langchain/core/messages';\nimport { z } from 'zod';\n\nimport { createStructuredModel } from \"../agent/model.config.js\";\nimport { invokeWithAbortSignal } from \"../agent/model-signal.js\";\nimport { protocolLogger } from '../observability/protocol.logger.js';\nimport { Timed } from \"../observability/performance.js\";\nimport { HydeSourceFrameSchema, sanitizeHydeSourceFrame, type HydeSourceFrame } from './hyde.frame.js';\n\nexport type HydeTargetCorpus = 'profiles' | 'intents' | 'premises';\n\n/** A single inferred lens — a search perspective the LLM decided is relevant. */\nexport interface Lens {\n /** Free-text description (e.g. \"crypto infrastructure VC\"). */\n label: string;\n /** Which vector index to search: user profiles or user intents. */\n corpus: HydeTargetCorpus;\n /** Why this perspective is relevant (for logging/trace). */\n reasoning: string;\n}\n\nexport interface LensInferenceInput {\n /** Intent payload or search query. */\n sourceText: string;\n /** User's profile summary for domain context (optional). */\n profileContext?: string;\n /** Maximum number of lenses to infer (default 3). */\n maxLenses?: number;\n /** Use the source-grounded frame-v1 inference path. */\n frameConstrained?: boolean;\n}\n\nexport interface LensInferenceOutput {\n lenses: Lens[];\n /** Sanitized source frame, present only for frame-constrained inference. */\n sourceFrame?: HydeSourceFrame;\n}\n\nconst SYSTEM_PROMPT = `You analyze goals and search queries to identify the most relevant perspectives for finding matching people in a professional network.\n\nFor each perspective you identify, specify:\n1. A clear, specific description of who or what to search for\n2. Whether to search \"profiles\" (user bios, expertise, backgrounds), \"intents\" (stated goals, needs, aspirations), or \"premises\" (identity assertions, values, worldview)\n3. A brief reason why this perspective is relevant\n\nGuidelines:\n- Be specific and domain-aware. \"early-stage crypto infrastructure investor\" is better than \"investor\".\n- Consider both sides: who can help the person AND whose goals complement theirs.\n- When user context is provided, tailor perspectives to their domain (e.g. a DePIN founder searching for \"investors\" needs crypto-native infra investors specifically).\n- Generate only perspectives that add distinct search value — don't repeat similar angles.\n- Use \"profiles\" when looking for a type of person (expert, advisor, leader). Use \"intents\" when looking for a complementary goal or need (someone raising, someone hiring, someone seeking collaboration). Use \"premises\" when looking for someone whose identity, values, or worldview aligns — stable traits rather than transient goals.\n- Always include at least one \"profiles\" perspective when the source describes a need that a specific type of professional could fulfill. Most intents benefit from profile-based discovery.\n- LOCATION AWARENESS: When the source text or user context mentions a specific location (city, region, country), incorporate it into lens descriptions. For example, \"investors in San Francisco\" should produce a lens like \"SF-based early-stage investor\" rather than just \"early-stage investor\". This helps the hypothetical document generator produce location-specific search documents, improving retrieval quality.`;\n\n/** Source-grounded system prompt used only by frame extraction. */\nexport const FRAME_SYSTEM_PROMPT = `You extract a source-grounded frame for semantic retrieval from sourceText alone.\n\nSource-frame rules:\n- Extract evidence ONLY from sourceText.\n- Every frame element must include an evidence field copied as an exact substring of sourceText.\n- sourceRoles describe roles held or offered by the source side.\n- sourceRoles and counterpartRoles MUST use generic lower-case role labels only (for example, \"founder\", \"investor\", or \"technical advisor\"). Never put a person, organization, product, location, time, number, credential, or exclusivity detail in a role label.\n- counterpartRoles describe reciprocal or complementary target roles. A generic role may be inferred, but its evidence must be an exact sourceText span that supports the inference.\n- hardConstraints contain only explicit constraints and classify each as location, time, numeric, credential, organization, exclusivity, or other.\n- namedEntities contain only proper names explicitly present in sourceText and classify each as person, organization, product, location, event, or other.\n- domainVocabulary contains source domain terms worth preserving.`;\n\nconst lensSchema = z.object({\n label: z.string().describe('Specific description of the search perspective'),\n corpus: z.enum(['profiles', 'intents', 'premises']).describe('Search user profiles, user intents, or user premises (identity/values)'),\n reasoning: z.string().describe('Why this perspective is relevant'),\n});\n\nconst responseFormat = z.object({\n lenses: z.array(lensSchema).min(1).max(5).describe('Inferred search lenses'),\n});\n\n/** Structured-output schema used only by source-frame extraction. */\nexport const FrameResponseSchema = z.object({\n sourceFrame: HydeSourceFrameSchema,\n});\n\ntype ModelInput = BaseLanguageModelInput;\n\n/** Minimal structured model contract used for deterministic injection in tests. */\nexport interface LensStructuredModel {\n invoke(input: ModelInput, config?: { signal?: AbortSignal }): Promise<unknown>;\n}\n\nexport interface LensInferrerOptions {\n legacyModel?: LensStructuredModel;\n frameModel?: LensStructuredModel;\n}\n\nconst logger = protocolLogger(\"LensInferrer\");\n\n/** Infers search lenses from source text and optional profile context. */\nexport class LensInferrer {\n private legacyModel?: LensStructuredModel;\n private frameModel?: LensStructuredModel;\n\n constructor(options: LensInferrerOptions = {}) {\n // Preserve legacy construction behavior in production. Frame-only model\n // injection deliberately avoids constructing a provider-backed legacy model.\n this.legacyModel = options.legacyModel\n ?? (options.frameModel ? undefined : createStructuredModel(\"lensInferrer\", responseFormat, { name: \"lens_inferrer\" }));\n this.frameModel = options.frameModel;\n }\n\n private getLegacyModel(): LensStructuredModel {\n this.legacyModel ??= createStructuredModel(\"lensInferrer\", responseFormat, {\n name: \"lens_inferrer\",\n });\n return this.legacyModel;\n }\n\n private getFrameModel(): LensStructuredModel {\n this.frameModel ??= createStructuredModel(\"lensInferrer\", FrameResponseSchema, {\n name: \"lens_inferrer_frame_v1\",\n });\n return this.frameModel;\n }\n\n /** Infer search lenses while preserving the legacy path unless explicitly enabled. */\n @Timed()\n async infer(input: LensInferenceInput): Promise<LensInferenceOutput> {\n const { sourceText, profileContext, maxLenses = 3, frameConstrained = false } = input;\n\n logger.verbose('Inferring lenses', {\n sourceTextLength: sourceText.length,\n hasProfileContext: !!profileContext,\n maxLenses,\n frameConstrained,\n });\n\n let humanPrompt = `Identify up to ${maxLenses} search perspectives for finding relevant matches.\\n\\nSource: \"${sourceText}\"`;\n\n if (profileContext) {\n humanPrompt += `\\n\\nUser context: ${profileContext}`;\n }\n\n const messages = [\n new SystemMessage(SYSTEM_PROMPT),\n new HumanMessage(humanPrompt),\n ];\n\n if (!frameConstrained) {\n try {\n const result = await invokeWithAbortSignal(this.getLegacyModel(), messages);\n const parsed = responseFormat.parse(result);\n const lenses = parsed.lenses.slice(0, maxLenses);\n\n logger.verbose('Lenses inferred', {\n count: lenses.length,\n lenses: lenses.map(l => ({ label: l.label, corpus: l.corpus })),\n });\n\n return { lenses };\n } catch (error: unknown) {\n logger.error('Lens inference failed', { error });\n return { lenses: [] };\n }\n }\n\n const frameMessages = [\n new SystemMessage(FRAME_SYSTEM_PROMPT),\n new HumanMessage(`Extract the source frame.\\n\\nSource: \"${sourceText}\"`),\n ];\n const lensPromise = (async () => {\n const result = await invokeWithAbortSignal(this.getLegacyModel(), messages);\n return responseFormat.parse(result).lenses.slice(0, maxLenses);\n })();\n const framePromise = (async () => {\n const result = await invokeWithAbortSignal(this.getFrameModel(), frameMessages);\n const parsed = FrameResponseSchema.parse(result);\n return sanitizeHydeSourceFrame(sourceText, parsed.sourceFrame);\n })();\n const [lensResult, frameResult] = await Promise.allSettled([lensPromise, framePromise]);\n\n if (lensResult.status === 'rejected') {\n logger.error('Lens inference failed', { error: lensResult.reason });\n return { lenses: [] };\n }\n\n const lenses = lensResult.value;\n logger.verbose('Frame-constrained lenses inferred', { count: lenses.length });\n\n if (frameResult.status === 'rejected') {\n logger.error('Source frame extraction failed', { error: frameResult.reason });\n return {\n lenses,\n sourceFrame: {\n sourceRoles: [],\n counterpartRoles: [],\n hardConstraints: [],\n namedEntities: [],\n domainVocabulary: [],\n },\n };\n }\n\n return { lenses, sourceFrame: frameResult.value };\n }\n}\n"]}