@indexnetwork/protocol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/chat.agent.d.ts +218 -0
- package/dist/agents/chat.agent.d.ts.map +1 -0
- package/dist/agents/chat.agent.js +884 -0
- package/dist/agents/chat.agent.js.map +1 -0
- package/dist/agents/chat.prompt.d.ts +18 -0
- package/dist/agents/chat.prompt.d.ts.map +1 -0
- package/dist/agents/chat.prompt.js +372 -0
- package/dist/agents/chat.prompt.js.map +1 -0
- package/dist/agents/chat.prompt.modules.d.ts +61 -0
- package/dist/agents/chat.prompt.modules.d.ts.map +1 -0
- package/dist/agents/chat.prompt.modules.js +366 -0
- package/dist/agents/chat.prompt.modules.js.map +1 -0
- package/dist/agents/chat.title.generator.d.ts +20 -0
- package/dist/agents/chat.title.generator.d.ts.map +1 -0
- package/dist/agents/chat.title.generator.js +66 -0
- package/dist/agents/chat.title.generator.js.map +1 -0
- package/dist/agents/home.categorizer.d.ts +28 -0
- package/dist/agents/home.categorizer.d.ts.map +1 -0
- package/dist/agents/home.categorizer.js +170 -0
- package/dist/agents/home.categorizer.js.map +1 -0
- package/dist/agents/hyde.generator.d.ts +27 -0
- package/dist/agents/hyde.generator.d.ts.map +1 -0
- package/dist/agents/hyde.generator.js +75 -0
- package/dist/agents/hyde.generator.js.map +1 -0
- package/dist/agents/hyde.strategies.d.ts +17 -0
- package/dist/agents/hyde.strategies.d.ts.map +1 -0
- package/dist/agents/hyde.strategies.js +29 -0
- package/dist/agents/hyde.strategies.js.map +1 -0
- package/dist/agents/intent.clarifier.d.ts +29 -0
- package/dist/agents/intent.clarifier.d.ts.map +1 -0
- package/dist/agents/intent.clarifier.js +186 -0
- package/dist/agents/intent.clarifier.js.map +1 -0
- package/dist/agents/intent.indexer.d.ts +77 -0
- package/dist/agents/intent.indexer.d.ts.map +1 -0
- package/dist/agents/intent.indexer.js +164 -0
- package/dist/agents/intent.indexer.js.map +1 -0
- package/dist/agents/intent.inferrer.d.ts +95 -0
- package/dist/agents/intent.inferrer.d.ts.map +1 -0
- package/dist/agents/intent.inferrer.js +238 -0
- package/dist/agents/intent.inferrer.js.map +1 -0
- package/dist/agents/intent.reconciler.d.ts +106 -0
- package/dist/agents/intent.reconciler.d.ts.map +1 -0
- package/dist/agents/intent.reconciler.js +184 -0
- package/dist/agents/intent.reconciler.js.map +1 -0
- package/dist/agents/intent.verifier.d.ts +97 -0
- package/dist/agents/intent.verifier.d.ts.map +1 -0
- package/dist/agents/intent.verifier.js +234 -0
- package/dist/agents/intent.verifier.js.map +1 -0
- package/dist/agents/invite.generator.d.ts +47 -0
- package/dist/agents/invite.generator.d.ts.map +1 -0
- package/dist/agents/invite.generator.js +56 -0
- package/dist/agents/invite.generator.js.map +1 -0
- package/dist/agents/lens.inferrer.d.ts +37 -0
- package/dist/agents/lens.inferrer.d.ts.map +1 -0
- package/dist/agents/lens.inferrer.js +98 -0
- package/dist/agents/lens.inferrer.js.map +1 -0
- package/dist/agents/model.config.d.ts +120 -0
- package/dist/agents/model.config.d.ts.map +1 -0
- package/dist/agents/model.config.js +76 -0
- package/dist/agents/model.config.js.map +1 -0
- package/dist/agents/negotiation.insights.generator.d.ts +32 -0
- package/dist/agents/negotiation.insights.generator.d.ts.map +1 -0
- package/dist/agents/negotiation.insights.generator.js +105 -0
- package/dist/agents/negotiation.insights.generator.js.map +1 -0
- package/dist/agents/negotiation.proposer.d.ts +26 -0
- package/dist/agents/negotiation.proposer.d.ts.map +1 -0
- package/dist/agents/negotiation.proposer.js +67 -0
- package/dist/agents/negotiation.proposer.js.map +1 -0
- package/dist/agents/negotiation.responder.d.ts +26 -0
- package/dist/agents/negotiation.responder.d.ts.map +1 -0
- package/dist/agents/negotiation.responder.js +71 -0
- package/dist/agents/negotiation.responder.js.map +1 -0
- package/dist/agents/opportunity.evaluator.d.ts +253 -0
- package/dist/agents/opportunity.evaluator.d.ts.map +1 -0
- package/dist/agents/opportunity.evaluator.js +413 -0
- package/dist/agents/opportunity.evaluator.js.map +1 -0
- package/dist/agents/opportunity.presenter.d.ts +115 -0
- package/dist/agents/opportunity.presenter.d.ts.map +1 -0
- package/dist/agents/opportunity.presenter.js +524 -0
- package/dist/agents/opportunity.presenter.js.map +1 -0
- package/dist/agents/profile.generator.d.ts +67 -0
- package/dist/agents/profile.generator.d.ts.map +1 -0
- package/dist/agents/profile.generator.js +97 -0
- package/dist/agents/profile.generator.js.map +1 -0
- package/dist/agents/profile.hyde.generator.d.ts +43 -0
- package/dist/agents/profile.hyde.generator.d.ts.map +1 -0
- package/dist/agents/profile.hyde.generator.js +113 -0
- package/dist/agents/profile.hyde.generator.js.map +1 -0
- package/dist/agents/suggestion.generator.d.ts +24 -0
- package/dist/agents/suggestion.generator.d.ts.map +1 -0
- package/dist/agents/suggestion.generator.js +96 -0
- package/dist/agents/suggestion.generator.js.map +1 -0
- package/dist/graphs/chat.graph.d.ts +312 -0
- package/dist/graphs/chat.graph.d.ts.map +1 -0
- package/dist/graphs/chat.graph.js +267 -0
- package/dist/graphs/chat.graph.js.map +1 -0
- package/dist/graphs/home.graph.d.ts +180 -0
- package/dist/graphs/home.graph.d.ts.map +1 -0
- package/dist/graphs/home.graph.js +598 -0
- package/dist/graphs/home.graph.js.map +1 -0
- package/dist/graphs/hyde.graph.d.ts +110 -0
- package/dist/graphs/hyde.graph.d.ts.map +1 -0
- package/dist/graphs/hyde.graph.js +235 -0
- package/dist/graphs/hyde.graph.js.map +1 -0
- package/dist/graphs/index.graph.d.ts +620 -0
- package/dist/graphs/index.graph.d.ts.map +1 -0
- package/dist/graphs/index.graph.js +226 -0
- package/dist/graphs/index.graph.js.map +1 -0
- package/dist/graphs/index_membership.graph.d.ts +250 -0
- package/dist/graphs/index_membership.graph.d.ts.map +1 -0
- package/dist/graphs/index_membership.graph.js +204 -0
- package/dist/graphs/index_membership.graph.js.map +1 -0
- package/dist/graphs/intent.graph.d.ts +490 -0
- package/dist/graphs/intent.graph.d.ts.map +1 -0
- package/dist/graphs/intent.graph.js +787 -0
- package/dist/graphs/intent.graph.js.map +1 -0
- package/dist/graphs/intent_index.graph.d.ts +396 -0
- package/dist/graphs/intent_index.graph.d.ts.map +1 -0
- package/dist/graphs/intent_index.graph.js +331 -0
- package/dist/graphs/intent_index.graph.js.map +1 -0
- package/dist/graphs/maintenance.graph.d.ts +177 -0
- package/dist/graphs/maintenance.graph.d.ts.map +1 -0
- package/dist/graphs/maintenance.graph.js +173 -0
- package/dist/graphs/maintenance.graph.js.map +1 -0
- package/dist/graphs/negotiation.graph.d.ts +819 -0
- package/dist/graphs/negotiation.graph.d.ts.map +1 -0
- package/dist/graphs/negotiation.graph.js +255 -0
- package/dist/graphs/negotiation.graph.js.map +1 -0
- package/dist/graphs/opportunity.graph.d.ts +1082 -0
- package/dist/graphs/opportunity.graph.d.ts.map +1 -0
- package/dist/graphs/opportunity.graph.js +2534 -0
- package/dist/graphs/opportunity.graph.js.map +1 -0
- package/dist/graphs/profile.graph.d.ts +617 -0
- package/dist/graphs/profile.graph.d.ts.map +1 -0
- package/dist/graphs/profile.graph.js +839 -0
- package/dist/graphs/profile.graph.js.map +1 -0
- package/dist/graphs/tests/chat.graph.mocks.d.ts +104 -0
- package/dist/graphs/tests/chat.graph.mocks.d.ts.map +1 -0
- package/dist/graphs/tests/chat.graph.mocks.js +225 -0
- package/dist/graphs/tests/chat.graph.mocks.js.map +1 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/auth.interface.d.ts +15 -0
- package/dist/interfaces/auth.interface.d.ts.map +1 -0
- package/dist/interfaces/auth.interface.js +2 -0
- package/dist/interfaces/auth.interface.js.map +1 -0
- package/dist/interfaces/cache.interface.d.ts +43 -0
- package/dist/interfaces/cache.interface.d.ts.map +1 -0
- package/dist/interfaces/cache.interface.js +6 -0
- package/dist/interfaces/cache.interface.js.map +1 -0
- package/dist/interfaces/chat-session.interface.d.ts +11 -0
- package/dist/interfaces/chat-session.interface.d.ts.map +1 -0
- package/dist/interfaces/chat-session.interface.js +2 -0
- package/dist/interfaces/chat-session.interface.js.map +1 -0
- package/dist/interfaces/contact.interface.d.ts +48 -0
- package/dist/interfaces/contact.interface.d.ts.map +1 -0
- package/dist/interfaces/contact.interface.js +2 -0
- package/dist/interfaces/contact.interface.js.map +1 -0
- package/dist/interfaces/database.interface.d.ts +1495 -0
- package/dist/interfaces/database.interface.d.ts.map +1 -0
- package/dist/interfaces/database.interface.js +2 -0
- package/dist/interfaces/database.interface.js.map +1 -0
- package/dist/interfaces/embedder.interface.d.ts +85 -0
- package/dist/interfaces/embedder.interface.d.ts.map +1 -0
- package/dist/interfaces/embedder.interface.js +5 -0
- package/dist/interfaces/embedder.interface.js.map +1 -0
- package/dist/interfaces/enrichment.interface.d.ts +40 -0
- package/dist/interfaces/enrichment.interface.d.ts.map +1 -0
- package/dist/interfaces/enrichment.interface.js +2 -0
- package/dist/interfaces/enrichment.interface.js.map +1 -0
- package/dist/interfaces/integration.interface.d.ts +91 -0
- package/dist/interfaces/integration.interface.d.ts.map +1 -0
- package/dist/interfaces/integration.interface.js +2 -0
- package/dist/interfaces/integration.interface.js.map +1 -0
- package/dist/interfaces/queue.interface.d.ts +17 -0
- package/dist/interfaces/queue.interface.d.ts.map +1 -0
- package/dist/interfaces/queue.interface.js +5 -0
- package/dist/interfaces/queue.interface.js.map +1 -0
- package/dist/interfaces/scraper.interface.d.ts +31 -0
- package/dist/interfaces/scraper.interface.d.ts.map +1 -0
- package/dist/interfaces/scraper.interface.js +2 -0
- package/dist/interfaces/scraper.interface.js.map +1 -0
- package/dist/interfaces/storage.interface.d.ts +46 -0
- package/dist/interfaces/storage.interface.d.ts.map +1 -0
- package/dist/interfaces/storage.interface.js +6 -0
- package/dist/interfaces/storage.interface.js.map +1 -0
- package/dist/mcp/mcp.server.d.ts +29 -0
- package/dist/mcp/mcp.server.d.ts.map +1 -0
- package/dist/mcp/mcp.server.js +171 -0
- package/dist/mcp/mcp.server.js.map +1 -0
- package/dist/states/chat.state.d.ts +126 -0
- package/dist/states/chat.state.d.ts.map +1 -0
- package/dist/states/chat.state.js +112 -0
- package/dist/states/chat.state.js.map +1 -0
- package/dist/states/home.state.d.ts +100 -0
- package/dist/states/home.state.d.ts.map +1 -0
- package/dist/states/home.state.js +74 -0
- package/dist/states/home.state.js.map +1 -0
- package/dist/states/hyde.state.d.ts +54 -0
- package/dist/states/hyde.state.d.ts.map +1 -0
- package/dist/states/hyde.state.js +66 -0
- package/dist/states/hyde.state.js.map +1 -0
- package/dist/states/index.state.d.ts +179 -0
- package/dist/states/index.state.d.ts.map +1 -0
- package/dist/states/index.state.js +56 -0
- package/dist/states/index.state.js.map +1 -0
- package/dist/states/index_membership.state.d.ts +77 -0
- package/dist/states/index_membership.state.d.ts.map +1 -0
- package/dist/states/index_membership.state.js +43 -0
- package/dist/states/index_membership.state.js.map +1 -0
- package/dist/states/intent.state.d.ts +203 -0
- package/dist/states/intent.state.d.ts.map +1 -0
- package/dist/states/intent.state.js +153 -0
- package/dist/states/intent.state.js.map +1 -0
- package/dist/states/intent_index.state.d.ts +148 -0
- package/dist/states/intent_index.state.d.ts.map +1 -0
- package/dist/states/intent_index.state.js +100 -0
- package/dist/states/intent_index.state.js.map +1 -0
- package/dist/states/maintenance.state.d.ts +36 -0
- package/dist/states/maintenance.state.d.ts.map +1 -0
- package/dist/states/maintenance.state.js +56 -0
- package/dist/states/maintenance.state.js.map +1 -0
- package/dist/states/negotiation.state.d.ts +230 -0
- package/dist/states/negotiation.state.d.ts.map +1 -0
- package/dist/states/negotiation.state.js +82 -0
- package/dist/states/negotiation.state.js.map +1 -0
- package/dist/states/opportunity.state.d.ts +300 -0
- package/dist/states/opportunity.state.d.ts.map +1 -0
- package/dist/states/opportunity.state.js +207 -0
- package/dist/states/opportunity.state.js.map +1 -0
- package/dist/states/profile.state.d.ts +172 -0
- package/dist/states/profile.state.d.ts.map +1 -0
- package/dist/states/profile.state.js +133 -0
- package/dist/states/profile.state.js.map +1 -0
- package/dist/streamers/chat.streamer.d.ts +55 -0
- package/dist/streamers/chat.streamer.d.ts.map +1 -0
- package/dist/streamers/chat.streamer.js +186 -0
- package/dist/streamers/chat.streamer.js.map +1 -0
- package/dist/streamers/index.d.ts +3 -0
- package/dist/streamers/index.d.ts.map +1 -0
- package/dist/streamers/index.js +3 -0
- package/dist/streamers/index.js.map +1 -0
- package/dist/streamers/response.streamer.d.ts +36 -0
- package/dist/streamers/response.streamer.d.ts.map +1 -0
- package/dist/streamers/response.streamer.js +46 -0
- package/dist/streamers/response.streamer.js.map +1 -0
- package/dist/support/chat.utils.d.ts +42 -0
- package/dist/support/chat.utils.d.ts.map +1 -0
- package/dist/support/chat.utils.js +89 -0
- package/dist/support/chat.utils.js.map +1 -0
- package/dist/support/debug-meta.sanitizer.d.ts +18 -0
- package/dist/support/debug-meta.sanitizer.d.ts.map +1 -0
- package/dist/support/debug-meta.sanitizer.js +82 -0
- package/dist/support/debug-meta.sanitizer.js.map +1 -0
- package/dist/support/feed.health.d.ts +32 -0
- package/dist/support/feed.health.d.ts.map +1 -0
- package/dist/support/feed.health.js +76 -0
- package/dist/support/feed.health.js.map +1 -0
- package/dist/support/introducer.discovery.d.ts +78 -0
- package/dist/support/introducer.discovery.d.ts.map +1 -0
- package/dist/support/introducer.discovery.js +101 -0
- package/dist/support/introducer.discovery.js.map +1 -0
- package/dist/support/log.d.ts +65 -0
- package/dist/support/log.d.ts.map +1 -0
- package/dist/support/log.js +76 -0
- package/dist/support/log.js.map +1 -0
- package/dist/support/lucide.icon-catalog.d.ts +22 -0
- package/dist/support/lucide.icon-catalog.d.ts.map +1 -0
- package/dist/support/lucide.icon-catalog.js +101 -0
- package/dist/support/lucide.icon-catalog.js.map +1 -0
- package/dist/support/opportunity.card-text.d.ts +39 -0
- package/dist/support/opportunity.card-text.d.ts.map +1 -0
- package/dist/support/opportunity.card-text.js +333 -0
- package/dist/support/opportunity.card-text.js.map +1 -0
- package/dist/support/opportunity.constants.d.ts +9 -0
- package/dist/support/opportunity.constants.d.ts.map +1 -0
- package/dist/support/opportunity.constants.js +11 -0
- package/dist/support/opportunity.constants.js.map +1 -0
- package/dist/support/opportunity.discover.d.ts +144 -0
- package/dist/support/opportunity.discover.d.ts.map +1 -0
- package/dist/support/opportunity.discover.js +610 -0
- package/dist/support/opportunity.discover.js.map +1 -0
- package/dist/support/opportunity.enricher.d.ts +44 -0
- package/dist/support/opportunity.enricher.d.ts.map +1 -0
- package/dist/support/opportunity.enricher.js +245 -0
- package/dist/support/opportunity.enricher.js.map +1 -0
- package/dist/support/opportunity.persist.d.ts +39 -0
- package/dist/support/opportunity.persist.d.ts.map +1 -0
- package/dist/support/opportunity.persist.js +63 -0
- package/dist/support/opportunity.persist.js.map +1 -0
- package/dist/support/opportunity.presentation.d.ts +21 -0
- package/dist/support/opportunity.presentation.d.ts.map +1 -0
- package/dist/support/opportunity.presentation.js +75 -0
- package/dist/support/opportunity.presentation.js.map +1 -0
- package/dist/support/opportunity.sanitize.d.ts +18 -0
- package/dist/support/opportunity.sanitize.d.ts.map +1 -0
- package/dist/support/opportunity.sanitize.js +89 -0
- package/dist/support/opportunity.sanitize.js.map +1 -0
- package/dist/support/opportunity.utils.d.ts +99 -0
- package/dist/support/opportunity.utils.d.ts.map +1 -0
- package/dist/support/opportunity.utils.js +184 -0
- package/dist/support/opportunity.utils.js.map +1 -0
- package/dist/support/performance.d.ts +19 -0
- package/dist/support/performance.d.ts.map +1 -0
- package/dist/support/performance.js +43 -0
- package/dist/support/performance.js.map +1 -0
- package/dist/support/profile.enrichment-display-name.d.ts +16 -0
- package/dist/support/profile.enrichment-display-name.d.ts.map +1 -0
- package/dist/support/profile.enrichment-display-name.js +22 -0
- package/dist/support/profile.enrichment-display-name.js.map +1 -0
- package/dist/support/protocol.logger.d.ts +22 -0
- package/dist/support/protocol.logger.d.ts.map +1 -0
- package/dist/support/protocol.logger.js +44 -0
- package/dist/support/protocol.logger.js.map +1 -0
- package/dist/support/request-context.d.ts +19 -0
- package/dist/support/request-context.d.ts.map +1 -0
- package/dist/support/request-context.js +7 -0
- package/dist/support/request-context.js.map +1 -0
- package/dist/tools/contact.tools.d.ts +7 -0
- package/dist/tools/contact.tools.d.ts.map +1 -0
- package/dist/tools/contact.tools.js +115 -0
- package/dist/tools/contact.tools.js.map +1 -0
- package/dist/tools/index.d.ts +17 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +140 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/index.tools.d.ts +3 -0
- package/dist/tools/index.tools.d.ts.map +1 -0
- package/dist/tools/index.tools.js +423 -0
- package/dist/tools/index.tools.js.map +1 -0
- package/dist/tools/integration.tools.d.ts +13 -0
- package/dist/tools/integration.tools.d.ts.map +1 -0
- package/dist/tools/integration.tools.js +77 -0
- package/dist/tools/integration.tools.js.map +1 -0
- package/dist/tools/intent.tools.d.ts +3 -0
- package/dist/tools/intent.tools.d.ts.map +1 -0
- package/dist/tools/intent.tools.js +458 -0
- package/dist/tools/intent.tools.js.map +1 -0
- package/dist/tools/opportunity.tools.d.ts +44 -0
- package/dist/tools/opportunity.tools.d.ts.map +1 -0
- package/dist/tools/opportunity.tools.js +814 -0
- package/dist/tools/opportunity.tools.js.map +1 -0
- package/dist/tools/profile.tools.d.ts +3 -0
- package/dist/tools/profile.tools.d.ts.map +1 -0
- package/dist/tools/profile.tools.js +513 -0
- package/dist/tools/profile.tools.js.map +1 -0
- package/dist/tools/tool.helpers.d.ts +225 -0
- package/dist/tools/tool.helpers.d.ts.map +1 -0
- package/dist/tools/tool.helpers.js +172 -0
- package/dist/tools/tool.helpers.js.map +1 -0
- package/dist/tools/tool.registry.d.ts +12 -0
- package/dist/tools/tool.registry.d.ts.map +1 -0
- package/dist/tools/tool.registry.js +62 -0
- package/dist/tools/tool.registry.js.map +1 -0
- package/dist/tools/utility.tools.d.ts +3 -0
- package/dist/tools/utility.tools.d.ts.map +1 -0
- package/dist/tools/utility.tools.js +107 -0
- package/dist/tools/utility.tools.js.map +1 -0
- package/dist/types/chat-streaming.types.d.ts +472 -0
- package/dist/types/chat-streaming.types.d.ts.map +1 -0
- package/dist/types/chat-streaming.types.js +260 -0
- package/dist/types/chat-streaming.types.js.map +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1,839 @@
|
|
|
1
|
+
import { StateGraph, START, END } from "@langchain/langgraph";
|
|
2
|
+
import { ProfileGraphState } from "../states/profile.state.js";
|
|
3
|
+
import { ProfileGenerator } from "../agents/profile.generator.js";
|
|
4
|
+
import { HydeGenerator } from "../agents/profile.hyde.generator.js";
|
|
5
|
+
import { shouldEnrichGhostDisplayNameFromParallel } from "../support/profile.enrichment-display-name.js";
|
|
6
|
+
import { protocolLogger } from "../support/protocol.logger.js";
|
|
7
|
+
import { timed } from "../support/performance.js";
|
|
8
|
+
import { requestContext } from "../support/request-context.js";
|
|
9
|
+
const logger = protocolLogger("ProfileGraphFactory");
|
|
10
|
+
/** Minimum length for input to be considered meaningful (e.g. not just "Yes") */
|
|
11
|
+
const MIN_MEANINGFUL_INPUT_LENGTH = 20;
|
|
12
|
+
/** Phrases that are confirmations only and must not be used as profile content */
|
|
13
|
+
const CONFIRMATION_PHRASES = new Set([
|
|
14
|
+
"yes", "yeah", "yep", "sure", "ok", "okay", "go ahead", "do it", "please",
|
|
15
|
+
"correct", "right", "exactly", "absolutely", "of course", "sounds good",
|
|
16
|
+
"create one", "create it", "set one up", "set it up", "create my profile",
|
|
17
|
+
"create profile", "set up profile", "create a profile"
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Returns true only if the input contains real profile information.
|
|
21
|
+
* Confirmation-only replies (e.g. "Yes" to "Would you like to create a profile?")
|
|
22
|
+
* must not be treated as input so we ask for user info / use scraper instead of inventing a profile.
|
|
23
|
+
*/
|
|
24
|
+
function isMeaningfulProfileInput(input) {
|
|
25
|
+
if (!input || typeof input !== "string")
|
|
26
|
+
return false;
|
|
27
|
+
const trimmed = input.trim();
|
|
28
|
+
if (trimmed.length < MIN_MEANINGFUL_INPUT_LENGTH)
|
|
29
|
+
return false;
|
|
30
|
+
const lower = trimmed.toLowerCase();
|
|
31
|
+
if (CONFIRMATION_PHRASES.has(lower))
|
|
32
|
+
return false;
|
|
33
|
+
if (CONFIRMATION_PHRASES.has(lower.replace(/[.!?]+$/, "")))
|
|
34
|
+
return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns true only when the value is a fully valid numeric vector (flat or nested).
|
|
39
|
+
* Used so we don't treat DB returns (e.g. pg vector as string, or empty/partial array) as "has embedding".
|
|
40
|
+
* Ensures callers re-embed when vectors contain non-number or NaN/Infinity.
|
|
41
|
+
*/
|
|
42
|
+
function hasValidProfileEmbedding(embedding) {
|
|
43
|
+
if (embedding == null)
|
|
44
|
+
return false;
|
|
45
|
+
if (!Array.isArray(embedding))
|
|
46
|
+
return false;
|
|
47
|
+
if (embedding.length === 0)
|
|
48
|
+
return false;
|
|
49
|
+
const first = embedding[0];
|
|
50
|
+
if (Array.isArray(first)) {
|
|
51
|
+
// Nested: number[][]
|
|
52
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
53
|
+
const sub = embedding[i];
|
|
54
|
+
if (!Array.isArray(sub) || sub.length === 0)
|
|
55
|
+
return false;
|
|
56
|
+
for (let j = 0; j < sub.length; j++) {
|
|
57
|
+
const v = sub[j];
|
|
58
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
// Flat: number[]
|
|
65
|
+
for (let i = 0; i < embedding.length; i++) {
|
|
66
|
+
const v = embedding[i];
|
|
67
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Factory class to build and compile the Profile Generation Graph.
|
|
74
|
+
*
|
|
75
|
+
* Flow:
|
|
76
|
+
* 1. check_state - Detect what's missing (profile, embeddings, hyde)
|
|
77
|
+
* 2. Conditional routing based on operation mode and missing components:
|
|
78
|
+
* - Query mode: Return immediately (fast path)
|
|
79
|
+
* - Write mode: Generate only what's needed
|
|
80
|
+
* 3. Profile generation (if needed)
|
|
81
|
+
* 4. Profile embedding (if needed)
|
|
82
|
+
* 5. HyDE generation (if needed or profile updated)
|
|
83
|
+
* 6. HyDE embedding (if needed)
|
|
84
|
+
*
|
|
85
|
+
* Key Features:
|
|
86
|
+
* - Read/Write separation (query vs write)
|
|
87
|
+
* - Conditional generation (skip expensive operations if data exists)
|
|
88
|
+
* - Automatic hyde regeneration when profile is updated
|
|
89
|
+
*/
|
|
90
|
+
export class ProfileGraphFactory {
|
|
91
|
+
constructor(database, embedder, scraper, enricher) {
|
|
92
|
+
this.database = database;
|
|
93
|
+
this.embedder = embedder;
|
|
94
|
+
this.scraper = scraper;
|
|
95
|
+
this.enricher = enricher;
|
|
96
|
+
}
|
|
97
|
+
createGraph() {
|
|
98
|
+
const profileGenerator = new ProfileGenerator();
|
|
99
|
+
const hydeGenerator = new HydeGenerator();
|
|
100
|
+
// ─────────────────────────────────────────────────────────
|
|
101
|
+
// NODE: Check State
|
|
102
|
+
// Loads existing profile from DB and detects what needs generation:
|
|
103
|
+
// - Profile missing
|
|
104
|
+
// - Profile embedding missing
|
|
105
|
+
// - HyDE description missing
|
|
106
|
+
// - HyDE embedding missing
|
|
107
|
+
// - User information insufficient for scraping
|
|
108
|
+
// ─────────────────────────────────────────────────────────
|
|
109
|
+
const checkStateNode = async (state) => {
|
|
110
|
+
return timed("ProfileGraph.checkState", async () => {
|
|
111
|
+
if (!state.userId) {
|
|
112
|
+
logger.error("Missing userId");
|
|
113
|
+
return {
|
|
114
|
+
error: "userId is required"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
logger.verbose("Checking profile state...", {
|
|
118
|
+
userId: state.userId,
|
|
119
|
+
operationMode: state.operationMode,
|
|
120
|
+
forceUpdate: state.forceUpdate
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
const profile = await this.database.getProfile(state.userId);
|
|
124
|
+
// Query mode: Just return the profile (fast path)
|
|
125
|
+
if (state.operationMode === 'query') {
|
|
126
|
+
logger.verbose("🚀 Query mode - returning existing profile (fast path)", {
|
|
127
|
+
hasProfile: !!profile
|
|
128
|
+
});
|
|
129
|
+
const profileWithId = profile ? await this.database.getProfileByUserId(state.userId) : null;
|
|
130
|
+
return {
|
|
131
|
+
profile: profile || undefined,
|
|
132
|
+
readResult: profile
|
|
133
|
+
? {
|
|
134
|
+
hasProfile: true,
|
|
135
|
+
profile: {
|
|
136
|
+
id: profileWithId?.id,
|
|
137
|
+
name: profile.identity.name,
|
|
138
|
+
bio: profile.identity.bio,
|
|
139
|
+
location: profile.identity.location,
|
|
140
|
+
skills: profile.attributes.skills,
|
|
141
|
+
interests: profile.attributes.interests,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
: {
|
|
145
|
+
hasProfile: false,
|
|
146
|
+
message: "You don't have a profile yet. Would you like to create one? You can share your LinkedIn, GitHub, or X/Twitter profile, or just tell me about yourself.",
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// Write mode: Detect what needs generation
|
|
151
|
+
// Treat confirmation-only input (e.g. "Yes") as no input so we ask for info / use scraper
|
|
152
|
+
const hasMeaningfulInput = !!state.input && isMeaningfulProfileInput(state.input);
|
|
153
|
+
const needsProfileGeneration = !profile || (state.forceUpdate && hasMeaningfulInput);
|
|
154
|
+
const needsProfileEmbedding = profile && !hasValidProfileEmbedding(profile.embedding);
|
|
155
|
+
const existingHydeDoc = await this.database.getHydeDocument('profile', state.userId, 'mirror');
|
|
156
|
+
const needsHydeGeneration = !existingHydeDoc || (state.forceUpdate && hasMeaningfulInput);
|
|
157
|
+
const needsHydeEmbedding = false; // Profile HyDE lives in hyde_documents; no partial "text only" state
|
|
158
|
+
// Check if we need to scrape (profile generation needed but no meaningful input provided)
|
|
159
|
+
const willNeedScraping = needsProfileGeneration && !hasMeaningfulInput;
|
|
160
|
+
// If we need to scrape, check if we have sufficient user information
|
|
161
|
+
let needsUserInfo = false;
|
|
162
|
+
let missingUserInfo = [];
|
|
163
|
+
if (willNeedScraping) {
|
|
164
|
+
logger.verbose("Will need scraping - checking user information...");
|
|
165
|
+
const user = await this.database.getUser(state.userId);
|
|
166
|
+
if (!user) {
|
|
167
|
+
logger.error("User not found", { userId: state.userId });
|
|
168
|
+
return {
|
|
169
|
+
error: `User not found: ${state.userId}`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// Check what information we have from the user table (schema: users)
|
|
173
|
+
// Required fields: email, name (always present)
|
|
174
|
+
// Optional fields: intro, avatar, location, socials
|
|
175
|
+
const hasSocials = !!(user.socials && (user.socials.x ||
|
|
176
|
+
user.socials.linkedin ||
|
|
177
|
+
user.socials.github ||
|
|
178
|
+
(user.socials.websites && user.socials.websites.length > 0)));
|
|
179
|
+
// Check if name is a full name (not just email username)
|
|
180
|
+
// For scraping to work well, we need first + last name
|
|
181
|
+
const hasMeaningfulName = user.name &&
|
|
182
|
+
user.name.trim() !== '' &&
|
|
183
|
+
!user.name.includes('@') &&
|
|
184
|
+
user.name.split(/\s+/).filter(Boolean).length >= 2;
|
|
185
|
+
const hasLocation = !!(user.location && user.location.trim() !== '');
|
|
186
|
+
// Minimum requirement for accurate scraping:
|
|
187
|
+
// - At least ONE social link (preferred - most reliable for finding the right person)
|
|
188
|
+
// - OR a full name (first + last) - less reliable but workable
|
|
189
|
+
// Location helps disambiguate but is not required
|
|
190
|
+
const hasMinimumInfo = hasSocials || hasMeaningfulName;
|
|
191
|
+
if (!hasMinimumInfo) {
|
|
192
|
+
needsUserInfo = true;
|
|
193
|
+
// Build precise list of what's missing and would help
|
|
194
|
+
if (!hasSocials) {
|
|
195
|
+
missingUserInfo.push('social_urls');
|
|
196
|
+
}
|
|
197
|
+
if (!hasMeaningfulName) {
|
|
198
|
+
missingUserInfo.push('full_name');
|
|
199
|
+
}
|
|
200
|
+
if (!hasLocation) {
|
|
201
|
+
missingUserInfo.push('location'); // Nice to have
|
|
202
|
+
}
|
|
203
|
+
logger.verbose("⚠️ Insufficient user information for scraping", {
|
|
204
|
+
hasSocials,
|
|
205
|
+
hasMeaningfulName,
|
|
206
|
+
hasLocation,
|
|
207
|
+
currentName: user.name,
|
|
208
|
+
missingUserInfo
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
logger.verbose("✅ Sufficient user information for scraping", {
|
|
213
|
+
hasSocials,
|
|
214
|
+
hasMeaningfulName,
|
|
215
|
+
hasLocation,
|
|
216
|
+
willProceedWith: hasSocials ? 'social links' : 'full name'
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
logger.verbose("📊 State detection complete", {
|
|
221
|
+
hasProfile: !!profile,
|
|
222
|
+
needsProfileGeneration,
|
|
223
|
+
needsProfileEmbedding,
|
|
224
|
+
needsHydeGeneration,
|
|
225
|
+
needsHydeEmbedding,
|
|
226
|
+
needsUserInfo,
|
|
227
|
+
missingUserInfo,
|
|
228
|
+
forceUpdate: state.forceUpdate,
|
|
229
|
+
hasInput: !!state.input,
|
|
230
|
+
hasMeaningfulInput,
|
|
231
|
+
hasHydeDocument: !!existingHydeDoc,
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
profile: profile || undefined,
|
|
235
|
+
hydeDescription: existingHydeDoc?.hydeText ?? undefined,
|
|
236
|
+
needsProfileGeneration,
|
|
237
|
+
needsProfileEmbedding,
|
|
238
|
+
needsHydeGeneration,
|
|
239
|
+
needsHydeEmbedding,
|
|
240
|
+
needsUserInfo,
|
|
241
|
+
missingUserInfo
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
logger.error("Failed to load profile", {
|
|
246
|
+
error: error instanceof Error ? error.message : String(error)
|
|
247
|
+
});
|
|
248
|
+
return {
|
|
249
|
+
profile: undefined,
|
|
250
|
+
error: "Failed to load profile from database"
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
};
|
|
255
|
+
// ─────────────────────────────────────────────────────────
|
|
256
|
+
// NODE: Scrape
|
|
257
|
+
// Scrapes data from web if input is not provided
|
|
258
|
+
// ─────────────────────────────────────────────────────────
|
|
259
|
+
const scrapeNode = async (state) => {
|
|
260
|
+
return timed("ProfileGraph.scrape", async () => {
|
|
261
|
+
if (state.input && isMeaningfulProfileInput(state.input)) {
|
|
262
|
+
logger.verbose("Meaningful input already provided - skipping scrape");
|
|
263
|
+
return {};
|
|
264
|
+
}
|
|
265
|
+
logger.verbose("Starting web scrape...", {
|
|
266
|
+
userId: state.userId
|
|
267
|
+
});
|
|
268
|
+
try {
|
|
269
|
+
// Fetch user details to construct objective for web scraping
|
|
270
|
+
const user = await this.database.getUser(state.userId);
|
|
271
|
+
if (!user) {
|
|
272
|
+
logger.error("User not found", { userId: state.userId });
|
|
273
|
+
return {
|
|
274
|
+
error: `User not found: ${state.userId}`
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
// Build scraping objective from available user information
|
|
278
|
+
// Priority: social links (most reliable) > name + location > email
|
|
279
|
+
const socialParts = [];
|
|
280
|
+
if (user.socials) {
|
|
281
|
+
if (user.socials.x)
|
|
282
|
+
socialParts.push(`X/Twitter: ${user.socials.x}`);
|
|
283
|
+
if (user.socials.linkedin)
|
|
284
|
+
socialParts.push(`LinkedIn: ${user.socials.linkedin}`);
|
|
285
|
+
if (user.socials.github)
|
|
286
|
+
socialParts.push(`GitHub: ${user.socials.github}`);
|
|
287
|
+
if (user.socials.websites && user.socials.websites.length > 0) {
|
|
288
|
+
user.socials.websites.forEach((url) => socialParts.push(`Website: ${url}`));
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Construct objective based on what we have
|
|
292
|
+
let objective = `Find information about ${user.name || 'this person'}`;
|
|
293
|
+
if (user.location) {
|
|
294
|
+
objective += ` located in ${user.location}`;
|
|
295
|
+
}
|
|
296
|
+
objective += '.\n\n';
|
|
297
|
+
if (socialParts.length > 0) {
|
|
298
|
+
objective += `Their social profiles:\n${socialParts.join('\n')}\n\n`;
|
|
299
|
+
objective += 'Use these links to find accurate information about their professional background, skills, and interests.';
|
|
300
|
+
}
|
|
301
|
+
else if (user.email) {
|
|
302
|
+
objective += `Their email: ${user.email}\n\n`;
|
|
303
|
+
objective += 'Search for professional information, skills, and background about this person.';
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
objective += 'Search for professional information and background about this person.';
|
|
307
|
+
}
|
|
308
|
+
logger.verbose("Constructed scraping objective", {
|
|
309
|
+
hasSocials: socialParts.length > 0,
|
|
310
|
+
hasLocation: !!user.location,
|
|
311
|
+
objectivePreview: objective.substring(0, 100)
|
|
312
|
+
});
|
|
313
|
+
const scrapedData = await this.scraper.scrape(objective);
|
|
314
|
+
logger.verbose("✅ Scrape complete", {
|
|
315
|
+
dataLength: scrapedData?.length || 0
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
objective,
|
|
319
|
+
input: scrapedData,
|
|
320
|
+
operationsPerformed: { scraped: true }
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
logger.error("Scrape failed", {
|
|
325
|
+
error: error instanceof Error ? error.message : String(error)
|
|
326
|
+
});
|
|
327
|
+
return {
|
|
328
|
+
error: "Web scrape failed"
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
};
|
|
333
|
+
// ─────────────────────────────────────────────────────────
|
|
334
|
+
// NODE: Auto-Generate (Parallel Chat API enrichment)
|
|
335
|
+
// Calls enrichUserProfile to get a structured profile directly.
|
|
336
|
+
// On success, sets prePopulatedProfile (skips LLM generation).
|
|
337
|
+
// On failure, falls back to basic user info for LLM generation.
|
|
338
|
+
// Used in 'generate' mode only.
|
|
339
|
+
// ─────────────────────────────────────────────────────────
|
|
340
|
+
const autoGenerateNode = async (state) => {
|
|
341
|
+
return timed("ProfileGraph.autoGenerate", async () => {
|
|
342
|
+
logger.verbose("Starting auto-generate via Chat API enrichment", {
|
|
343
|
+
userId: state.userId,
|
|
344
|
+
});
|
|
345
|
+
try {
|
|
346
|
+
const user = await this.database.getUser(state.userId);
|
|
347
|
+
if (!user) {
|
|
348
|
+
logger.error("User not found for auto-generate", { userId: state.userId });
|
|
349
|
+
return { error: `User not found: ${state.userId}` };
|
|
350
|
+
}
|
|
351
|
+
const request = {
|
|
352
|
+
name: user.name || undefined,
|
|
353
|
+
email: user.email || undefined,
|
|
354
|
+
linkedin: user.socials?.linkedin || undefined,
|
|
355
|
+
twitter: user.socials?.x || undefined,
|
|
356
|
+
github: user.socials?.github || undefined,
|
|
357
|
+
websites: user.socials?.websites?.length ? user.socials.websites : undefined,
|
|
358
|
+
};
|
|
359
|
+
const buildBasicInfo = () => {
|
|
360
|
+
const parts = [
|
|
361
|
+
user.name ? `Name: ${user.name}` : '',
|
|
362
|
+
user.email ? `Email: ${user.email}` : '',
|
|
363
|
+
user.location ? `Location: ${user.location}` : '',
|
|
364
|
+
user.intro ? `Bio: ${user.intro}` : '',
|
|
365
|
+
].filter(Boolean).join('\n');
|
|
366
|
+
return parts || "No information available";
|
|
367
|
+
};
|
|
368
|
+
try {
|
|
369
|
+
const enrichment = this.enricher
|
|
370
|
+
? await this.enricher.enrichUserProfile(request)
|
|
371
|
+
: null;
|
|
372
|
+
if (enrichment && !enrichment.isHuman) {
|
|
373
|
+
logger.info("Enrichment detected non-human entity, soft-deleting ghost", { userId: state.userId });
|
|
374
|
+
await this.database.softDeleteGhost(state.userId);
|
|
375
|
+
return { error: "Non-human entity detected" };
|
|
376
|
+
}
|
|
377
|
+
const hasMeaningfulEnrichment = !!enrichment &&
|
|
378
|
+
enrichment.confidentMatch &&
|
|
379
|
+
(enrichment.identity.bio.trim().length > 0 ||
|
|
380
|
+
enrichment.narrative.context.trim().length > 0 ||
|
|
381
|
+
enrichment.attributes.skills.length > 0 ||
|
|
382
|
+
enrichment.attributes.interests.length > 0);
|
|
383
|
+
if (hasMeaningfulEnrichment) {
|
|
384
|
+
logger.verbose("Chat API enrichment succeeded", {
|
|
385
|
+
userId: state.userId,
|
|
386
|
+
skillsCount: enrichment.attributes.skills.length,
|
|
387
|
+
interestsCount: enrichment.attributes.interests.length,
|
|
388
|
+
});
|
|
389
|
+
// Update user record with enriched data
|
|
390
|
+
const updatePayload = {};
|
|
391
|
+
const enrichedName = enrichment.identity.name?.trim();
|
|
392
|
+
if (enrichedName &&
|
|
393
|
+
shouldEnrichGhostDisplayNameFromParallel({ isGhost: !!user.isGhost, name: user.name ?? '', email: user.email ?? '' }, enrichedName)) {
|
|
394
|
+
updatePayload.name = enrichedName;
|
|
395
|
+
}
|
|
396
|
+
if (enrichment.identity.bio?.trim())
|
|
397
|
+
updatePayload.intro = enrichment.identity.bio.trim();
|
|
398
|
+
if (enrichment.identity.location?.trim())
|
|
399
|
+
updatePayload.location = enrichment.identity.location.trim();
|
|
400
|
+
const socials = {};
|
|
401
|
+
if (enrichment.socials.twitter)
|
|
402
|
+
socials.x = enrichment.socials.twitter;
|
|
403
|
+
if (enrichment.socials.linkedin)
|
|
404
|
+
socials.linkedin = enrichment.socials.linkedin;
|
|
405
|
+
if (enrichment.socials.github)
|
|
406
|
+
socials.github = enrichment.socials.github;
|
|
407
|
+
if (enrichment.socials.websites?.length)
|
|
408
|
+
socials.websites = enrichment.socials.websites;
|
|
409
|
+
if (Object.keys(socials).length > 0)
|
|
410
|
+
updatePayload.socials = socials;
|
|
411
|
+
if (Object.keys(updatePayload).length > 0) {
|
|
412
|
+
await this.database.updateUser(state.userId, updatePayload);
|
|
413
|
+
}
|
|
414
|
+
return {
|
|
415
|
+
prePopulatedProfile: {
|
|
416
|
+
identity: enrichment.identity,
|
|
417
|
+
narrative: enrichment.narrative,
|
|
418
|
+
attributes: enrichment.attributes,
|
|
419
|
+
},
|
|
420
|
+
needsUserInfo: false,
|
|
421
|
+
operationsPerformed: { scraped: true },
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
if (user.isGhost) {
|
|
425
|
+
logger.info("Low-confidence enrichment for ghost, soft-deleting", { userId: state.userId });
|
|
426
|
+
await this.database.softDeleteGhost(state.userId);
|
|
427
|
+
return { error: "Enrichment not confident for ghost user" };
|
|
428
|
+
}
|
|
429
|
+
logger.warn("Chat API returned low-signal enrichment, falling back to basic info", { userId: state.userId });
|
|
430
|
+
}
|
|
431
|
+
catch (enrichErr) {
|
|
432
|
+
if (user.isGhost) {
|
|
433
|
+
logger.info("Enrichment failed for ghost, soft-deleting", { userId: state.userId });
|
|
434
|
+
await this.database.softDeleteGhost(state.userId);
|
|
435
|
+
return { error: "Enrichment failed for ghost user" };
|
|
436
|
+
}
|
|
437
|
+
logger.warn("Chat API enrichment failed, falling back to basic info", {
|
|
438
|
+
userId: state.userId,
|
|
439
|
+
error: enrichErr instanceof Error ? enrichErr.message : String(enrichErr),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
input: buildBasicInfo(),
|
|
444
|
+
needsUserInfo: false,
|
|
445
|
+
needsProfileGeneration: true,
|
|
446
|
+
operationsPerformed: { scraped: true },
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
catch (err) {
|
|
450
|
+
logger.error("Auto-generate failed", {
|
|
451
|
+
error: err instanceof Error ? err.message : String(err),
|
|
452
|
+
});
|
|
453
|
+
return { error: `Auto-generate failed: ${err instanceof Error ? err.message : String(err)}` };
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
// ─────────────────────────────────────────────────────────
|
|
458
|
+
// NODE: Use Pre-Populated Profile
|
|
459
|
+
// Converts pre-populated profile (from external enrichment like Parallel Chat API)
|
|
460
|
+
// into the format expected by the embedding step, skipping LLM generation.
|
|
461
|
+
// ─────────────────────────────────────────────────────────
|
|
462
|
+
const usePrePopulatedProfileNode = async (state) => {
|
|
463
|
+
return timed("ProfileGraph.usePrePopulatedProfile", async () => {
|
|
464
|
+
if (!state.prePopulatedProfile) {
|
|
465
|
+
logger.error("No pre-populated profile provided");
|
|
466
|
+
return { error: "Pre-populated profile required" };
|
|
467
|
+
}
|
|
468
|
+
logger.verbose("Using pre-populated profile from external enrichment", {
|
|
469
|
+
name: state.prePopulatedProfile.identity.name,
|
|
470
|
+
skillsCount: state.prePopulatedProfile.attributes.skills.length,
|
|
471
|
+
interestsCount: state.prePopulatedProfile.attributes.interests.length,
|
|
472
|
+
});
|
|
473
|
+
return {
|
|
474
|
+
profile: {
|
|
475
|
+
...state.prePopulatedProfile,
|
|
476
|
+
userId: state.userId,
|
|
477
|
+
embedding: [],
|
|
478
|
+
},
|
|
479
|
+
needsHydeGeneration: true,
|
|
480
|
+
operationsPerformed: { generatedProfile: true },
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
// ─────────────────────────────────────────────────────────
|
|
485
|
+
// NODE: Generate Profile
|
|
486
|
+
// Generates profile from input using ProfileGenerator agent.
|
|
487
|
+
// If updating existing profile, merges new information intelligently.
|
|
488
|
+
// ─────────────────────────────────────────────────────────
|
|
489
|
+
const generateProfileNode = async (state) => {
|
|
490
|
+
return timed("ProfileGraph.generateProfile", async () => {
|
|
491
|
+
if (!state.input) {
|
|
492
|
+
logger.error("No input provided for profile generation");
|
|
493
|
+
return {
|
|
494
|
+
error: "Input required for profile generation"
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
logger.verbose("Starting profile generation...", {
|
|
498
|
+
hasExistingProfile: !!state.profile,
|
|
499
|
+
isUpdate: state.forceUpdate,
|
|
500
|
+
inputLength: state.input.length
|
|
501
|
+
});
|
|
502
|
+
const agentTimingsAccum = [];
|
|
503
|
+
try {
|
|
504
|
+
// If updating existing profile, include it in the input for context
|
|
505
|
+
let inputWithContext = state.input;
|
|
506
|
+
if (state.profile && state.forceUpdate) {
|
|
507
|
+
inputWithContext = `EXISTING PROFILE:\n${JSON.stringify(state.profile, null, 2)}\n\nUSER REQUEST:\n${state.input}\n\nApply the user's request to the existing profile. Preserve existing data unless the user asks to change or remove it. You may add, update, or remove skills and interests as requested. Output the full updated profile.`;
|
|
508
|
+
logger.verbose("Merging with existing profile");
|
|
509
|
+
}
|
|
510
|
+
const _traceEmitterProfileGen = requestContext.getStore()?.traceEmitter;
|
|
511
|
+
const profileGeneratorStart = Date.now();
|
|
512
|
+
_traceEmitterProfileGen?.({ type: "agent_start", name: "profile-generator" });
|
|
513
|
+
const result = await profileGenerator.invoke(inputWithContext);
|
|
514
|
+
agentTimingsAccum.push({ name: 'profile.generator', durationMs: Date.now() - profileGeneratorStart });
|
|
515
|
+
_traceEmitterProfileGen?.({ type: "agent_end", name: "profile-generator", durationMs: Date.now() - profileGeneratorStart, summary: `Generated profile for ${result.output.identity.name || "user"}` });
|
|
516
|
+
logger.verbose("✅ Profile generated successfully", {
|
|
517
|
+
name: result.output.identity.name,
|
|
518
|
+
skillsCount: result.output.attributes.skills.length,
|
|
519
|
+
interestsCount: result.output.attributes.interests.length
|
|
520
|
+
});
|
|
521
|
+
return {
|
|
522
|
+
profile: {
|
|
523
|
+
...result.output,
|
|
524
|
+
userId: state.userId,
|
|
525
|
+
embedding: []
|
|
526
|
+
},
|
|
527
|
+
// Mark that hyde needs regeneration since profile was updated
|
|
528
|
+
needsHydeGeneration: true,
|
|
529
|
+
agentTimings: agentTimingsAccum,
|
|
530
|
+
operationsPerformed: { generatedProfile: true }
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
catch (error) {
|
|
534
|
+
logger.error("Profile generation failed", {
|
|
535
|
+
error: error instanceof Error ? error.message : String(error)
|
|
536
|
+
});
|
|
537
|
+
return {
|
|
538
|
+
error: "Profile generation failed",
|
|
539
|
+
agentTimings: agentTimingsAccum
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
};
|
|
544
|
+
// ─────────────────────────────────────────────────────────
|
|
545
|
+
// NODE: Embed & Save Profile
|
|
546
|
+
// Generates embedding for profile and saves to DB
|
|
547
|
+
// ─────────────────────────────────────────────────────────
|
|
548
|
+
const embedSaveProfileNode = async (state) => {
|
|
549
|
+
return timed("ProfileGraph.embedSaveProfile", async () => {
|
|
550
|
+
if (!state.profile) {
|
|
551
|
+
logger.error("Profile missing in embed step");
|
|
552
|
+
return {
|
|
553
|
+
error: "Profile missing in embed step"
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
logger.verbose("Starting profile embedding...", {
|
|
557
|
+
userId: state.userId
|
|
558
|
+
});
|
|
559
|
+
try {
|
|
560
|
+
const profile = { ...state.profile };
|
|
561
|
+
const textToEmbed = [
|
|
562
|
+
'# Identity',
|
|
563
|
+
'## Name', profile.identity.name,
|
|
564
|
+
'## Bio', profile.identity.bio,
|
|
565
|
+
'## Location', profile.identity.location,
|
|
566
|
+
'# Narrative',
|
|
567
|
+
'## Context', profile.narrative.context,
|
|
568
|
+
'# Attributes',
|
|
569
|
+
'## Interests', profile.attributes.interests.join(', '),
|
|
570
|
+
'## Skills', profile.attributes.skills.join(', ')
|
|
571
|
+
].join('\n');
|
|
572
|
+
logger.verbose("Generating embedding...", {
|
|
573
|
+
textLength: textToEmbed.length
|
|
574
|
+
});
|
|
575
|
+
const embedding = await this.embedder.generate(textToEmbed);
|
|
576
|
+
profile.embedding = embedding;
|
|
577
|
+
logger.verbose("Saving profile to DB...", {
|
|
578
|
+
userId: state.userId,
|
|
579
|
+
embeddingDimensions: Array.isArray(embedding[0]) ? embedding[0].length : embedding.length
|
|
580
|
+
});
|
|
581
|
+
await this.database.saveProfile(state.userId, profile);
|
|
582
|
+
logger.verbose("✅ Profile saved successfully");
|
|
583
|
+
return {
|
|
584
|
+
profile,
|
|
585
|
+
operationsPerformed: { embeddedProfile: true }
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
catch (error) {
|
|
589
|
+
logger.error("Failed to embed/save profile", {
|
|
590
|
+
error: error instanceof Error ? error.message : String(error)
|
|
591
|
+
});
|
|
592
|
+
return {
|
|
593
|
+
error: "Failed to embed/save profile"
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
};
|
|
598
|
+
// ─────────────────────────────────────────────────────────
|
|
599
|
+
// NODE: Generate HyDE
|
|
600
|
+
// Generates Hypothetical Document Embedding description for profile matching
|
|
601
|
+
// ─────────────────────────────────────────────────────────
|
|
602
|
+
const generateHydeNode = async (state) => {
|
|
603
|
+
return timed("ProfileGraph.generateHyde", async () => {
|
|
604
|
+
if (!state.profile) {
|
|
605
|
+
logger.error("Profile missing for HyDE generation");
|
|
606
|
+
return {
|
|
607
|
+
error: "Profile missing for HyDE generation"
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
logger.verbose("Starting HyDE generation...", {
|
|
611
|
+
userId: state.userId,
|
|
612
|
+
profileName: state.profile.identity.name
|
|
613
|
+
});
|
|
614
|
+
const agentTimingsAccum = [];
|
|
615
|
+
try {
|
|
616
|
+
const profileString = JSON.stringify(state.profile, null, 2);
|
|
617
|
+
const _traceEmitterHydeGen = requestContext.getStore()?.traceEmitter;
|
|
618
|
+
const hydeGeneratorStart = Date.now();
|
|
619
|
+
_traceEmitterHydeGen?.({ type: "agent_start", name: "hyde-generator" });
|
|
620
|
+
const result = await hydeGenerator.invoke(profileString);
|
|
621
|
+
agentTimingsAccum.push({ name: 'hyde.generator', durationMs: Date.now() - hydeGeneratorStart });
|
|
622
|
+
_traceEmitterHydeGen?.({ type: "agent_end", name: "hyde-generator", durationMs: Date.now() - hydeGeneratorStart, summary: `Generated HyDE for ${state.profile?.identity?.name || "profile"}` });
|
|
623
|
+
logger.verbose("✅ HyDE generated successfully", {
|
|
624
|
+
descriptionLength: result.textToEmbed.length
|
|
625
|
+
});
|
|
626
|
+
return {
|
|
627
|
+
hydeDescription: result.textToEmbed,
|
|
628
|
+
agentTimings: agentTimingsAccum,
|
|
629
|
+
operationsPerformed: { generatedHyde: true }
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
catch (error) {
|
|
633
|
+
logger.error("HyDE generation failed", {
|
|
634
|
+
error: error instanceof Error ? error.message : String(error)
|
|
635
|
+
});
|
|
636
|
+
return {
|
|
637
|
+
error: "HyDE generation failed"
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
};
|
|
642
|
+
// ─────────────────────────────────────────────────────────
|
|
643
|
+
// NODE: Embed & Save HyDE
|
|
644
|
+
// Generates embedding for HyDE description and saves to DB
|
|
645
|
+
// ─────────────────────────────────────────────────────────
|
|
646
|
+
const embedSaveHydeNode = async (state) => {
|
|
647
|
+
return timed("ProfileGraph.embedSaveHyde", async () => {
|
|
648
|
+
if (!state.hydeDescription) {
|
|
649
|
+
logger.error("HyDE description missing");
|
|
650
|
+
return {
|
|
651
|
+
error: "HyDE description missing"
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
logger.verbose("Starting HyDE embedding...", {
|
|
655
|
+
userId: state.userId,
|
|
656
|
+
descriptionLength: state.hydeDescription.length
|
|
657
|
+
});
|
|
658
|
+
try {
|
|
659
|
+
const hydeEmbedding = await this.embedder.generate(state.hydeDescription);
|
|
660
|
+
// Normalize embedding if needed (Adapters usually handle this, but to be sure)
|
|
661
|
+
const flatHydeEmbedding = Array.isArray(hydeEmbedding[0])
|
|
662
|
+
? hydeEmbedding[0]
|
|
663
|
+
: hydeEmbedding;
|
|
664
|
+
logger.verbose("Saving HyDE to hyde_documents...", {
|
|
665
|
+
userId: state.userId,
|
|
666
|
+
embeddingDimensions: flatHydeEmbedding.length
|
|
667
|
+
});
|
|
668
|
+
await this.database.saveHydeDocument({
|
|
669
|
+
sourceType: 'profile',
|
|
670
|
+
sourceId: state.userId,
|
|
671
|
+
strategy: 'mirror',
|
|
672
|
+
targetCorpus: 'profiles',
|
|
673
|
+
hydeText: state.hydeDescription,
|
|
674
|
+
hydeEmbedding: flatHydeEmbedding,
|
|
675
|
+
});
|
|
676
|
+
return {
|
|
677
|
+
operationsPerformed: { embeddedHyde: true }
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
catch (error) {
|
|
681
|
+
logger.error("Failed to embed/save HyDE", {
|
|
682
|
+
error: error instanceof Error ? error.message : String(error)
|
|
683
|
+
});
|
|
684
|
+
return {
|
|
685
|
+
error: "Failed to embed/save HyDE"
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
};
|
|
690
|
+
// ─────────────────────────────────────────────────────────
|
|
691
|
+
// ROUTING CONDITIONS
|
|
692
|
+
// Smart conditional routing based on operation mode and missing components
|
|
693
|
+
// ─────────────────────────────────────────────────────────
|
|
694
|
+
/**
|
|
695
|
+
* Route from check_state to next step based on operation mode and detected needs.
|
|
696
|
+
*/
|
|
697
|
+
const checkStateCondition = (state) => {
|
|
698
|
+
// Query mode: Return immediately (fast path)
|
|
699
|
+
if (state.operationMode === 'query') {
|
|
700
|
+
logger.verbose("Query mode - ending (fast path)");
|
|
701
|
+
return END;
|
|
702
|
+
}
|
|
703
|
+
// Pre-populated profile from external enrichment (e.g. Parallel Chat API)
|
|
704
|
+
// Skip profile generation, go directly to embedding
|
|
705
|
+
if (state.prePopulatedProfile) {
|
|
706
|
+
logger.verbose("Pre-populated profile detected - skipping generation, routing to embed");
|
|
707
|
+
return "use_prepopulated_profile";
|
|
708
|
+
}
|
|
709
|
+
// Generate mode: use enrichUserProfile Chat API to auto-generate
|
|
710
|
+
if (state.operationMode === 'generate') {
|
|
711
|
+
logger.verbose("Generate mode - routing to auto_generate");
|
|
712
|
+
return "auto_generate";
|
|
713
|
+
}
|
|
714
|
+
// Check if user information is insufficient for scraping
|
|
715
|
+
// Return early so chat graph can request the missing information
|
|
716
|
+
if (state.needsUserInfo) {
|
|
717
|
+
logger.verbose("⚠️ Insufficient user info - requesting from user", {
|
|
718
|
+
missingInfo: state.missingUserInfo
|
|
719
|
+
});
|
|
720
|
+
return END;
|
|
721
|
+
}
|
|
722
|
+
// Write mode: Check what needs generation
|
|
723
|
+
if (state.needsProfileGeneration) {
|
|
724
|
+
// Only use provided input if it's meaningful (not just "Yes" / confirmation)
|
|
725
|
+
if (state.input && isMeaningfulProfileInput(state.input)) {
|
|
726
|
+
logger.verbose("Profile generation needed with meaningful input provided");
|
|
727
|
+
return "generate_profile";
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
logger.verbose("Profile generation needed - scraping first (no meaningful input)");
|
|
731
|
+
return "scrape";
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// Profile exists but missing embedding
|
|
735
|
+
if (state.needsProfileEmbedding) {
|
|
736
|
+
logger.verbose("Profile embedding needed");
|
|
737
|
+
return "embed_save_profile";
|
|
738
|
+
}
|
|
739
|
+
// Profile and embedding exist, check hyde
|
|
740
|
+
if (state.needsHydeGeneration) {
|
|
741
|
+
logger.verbose("HyDE generation needed");
|
|
742
|
+
return "generate_hyde";
|
|
743
|
+
}
|
|
744
|
+
// Hyde exists but missing embedding
|
|
745
|
+
if (state.needsHydeEmbedding) {
|
|
746
|
+
logger.verbose("HyDE embedding needed");
|
|
747
|
+
return "embed_save_hyde";
|
|
748
|
+
}
|
|
749
|
+
// Everything exists and is up to date
|
|
750
|
+
logger.verbose("All components exist - ending");
|
|
751
|
+
return END;
|
|
752
|
+
};
|
|
753
|
+
/**
|
|
754
|
+
* Route after profile embedding to check if hyde needs generation.
|
|
755
|
+
*/
|
|
756
|
+
const afterProfileEmbeddingCondition = (state) => {
|
|
757
|
+
// If profile was just generated/updated, regenerate hyde
|
|
758
|
+
if (state.needsHydeGeneration || state.forceUpdate) {
|
|
759
|
+
logger.verbose("Profile updated - regenerating HyDE");
|
|
760
|
+
return "generate_hyde";
|
|
761
|
+
}
|
|
762
|
+
// Check if hyde embedding is missing
|
|
763
|
+
if (state.needsHydeEmbedding) {
|
|
764
|
+
logger.verbose("HyDE embedding needed");
|
|
765
|
+
return "embed_save_hyde";
|
|
766
|
+
}
|
|
767
|
+
logger.verbose("Profile complete - ending");
|
|
768
|
+
return END;
|
|
769
|
+
};
|
|
770
|
+
/**
|
|
771
|
+
* Route after hyde generation to embedding step.
|
|
772
|
+
* Always embed after generating hyde.
|
|
773
|
+
*/
|
|
774
|
+
const afterHydeGenerationCondition = (state) => {
|
|
775
|
+
logger.verbose("HyDE generated - proceeding to embedding");
|
|
776
|
+
return "embed_save_hyde";
|
|
777
|
+
};
|
|
778
|
+
// ─────────────────────────────────────────────────────────
|
|
779
|
+
// GRAPH ASSEMBLY
|
|
780
|
+
// Conditional flow based on operation mode and detected needs
|
|
781
|
+
// ─────────────────────────────────────────────────────────
|
|
782
|
+
const workflow = new StateGraph(ProfileGraphState)
|
|
783
|
+
// Add all nodes
|
|
784
|
+
.addNode("check_state", checkStateNode)
|
|
785
|
+
.addNode("scrape", scrapeNode)
|
|
786
|
+
.addNode("auto_generate", autoGenerateNode)
|
|
787
|
+
.addNode("use_prepopulated_profile", usePrePopulatedProfileNode)
|
|
788
|
+
.addNode("generate_profile", generateProfileNode)
|
|
789
|
+
.addNode("embed_save_profile", embedSaveProfileNode)
|
|
790
|
+
.addNode("generate_hyde", generateHydeNode)
|
|
791
|
+
.addNode("embed_save_hyde", embedSaveHydeNode)
|
|
792
|
+
// Start with state check
|
|
793
|
+
.addEdge(START, "check_state")
|
|
794
|
+
// Conditional routing from check_state
|
|
795
|
+
.addConditionalEdges("check_state", checkStateCondition, {
|
|
796
|
+
use_prepopulated_profile: "use_prepopulated_profile", // Pre-populated profile -> skip generation
|
|
797
|
+
auto_generate: "auto_generate", // Generate mode -> Chat API enrichment
|
|
798
|
+
scrape: "scrape", // Need profile, no input -> scrape first
|
|
799
|
+
generate_profile: "generate_profile", // Need profile, have input -> generate
|
|
800
|
+
embed_save_profile: "embed_save_profile", // Have profile, need embedding
|
|
801
|
+
generate_hyde: "generate_hyde", // Have profile+embedding, need hyde
|
|
802
|
+
embed_save_hyde: "embed_save_hyde", // Have hyde, need embedding
|
|
803
|
+
[END]: END // Query mode or everything exists
|
|
804
|
+
})
|
|
805
|
+
// Pre-populated profile feeds into embedding (skips generation)
|
|
806
|
+
.addEdge("use_prepopulated_profile", "embed_save_profile")
|
|
807
|
+
// Auto-generate routes based on enrichment result
|
|
808
|
+
.addConditionalEdges("auto_generate", (state) => {
|
|
809
|
+
if (state.prePopulatedProfile) {
|
|
810
|
+
logger.verbose("Enrichment succeeded — using pre-populated profile");
|
|
811
|
+
return "use_prepopulated_profile";
|
|
812
|
+
}
|
|
813
|
+
logger.verbose("Enrichment failed — falling back to LLM profile generation");
|
|
814
|
+
return "generate_profile";
|
|
815
|
+
}, {
|
|
816
|
+
use_prepopulated_profile: "use_prepopulated_profile",
|
|
817
|
+
generate_profile: "generate_profile",
|
|
818
|
+
})
|
|
819
|
+
// Scrape -> Generate profile (linear)
|
|
820
|
+
.addEdge("scrape", "generate_profile")
|
|
821
|
+
// Generate profile -> Embed profile (linear)
|
|
822
|
+
.addEdge("generate_profile", "embed_save_profile")
|
|
823
|
+
// After profile embedding, check if hyde needs generation
|
|
824
|
+
.addConditionalEdges("embed_save_profile", afterProfileEmbeddingCondition, {
|
|
825
|
+
generate_hyde: "generate_hyde", // Profile updated -> regenerate hyde
|
|
826
|
+
embed_save_hyde: "embed_save_hyde", // Only hyde embedding missing
|
|
827
|
+
[END]: END // Everything complete
|
|
828
|
+
})
|
|
829
|
+
// After hyde generation, always embed it
|
|
830
|
+
.addConditionalEdges("generate_hyde", afterHydeGenerationCondition, {
|
|
831
|
+
embed_save_hyde: "embed_save_hyde"
|
|
832
|
+
})
|
|
833
|
+
// Hyde embedding -> END (linear)
|
|
834
|
+
.addEdge("embed_save_hyde", END);
|
|
835
|
+
logger.verbose("Graph built successfully");
|
|
836
|
+
return workflow.compile();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
//# sourceMappingURL=profile.graph.js.map
|