Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions electron/ai/moleculeInspection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ export interface RouteResolutionOutcome {
/** Species the model supplied as structures because no name would resolve, as prose, so the
* caller discloses that their structure came from the model, not a reference. */
authorStructures: string[];
/** Every species' resolution status and source, so a run can record where each structure came
* from. The author-supplied ones are the category silent wrongness hides in. */
resolutionSources: Array<{ status: string; source?: string }>;
/** True when the installed package has no resolve-names tool, so the caller falls back to
* the legacy reaction-line path. */
legacy: boolean;
Expand Down Expand Up @@ -505,7 +508,7 @@ export async function resolveNamedRoute(
modelAnswer: string,
options: InspectOptions = {},
): Promise<RouteResolutionOutcome> {
const legacy: RouteResolutionOutcome = { answer: finalAnswer, steps: [], labels: [], consistent: true, corrections: [], authorStructures: [], legacy: true };
const legacy: RouteResolutionOutcome = { answer: finalAnswer, steps: [], labels: [], consistent: true, corrections: [], resolutionSources: [], authorStructures: [], legacy: true };
if (options.enabled === false) return legacy;
const provider = resolveProvider();
if (!provider) return legacy;
Expand Down Expand Up @@ -597,6 +600,7 @@ export async function resolveNamedRoute(
const steps = buildRouteSteps(resolvedByStep);
const labels: RouteSpeciesLabel[][] = resolvedByStep.map((step) => step.filter((entry) => entry.smiles).map((entry) => ({ role: entry.role, byproduct: entry.byproduct, name: entry.name, smiles: entry.smiles! })));
const authorStructures = resolvedByStep.flat().filter((entry) => entry.status === 'fallback' && entry.smiles).map((entry) => `${entry.name} — \`${entry.smiles}\``);
const resolutionSources = resolvedByStep.flat().map((entry) => ({ status: entry.status, ...(entry.source ? { source: entry.source } : {}) }));
const annotated = `${annotateSpeciesSmiles(finalAnswer, resolvedByStep).trimEnd()}\n`;
return {
answer: annotated,
Expand All @@ -605,6 +609,7 @@ export async function resolveNamedRoute(
consistent: critical.length === 0,
corrections,
authorStructures,
resolutionSources,
...(critical.length ? { clarification: formatUnresolvedNameClarification(critical, options.target), unresolved: critical } : {}),
legacy: false,
};
Expand Down Expand Up @@ -888,7 +893,7 @@ export async function appendRouteReportAndDrawings(
// Paint the deterministic report and drawings before the reviewer returns. The transport
// replaces the provisional stream with this returned answer, so the route only waits on
// the reviewer when the reviewer is the last thing outstanding.
if (options.onDeterministic) options.onDeterministic(`${finalAnswer.trimEnd()}\n\n${formatRouteAudit(audit, labels, null, true)}\n${drawings}`);
if (options.onDeterministic) options.onDeterministic(`${finalAnswer.trimEnd()}\n\n${formatRouteAudit(audit, labels, null, true, overrides.unresolved ?? [])}\n${drawings}`);
// The lookup already carries its drawings; this only formats. Best-effort throughout. The
// step support (textbook passage per reaction class, ORD alternatives for a failed or
// unprecedented step) needs the lookup's classes, so it follows it, still beside the review.
Expand All @@ -912,7 +917,7 @@ export async function appendRouteReportAndDrawings(
// Which starting materials the user's vendor stock lists hold (no lists: nothing is said).
const stockPromise = options.evidenceScope?.external === false ? Promise.resolve('') : startingMaterialStockLine(runner, labels).catch(() => '');
const review = await reviewPromise;
const report = formatRouteAudit(audit, labels, review);
const report = formatRouteAudit(audit, labels, review, false, overrides.unresolved ?? []);
const precedentText = await precedentSection;
const support = await supportPromise;
// A step's high-severity clashes ride along in its fix prompt, as evidence.
Expand Down
7 changes: 5 additions & 2 deletions electron/ai/researchAssistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ResearchCorpusRun } from './researchCorpusRun';
import { RESEARCH_CHAT_AGENT_DECISION_BYTES, RESEARCH_CHAT_AGENT_SETTINGS, RESEARCH_CHAT_LIGHT_AGENT_SETTINGS, researchScopeForPrompt, validateRetrievalSettings, compactResearchTraversal } from '@shared/researchCorpus';
import { planResearchTurn, literalResearchTurnPlan } from './researchTurnPlanner';
import { inspectResearchMolecules, appendStructureAudit, appendRouteReportAndDrawings, resolveNamedRoute, chemistryRunner } from './moleculeInspection';
import { countRouteSteps, findStepNamedSpecies, formatAuthorStructureNote, formatMissingSpeciesPrompt, formatNameCorrectionNote, formatRouteCheckUnavailable, isRouteFixPrompt, MOLECULE_DOSSIER_SYSTEM_RULE, ROUTE_CONTINUITY_SYSTEM_RULE, requestedTargetFor, routeFixPromptForHistory, routeReportsForHistory } from '@shared/moleculeInspection';
import { countRouteSteps, findStepNamedSpecies, formatAuthorStructureNote, formatResolutionSourceNote, formatMissingSpeciesPrompt, formatNameCorrectionNote, formatRouteCheckUnavailable, isRouteFixPrompt, MOLECULE_DOSSIER_SYSTEM_RULE, ROUTE_CONTINUITY_SYSTEM_RULE, requestedTargetFor, routeFixPromptForHistory, routeReportsForHistory } from '@shared/moleculeInspection';
import { SYNTHESIS_TEMPLATE_ADDENDUM, looksLikeSynthesisRequest } from '@shared/synthesisPrompt';
import { reviseRouteWithEvidence, revisionUserMessage, routeEvidencePassEnabled } from './routeEvidencePass';
import { SYNTHESIS_EVIDENCE_KEY, SYNTHESIS_EVIDENCE_SYSTEM_RULE, synthesisEvidencePayload, synthesisRetrievalQuery } from '@shared/synthesisEvidence';
Expand Down Expand Up @@ -288,7 +288,10 @@ async function auditAnswer(answer: string, execution: ReturnType<typeof skillExe
const routed = await appendRouteReportAndDrawings(withStructures, resolved.answer, { ...options, target: execution.target }, { steps: resolved.steps, labels: resolved.labels, unresolved: resolved.unresolved ?? [] });
const correctionNote = formatNameCorrectionNote(resolved.corrections);
const structureNote = formatAuthorStructureNote(resolved.authorStructures);
const notes = [correctionNote, structureNote].filter(Boolean).join('\n\n');
// Where every structure came from. A run that cannot say this cannot tell an offline
// dictionary hit from a network lookup or from the model's own drawing of the molecule.
const sourceNote = formatResolutionSourceNote(resolved.resolutionSources);
const notes = [correctionNote, structureNote, sourceNote].filter(Boolean).join('\n\n');
const withNotes = notes ? `${routed.trimEnd()}\n\n${notes}\n` : routed;
return resolved.clarification ? `${withNotes.trimEnd()}\n\n${resolved.clarification}\n` : withNotes;
} finally {
Expand Down
Loading
Loading