Skip to content

Commit 2ee8af9

Browse files
committed
feat: add semantic bias option and update metrics documentation
Why: The analyzer previously used only keyword bias cues. Users now need an opt-in mode for transformer-based semantic bias checks. The metrics doc and README also required updates to explain the new behavior. What: - Add --semantic-bias flag to analyse command - Add semanticBias field through tools, workflow, and analyzer options - Load semantic bias detector lazily to avoid extra startup cost - Update metrics doc with keyword vs semantic bias note - Update README with usage examples for semantic bias mode - Update structured-report tests for new similarity fields and analysis_window section
1 parent 1dca993 commit 2ee8af9

6 files changed

Lines changed: 40 additions & 4 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,23 @@ catalog or discover new ones using the lookup command.
150150

151151
1. Run the analyzer:
152152

153+
```bash
154+
gwaln analyse --topic moon
155+
```
156+
157+
To bypass cached results and regenerate even if inputs are unchanged:
158+
153159
```bash
154160
gwaln analyse --topic moon --force
155161
```
156162

163+
By default, bias cues are keyword-only. Enable transformer-based
164+
semantic bias detection (slower, downloads a model) when you need it:
165+
166+
```bash
167+
gwaln analyse --topic moon --force --semantic-bias
168+
```
169+
157170
2. Review the terminal summary:
158171

159172
```bash

docs/html-report-metrics.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ the articles **diverge**. A score near 0.0 means “near-identical /
129129
likely copied,” and a score near 1.0 means “independent or divergent
130130
content.” Very high similarity will naturally push the score down.
131131

132+
> Note: Bias cues in the default CLI run come from the keyword detector.
133+
> Semantic transformer-based bias checks are opt-in (`--semantic-bias`)
134+
> and take longer because they load a model.
135+
132136
### How the score is calculated (current behavior)
133137

134138
The score starts at `1 - sentence_similarity`, then applies boosts or

src/commands/analyse.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface AnalyseCliOptions extends BiasVerifierOptionInput, GeminiSummaryOption
2121
force?: boolean;
2222
geminiSummary?: boolean;
2323
verifyCitations?: boolean;
24+
semanticBias?: boolean;
2425
}
2526

2627
const createCliHooks = (): AnalyzeWorkflowHooks => {
@@ -62,6 +63,10 @@ const analyseCommand = new Command('analyse')
6263
'--verify-citations',
6364
'Fetch Grokipedia citations and confirm extra sentences are supported',
6465
)
66+
.option(
67+
'--semantic-bias',
68+
'Enable transformer-based semantic bias detection (otherwise keyword-only cues are used)',
69+
)
6570
.action(async (options: AnalyseCliOptions) => {
6671
const verifier = resolveBiasVerifierOptions(options);
6772
const summary = options.geminiSummary ? resolveGeminiSummaryOptions(options) : null;
@@ -71,6 +76,7 @@ const analyseCommand = new Command('analyse')
7176
biasVerifier: verifier,
7277
summary,
7378
verifyCitations: options.verifyCitations,
79+
semanticBias: options.semanticBias,
7480
hooks: createCliHooks(),
7581
});
7682
});

src/lib/analyzer.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
type EntityDiscrepancy,
3232
type NumericDiscrepancy,
3333
} from './discrepancies';
34-
import { detectSemanticBiasBatch } from './semantic-bias-detector';
3534
import type {
3635
StructuredArticle,
3736
StructuredClaim,
@@ -106,6 +105,7 @@ export interface AnalysisMeta {
106105

107106
interface AnalyzerOptions {
108107
contentHash?: string;
108+
semanticBias?: boolean;
109109
}
110110

111111
export interface GeminiSummary {
@@ -458,6 +458,7 @@ const detectBiasEventsHybrid = async (
458458
}
459459

460460
const keywordEvents = detectBiasEventsKeywordOnly(extraSentences, wikiText);
461+
const { detectSemanticBiasBatch } = await import('./semantic-bias-detector');
461462

462463
const flaggedSentences = new Set(keywordEvents.map((e) => e.evidence.grokipedia!));
463464
const sampleSize = Math.min(50, Math.ceil(extraSentences.length * 0.1));
@@ -530,7 +531,11 @@ const detectBiasEventsHybrid = async (
530531
const detectBiasEvents = async (
531532
extraSentences: string[],
532533
wikiText: string,
534+
enableSemantic: boolean,
533535
): Promise<DiscrepancyRecord[]> => {
536+
if (!enableSemantic) {
537+
return detectBiasEventsKeywordOnly(extraSentences, wikiText);
538+
}
534539
try {
535540
return await detectBiasEventsHybrid(extraSentences, wikiText);
536541
} catch (error) {
@@ -1046,7 +1051,8 @@ export const analyzeContent = async (
10461051
const numericDiscrepancies = detectNumericDiscrepancies(claimAlignment);
10471052
const entityDiscrepancies = detectEntityDiscrepancies(claimAlignment);
10481053

1049-
const biasEvents = await detectBiasEvents(extra, wikiText);
1054+
const semanticBiasEnabled = options.semanticBias === true;
1055+
const biasEvents = await detectBiasEvents(extra, wikiText, semanticBiasEnabled);
10501056
const hallucinationEvents = detectHallucinationEvents(extra, wiki.content.claims);
10511057
const factualErrors = detectFactualErrors(
10521058
claimAlignment,

src/tools/analyze.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const AnalyzeInputSchema = z.object({
1919
geminiModel: z.string().optional(),
2020
geminiSummary: z.boolean().optional(),
2121
verifyCitations: z.boolean().optional(),
22+
semanticBias: z.boolean().optional(),
2223
});
2324

2425
export const analyzeTool = {
@@ -52,6 +53,7 @@ export const analyzeHandler = async (
5253
biasVerifier: verifier,
5354
summary,
5455
verifyCitations: input.verifyCitations,
56+
semanticBias: input.semanticBias,
5557
logger,
5658
});
5759
return {

src/workflows/analyze-workflow.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface AnalyzeWorkflowOptions {
5757
verifyCitations?: boolean;
5858
logger?: Pick<Console, 'log' | 'warn' | 'error'>;
5959
hooks?: AnalyzeWorkflowHooks;
60+
semanticBias?: boolean;
6061
}
6162

6263
type WorkflowLogger = Pick<Console, 'log' | 'warn' | 'error'>;
@@ -103,6 +104,7 @@ const buildTopicContext = (topic: Topic): TopicContext => {
103104
const analyzeTopicSync = async (
104105
topic: Topic,
105106
force: boolean | undefined,
107+
semanticBias: boolean | undefined,
106108
context: TopicContext,
107109
logger: WorkflowLogger,
108110
): Promise<AnalysisPayload | null> => {
@@ -123,19 +125,21 @@ const analyzeTopicSync = async (
123125

124126
return await analyzeContent(topic, context.wikiSource, context.grokSource, {
125127
contentHash: context.contentHash,
128+
semanticBias,
126129
});
127130
};
128131

129132
const analyzeTopicAsync = (
130133
topic: Topic,
131134
force: boolean | undefined,
135+
semanticBias: boolean | undefined,
132136
context: TopicContext,
133137
logger: WorkflowLogger,
134138
): Promise<AnalysisPayload | null> =>
135139
new Promise((resolve, reject) => {
136140
setImmediate(async () => {
137141
try {
138-
resolve(await analyzeTopicSync(topic, force, context, logger));
142+
resolve(await analyzeTopicSync(topic, force, semanticBias, context, logger));
139143
} catch (error) {
140144
reject(error);
141145
}
@@ -150,6 +154,7 @@ export const runAnalyzeWorkflow = async ({
150154
verifyCitations,
151155
logger,
152156
hooks,
157+
semanticBias,
153158
}: AnalyzeWorkflowOptions): Promise<AnalyzeTopicResult[]> => {
154159
const topics = loadTopics();
155160
const selection = selectTopics(topics, topicId);
@@ -177,7 +182,7 @@ export const runAnalyzeWorkflow = async ({
177182
}
178183

179184
try {
180-
const analysis = await analyzeTopicAsync(topic, force, context, activeLogger);
185+
const analysis = await analyzeTopicAsync(topic, force, semanticBias, context, activeLogger);
181186
if (!analysis) {
182187
const result: AnalyzeTopicResult = {
183188
topicId: topic.id,

0 commit comments

Comments
 (0)