@eeacms/volto-eea-chatbot 3.0.1 → 4.0.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/CHANGELOG.md CHANGED
@@ -4,7 +4,32 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
- ### [3.0.1](https://github.com/eea/volto-eea-chatbot/compare/3.0.0...3.0.1) - 5 August 2026
7
+ ### [4.0.0](https://github.com/eea/volto-eea-chatbot/compare/3.0.1...4.0.0) - 13 August 2026
8
+
9
+ #### :bug: Bug Fixes
10
+
11
+ - fix: correct 100.00% → High in ClaimModal snapshot [Tiberiu Ichim - [`2f3e620`](https://github.com/eea/volto-eea-chatbot/commit/2f3e620627faae3b050f0cb6ba1e3db4015dc5db)]
12
+ - fix: avoid Betterleaks false positive on LLM_API_KEY example [Tiberiu Ichim - [`44e05bc`](https://github.com/eea/volto-eea-chatbot/commit/44e05bc74a042e7f08cc0e6878930f9acc1964b9)]
13
+ - fix: filter skipped claims in span processor to prevent text duplication [Tiberiu Ichim - [`9477a42`](https://github.com/eea/volto-eea-chatbot/commit/9477a42d363d6961f4def628ed567a2409b316ea)]
14
+
15
+ #### :house: Internal changes
16
+
17
+ - chore: bump version to 4.0.0 (breaking change) [Tiberiu Ichim - [`e1038b6`](https://github.com/eea/volto-eea-chatbot/commit/e1038b6db64612f7c772a898b99ff802b3c133e9)]
18
+ - style: Automated code fix [eea-jenkins - [`03891e2`](https://github.com/eea/volto-eea-chatbot/commit/03891e24e48ccd0eccb55d4ed42aabbcdf34251f)]
19
+
20
+ #### :house: Documentation changes
21
+
22
+ - docs: document rag-facts-check backend dependency for quality checks [Tiberiu Ichim - [`123c7ea`](https://github.com/eea/volto-eea-chatbot/commit/123c7ea5d56ee48e6d5c787049a010b6bebd5aef)]
23
+ - docs: update README for rag-fact-checker integration [Tiberiu Ichim - [`4ae32b4`](https://github.com/eea/volto-eea-chatbot/commit/4ae32b41e838176d4a3ba1ea55367fc21e1a8cae)]
24
+
25
+ #### :hammer_and_wrench: Others
26
+
27
+ - test: update ClaimModal snapshots for categorical score labels [Tiberiu Ichim - [`3794be1`](https://github.com/eea/volto-eea-chatbot/commit/3794be1fc112b9f209a58c6577f99649350b1d59)]
28
+ - Deduplicate sources by text before sending to fact-checker [Tiberiu Ichim - [`67dc793`](https://github.com/eea/volto-eea-chatbot/commit/67dc793f955058d9ba5dbc92ea23a652e4586172)]
29
+ - Fix: use doc.content from final_documents when tool packets lack content [Tiberiu Ichim - [`4174212`](https://github.com/eea/volto-eea-chatbot/commit/4174212a90742c139ad2399f7a07feb99d619fbc)]
30
+ - Replace numeric claim scores with categorical labels [Tiberiu Ichim - [`d0aaf77`](https://github.com/eea/volto-eea-chatbot/commit/d0aaf779a69340386af81e9af4aa5147d93be388)]
31
+ - Send structured sources with document titles to fact-checker [Tiberiu Ichim - [`190c79b`](https://github.com/eea/volto-eea-chatbot/commit/190c79b2a888a7d655157e683633fd9901b8871c)]
32
+ ### [3.0.1](https://github.com/eea/volto-eea-chatbot/compare/3.0.0...3.0.1) - 7 August 2026
8
33
 
9
34
  #### :bug: Bug Fixes
10
35
 
@@ -35,7 +60,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
35
60
  #### :house: Internal changes
36
61
 
37
62
  - chore: bump version to 3.0.0 (EEA design system dependency) [skip ci] [Tiberiu Ichim - [`bc5a8c1`](https://github.com/eea/volto-eea-chatbot/commit/bc5a8c1f72d4e706b096cb96d0d24c2c3f5ac0f8)]
38
- - chore: [JENKINSFILE] sanitize sonarqube command Refs #305084 [valentinab25 - [`83bf08b`](https://github.com/eea/volto-eea-chatbot/commit/83bf08b7188548a50801b8e464a4d113f0cbbac4)]
39
63
 
40
64
  #### :house: Documentation changes
41
65
 
@@ -113,7 +137,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
113
137
  #### :house: Internal changes
114
138
 
115
139
  - style: Automated code fix [eea-jenkins - [`8a8c3c4`](https://github.com/eea/volto-eea-chatbot/commit/8a8c3c4172a4f669661378cf1b5a3569d85609e6)]
116
- - chore: [JENKINSFILE] add package version in sonarqube [valentinab25 - [`535d986`](https://github.com/eea/volto-eea-chatbot/commit/535d986b7adc77743a668bc4ac63f835eef58df3)]
117
140
 
118
141
  #### :hammer_and_wrench: Others
119
142
 
@@ -124,7 +147,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
124
147
  #### :house: Internal changes
125
148
 
126
149
  - style: Automated code fix [eea-jenkins - [`fdcd884`](https://github.com/eea/volto-eea-chatbot/commit/fdcd8848fd4c3f990ca5ea021f407487aebd6010)]
127
- - chore: [JENKINSFILE] use sonarqube branches [EEA Jenkins - [`3d428d7`](https://github.com/eea/volto-eea-chatbot/commit/3d428d72f32c3d05452b0961c76f5db1c416e05c)]
128
150
 
129
151
  #### :hammer_and_wrench: Others
130
152
 
package/README.md CHANGED
@@ -40,7 +40,7 @@ The **Volto Chatbot** block allows the integration of an AI-powered chatbot into
40
40
  | `enableShowTotalFailMessage` | Show total failure message. | Boolean | `false` |
41
41
  | `showAssistantTitle` | Display or hide the assistant's title in the chat interface. | Boolean | `true` |
42
42
  | `showAssistantDescription` | Display or hide the assistant's description in the chat interface. | Boolean | `true` |
43
- | `qualityCheck` | Show Halloumi-based automated quality check. | Dropdown | `Disabled` |
43
+ | `qualityCheck` | Show automated fact-checking of AI answers against source documents. | Dropdown | `Disabled` |
44
44
  | `onDemandInputToggle` | Sets the default state of the fact-check AI toggle. | Boolean | `true` |
45
45
  | `showTools` | Show or hide tools in the chat interface. | Array | `["internal_search_tool_start"]` |
46
46
  | `scrollToInput` | Automatically scroll the page to focus on the chat input when interacting. | Boolean | `false` |
@@ -116,6 +116,44 @@ Go to http://localhost:3000
116
116
 
117
117
  For a legacy Volto 17 project, install the package with `yarn` and restart the frontend as usual.
118
118
 
119
+ ## Quality Checks (Fact-Checking)
120
+
121
+ When `qualityCheck` is enabled, the chatbot sends AI answers and their source
122
+ documents to a fact-checking backend that extracts claims, verifies each
123
+ against the sources, and returns per-claim verdicts with evidence.
124
+
125
+ ### Backend dependency
126
+
127
+ The fact-checking feature requires the
128
+ [eea/rag-facts-check](https://github.com/eea/rag-facts-check) service running
129
+ and reachable at the URL configured in `RAG_FACT_CHECKER_URL`.
130
+
131
+ **Without this backend, quality checks will fail with a connection error.**
132
+
133
+ The backend exposes a halloumi-compatible endpoint (`POST /halloumi/generate`)
134
+ so the frontend can call it without code changes. It also provides a native
135
+ `POST /check` endpoint with a richer response schema.
136
+
137
+ ### Deploying the backend
138
+
139
+ ```bash
140
+ # Clone and build
141
+ git clone https://github.com/eea/rag-facts-check.git
142
+ cd rag-facts-check
143
+ docker build -t rag-fact-check .
144
+
145
+ # Run (requires an LLM endpoint)
146
+ # Set LLM_API_KEY to your actual API key before running
147
+ docker run -p 8000:8000 \
148
+ -e LLM_API_BASE=http://your-llm:4002/v1 \
149
+ -e LLM_API_KEY \
150
+ -e LLM_MODEL=gemma \
151
+ rag-fact-check
152
+ ```
153
+
154
+ See the [backend README](https://github.com/eea/rag-facts-check#rag-facts-check)
155
+ for full configuration options.
156
+
119
157
  ## Environment Configuration
120
158
 
121
159
  To properly configure the middleware and authenticate with the Onyx service, ensure that the following environment variables are set:
@@ -131,20 +169,13 @@ This document lists the environment variables used in the Volto Chatbot project.
131
169
  - `JEST_USE_SETUP`
132
170
  Used in Jest configuration. When set to 'ON', it enables a specific Jest setup.
133
171
 
134
- - `LLMGW_URL`
135
- The URL for the LLM Gateway service.
136
-
137
- - `LLMGW_TOKEN`
138
- The token for authenticating with the LLM Gateway service.
172
+ - `RAG_FACT_CHECKER_URL`
173
+ The base URL for the [rag-facts-check](https://github.com/eea/rag-facts-check)
174
+ fact-checking backend. Required when `qualityCheck` is enabled.
175
+ Default: `http://localhost:8000`.
139
176
 
140
177
  ### Development-specific environment variables
141
178
 
142
- - `MOCK_HALLOUMI_FILE_PATH`
143
- When set, this specifies the absolute path to the JSON file containing the mocked Halloumi response. Setting this variable enables mocking of Halloumi API calls.
144
-
145
- - `DUMP_HALLOUMI_FILE_PATH`
146
- When set, the Halloumi response will be dumped to the specified absolute file path for debugging or to create new mock files.
147
-
148
179
  - `MOCK_LLM_FILE_PATH`
149
180
  When set, this specifies the absolute path to the JSONL file containing the mocked Onyx stream response. Setting this variable enables mocking of Onyx LLM calls.
150
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "3.0.1",
3
+ "version": "4.0.0",
4
4
  "description": "@eeacms/volto-eea-chatbot: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -81,31 +81,21 @@ function addQualityMarkersPlugin() {
81
81
  };
82
82
  }
83
83
 
84
- export function addHalloumiContext(doc: any, text: string) {
85
- // TODO: CLEAN UP
86
- // const updatedDate = doc.updated_at
87
- // ? new Date(doc.updated_at).toLocaleString('en-GB', {
88
- // year: 'numeric',
89
- // month: 'long',
90
- // day: '2-digit',
91
- // hour: '2-digit',
92
- // minute: '2-digit',
93
- // })
94
- // : '';
95
-
96
- // const docIndex = doc.index ? `DOCUMENT ${doc.index}: ` : '';
97
- // const sources: any = { web: 'Website', file: 'File' };
98
-
99
- // const sourceType = doc.source_type
100
- // ? sources[doc.source_type] || capitalize(doc.source_type)
101
- // : '';
102
-
103
- // const header = `${docIndex}${doc.semantic_identifier}${
104
- // sourceType ? `\nSource: ${sourceType}` : ''
105
- // }${updatedDate ? `\nUpdated: ${updatedDate}` : ''}`;
106
-
107
- // return `${header}\n${text}`;
108
- return text.replace(/\u00A0/g, ' ');
84
+ /**
85
+ * Build a structured source object for the fact-checker backend.
86
+ *
87
+ * The backend accepts {text, title, source_type} dicts so it can
88
+ * include document titles in LLM prompts without polluting the raw
89
+ * text (which would break span-offset matching).
90
+ */
91
+ export function buildHalloumiSource(doc: any, text: string) {
92
+ const cleanedText = text.replace(/\u00A0/g, ' ');
93
+ return {
94
+ text: cleanedText,
95
+ title: doc.semantic_identifier || null,
96
+ source_type: doc.source_type || null,
97
+ link: doc.link || null,
98
+ };
109
99
  }
110
100
 
111
101
  function mapToolDocumentsToText(message: any) {
@@ -135,24 +125,36 @@ function getContextSources(
135
125
  const documentIdToText = mapToolDocumentsToText(message);
136
126
 
137
127
  return qualityCheckContext === 'citations'
138
- ? sources.map((doc: any) => ({
139
- ...doc,
140
- id: doc.document_id,
141
- text: documentIdToText[doc.document_id] || '',
142
- halloumiContext: addHalloumiContext(
143
- doc,
144
- documentIdToText[doc.document_id] || '',
145
- ),
146
- }))
128
+ ? sources.map((doc: any) => {
129
+ // Prefer content from tool packets (may have enriched text),
130
+ // but fall back to doc.content from final_documents.
131
+ // Without this fallback, sources sent to the fact-checker are
132
+ // empty strings when tool packets don't carry full content.
133
+ const text = documentIdToText[doc.document_id] || doc.content || '';
134
+ const cleanedText = text.replace(/\u00A0/g, ' ');
135
+ return {
136
+ ...doc,
137
+ id: doc.document_id,
138
+ text,
139
+ // Keep for ClaimSegments span highlighting (plain text)
140
+ halloumiContext: cleanedText,
141
+ // Structured source for the fact-checker backend
142
+ halloumiSource: buildHalloumiSource(doc, text),
143
+ };
144
+ })
147
145
  : (message.toolCalls || []).reduce(
148
146
  (acc: any, cur: any) => [
149
147
  ...acc,
150
- ...(cur.tool_result || []).map((doc: any) => ({
151
- ...doc,
152
- id: doc.document_id,
153
- text: doc.content,
154
- halloumiContext: addHalloumiContext(doc, doc.content),
155
- })),
148
+ ...(cur.tool_result || []).map((doc: any) => {
149
+ const cleanedText = (doc.content || '').replace(/\u00A0/g, ' ');
150
+ return {
151
+ ...doc,
152
+ id: doc.document_id,
153
+ text: doc.content,
154
+ halloumiContext: cleanedText,
155
+ halloumiSource: buildHalloumiSource(doc, doc.content || ''),
156
+ };
157
+ }),
156
158
  ], // TODO: make sure we don't add multiple times the same doc
157
159
  // TODO: this doesn't have the index for source
158
160
  [],
@@ -281,7 +283,24 @@ export function AIMessage({
281
283
  qualityCheckContext,
282
284
  );
283
285
 
284
- const stableContextSources = useDeepCompareMemoize(contextSources);
286
+ // Deduplicate sources by text content. The streaming backend often sends
287
+ // the same document in multiple packets (MESSAGE_START, SEARCH_TOOL_DELTA,
288
+ // etc.), resulting in 40+ copies of the same text. Dedup keeps the first
289
+ // occurrence, preserving order. Critical: texts must remain identical
290
+ // between frontend (span highlighting) and backend (evidence spans).
291
+ const dedupedSources = useMemo(() => {
292
+ const seen = new Set<string>();
293
+ return contextSources.filter((src: any) => {
294
+ const key = src.halloumiContext || src.halloumiSource?.text || '';
295
+ if (key && !seen.has(key)) {
296
+ seen.add(key);
297
+ return true;
298
+ }
299
+ return false;
300
+ });
301
+ }, [contextSources]);
302
+
303
+ const stableContextSources = useDeepCompareMemoize(dedupedSources);
285
304
 
286
305
  const doQualityControl =
287
306
  messageDisplayed &&
@@ -1,6 +1,6 @@
1
1
  import { Modal, ModalContent, ModalHeader } from 'semantic-ui-react';
2
2
  import cx from 'classnames';
3
- import { convertToPercentage } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
3
+ import { scoreToLabel } from '@eeacms/volto-eea-chatbot/ChatBlock/utils';
4
4
  import SVGIcon from '@eeacms/volto-eea-chatbot/ChatBlock/components/Icon';
5
5
  import { getSupportedBgColor } from './colors';
6
6
  import { ClaimSegments } from './ClaimSegments';
@@ -70,9 +70,9 @@ export function ClaimModal({ claim, markers, text, citedSources }) {
70
70
  <div className="score-badge-section">
71
71
  <div className="score-badge">
72
72
  <span className="score-percentage">
73
- {convertToPercentage(claim.score)}
73
+ {scoreToLabel(claim.score)}
74
74
  </span>
75
- <span className="score-label">Citation Support</span>
75
+ <span className="score-label">Verification</span>
76
76
  </div>
77
77
  <div className="score-progress-bar">
78
78
  <div
@@ -43,6 +43,7 @@ export function components(message, markers, citedSources) {
43
43
  const end = child.position.end.offset;
44
44
  const claims = markers.claims?.filter(
45
45
  (claim) =>
46
+ !claim.skipped &&
46
47
  claim.score !== null &&
47
48
  ((start >= claim.startOffset && end <= claim.endOffset) ||
48
49
  (start <= claim.endOffset && end >= claim.endOffset) ||
@@ -48,7 +48,16 @@ export function useQualityMarkers(
48
48
 
49
49
  React.useEffect(() => {
50
50
  async function handler() {
51
- const textSources = sources.map(({ halloumiContext }) => halloumiContext);
51
+ // Prefer structured sources (with title metadata) over plain text.
52
+ // Fall back to halloumiContext strings for backward compatibility.
53
+ const firstSource = sources[0];
54
+ const hasStructuredSources =
55
+ firstSource && typeof firstSource.halloumiSource === 'object';
56
+
57
+ const halloumiSources = hasStructuredSources
58
+ ? sources.map(({ halloumiSource }) => halloumiSource)
59
+ : sources.map(({ halloumiContext }) => halloumiContext);
60
+
52
61
  if (sources.length === 0) {
53
62
  setHalloumiResponse(empty(message, FAILURE_RATIONALE));
54
63
  return;
@@ -59,7 +68,7 @@ export function useQualityMarkers(
59
68
  try {
60
69
  const feedback = await fetchHalloumi(
61
70
  message,
62
- textSources,
71
+ halloumiSources,
63
72
  maxContextSegments,
64
73
  );
65
74
  const body = await feedback.json();
@@ -211,8 +211,9 @@ export class MessageProcessor {
211
211
  this._documents = Array.from(this.documentMap.values());
212
212
  }
213
213
 
214
- // If we have final_documents and no citations yet, create a fallback mapping
215
- // This ensures the Sources tab shows up in v3 when citation_info is missing
214
+ // If we have final_documents and no citations yet, create a fallback mapping.
215
+ // This ensures the Sources tab shows up in v3 when citation_info is missing.
216
+ // The fallback is cleared in processCitations() when real citation_info arrives.
216
217
  if (
217
218
  packet.obj.type === PacketType.MESSAGE_START &&
218
219
  data.final_documents &&
@@ -240,9 +241,12 @@ export class MessageProcessor {
240
241
  */
241
242
  private processCitations(packet: Packet) {
242
243
  if (packet.obj.type === PacketType.CITATION_INFO) {
244
+ // First real citation_info arrived — discard the fallback mapping
245
+ // (which mapped ALL final_documents) and start fresh with actual citations.
246
+ this.clearFallbackCitations();
247
+
243
248
  const citationInfo = packet.obj as any;
244
249
  if (citationInfo.citation_number && citationInfo.document_id) {
245
- this.clearFallbackCitations();
246
250
  this._citations.set(
247
251
  citationInfo.citation_number,
248
252
  citationInfo.document_id,
@@ -58,6 +58,16 @@ export const useCopyToClipboard = (text: string): [boolean, () => void] => {
58
58
  return [copied, copy];
59
59
  };
60
60
 
61
+ /**
62
+ * Map a 0-1 claim score to a categorical label.
63
+ * Scores: 1.0 = supported, 0.4 = not_enough_info, 0.0 = contradicted.
64
+ */
65
+ export function scoreToLabel(score: number): string {
66
+ if (score >= 0.8) return 'High';
67
+ if (score >= 0.1) return 'Low';
68
+ return 'Failed';
69
+ }
70
+
61
71
  export function convertToPercentage(
62
72
  floatValue: number,
63
73
  digits: number = 2,
@@ -1 +1,27 @@
1
- code based on Apache2 licensed https://github.com/oumi-ai/halloumi-demo/tree/d088e1f25e7785326a53bc120113e226ee2f54b7
1
+ # Halloumi Fact-Checking Middleware
2
+
3
+ This middleware proxies fact-checking requests to the **rag-fact-checker** service (https://github.com/eea/rag-fact-check), which replaces the original Halloumi LLM-based integration.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ Frontend (useQualityMarkers.js)
9
+ → POST /_ha/generate
10
+ → Express middleware (middleware.js)
11
+ → POST {RAG_FACT_CHECKER_URL}/halloumi/generate
12
+ → rag-fact-checker FastAPI service
13
+ ```
14
+
15
+ The rag-fact-checker's `/halloumi/generate` endpoint is a drop-in replacement for the original Halloumi API — it accepts the same request format (`{answer, sources}`) and returns a compatible response (`{claims, segments}`), so all existing frontend components (`HalloumiFeedback`, `useQualityMarkers`, `ClaimSegments`) work without changes.
16
+
17
+ ## Configuration
18
+
19
+ | Environment Variable | Default | Description |
20
+ | ---------------------- | ----------------------- | ---------------------------------------- |
21
+ | `RAG_FACT_CHECKER_URL` | `http://localhost:8000` | Base URL of the rag-fact-checker service |
22
+
23
+ ## Original Halloumi
24
+
25
+ The original implementation was based on Apache2 licensed https://github.com/oumi-ai/halloumi-demo/tree/d088e1f25e7785326a53bc120113e226ee2f54b7
26
+
27
+ It used a custom LLM model (HallOumi-8B) accessed via an LLM gateway, with complex prompt preprocessing, chunking, logprob-based postprocessing, and claim filtering. This has been replaced by the rag-fact-checker service which uses a claim extraction + per-claim verification pipeline.
@@ -1,41 +1,18 @@
1
- // import debug from 'debug';
2
- import { getVerifyClaimResponse } from './generative';
1
+ import fetch from 'node-fetch';
2
+ import debug from 'debug';
3
3
  import { isPathAllowed } from '../middleware';
4
4
 
5
- // const log = debug('halloumi');
5
+ const log = debug('halloumi');
6
6
 
7
7
  const MSG_INVALID_CONFIGURATION =
8
- 'Invalid configuration: missing LLMGW_TOKEN or LLMGW_URL';
8
+ 'Invalid configuration: missing RAG_FACT_CHECKER_URL';
9
9
 
10
10
  // Allowed paths for _ha (Halloumi) proxy.
11
11
  // When adding new endpoints, update this list.
12
- const ALLOWED_HALLOUMI_PATHS = [
13
- { path: '/generate', methods: ['POST'] },
14
- { path: '/classify', methods: ['POST'] },
15
- ];
12
+ const ALLOWED_HALLOUMI_PATHS = [{ path: '/generate', methods: ['POST'] }];
16
13
 
17
- const LLMGW_URL = process.env.LLMGW_URL;
18
- const LLMGW_TOKEN = process.env.LLMGW_TOKEN; //betterleaks:allow
19
-
20
- const generativeModel = {
21
- name: 'Inhouse-LLM/HallOumi-8B',
22
- apiUrl: `${LLMGW_URL}/chat/completions`,
23
- plattScaling: {
24
- a: -0.5764390035379638,
25
- b: 0.16648741572432335,
26
- },
27
- isEmbeddingModel: false,
28
- };
29
-
30
- const classifyModel = {
31
- name: 'Inhouse-LLM/HallOumi-8B-classifier',
32
- apiUrl: `${LLMGW_URL}/v1/embeddings`,
33
- isEmbeddingModel: true,
34
- plattScaling: {
35
- a: -0.9468640744087437,
36
- b: -0.07379217647931409,
37
- },
38
- };
14
+ const RAG_FACT_CHECKER_URL =
15
+ process.env.RAG_FACT_CHECKER_URL || 'http://localhost:8000';
39
16
 
40
17
  export default async function middleware(req, res, next) {
41
18
  const path = req.url.replace('/_ha/', '/');
@@ -48,39 +25,52 @@ export default async function middleware(req, res, next) {
48
25
  return;
49
26
  }
50
27
 
51
- if (!(LLMGW_TOKEN && LLMGW_URL)) {
28
+ if (!RAG_FACT_CHECKER_URL) {
52
29
  res.send({
53
30
  error: MSG_INVALID_CONFIGURATION,
54
31
  });
55
32
  return;
56
33
  }
57
34
 
58
- const model = {
59
- ...(path === '/classify' ? classifyModel : generativeModel),
60
- apiKey: LLMGW_TOKEN, //betterleaks:allow
61
- };
62
35
  const body = req.body;
63
-
64
- // log('Halloumi body', body);
65
36
  const { sources, answer } = body;
66
37
 
38
+ log('Halloumi request body', {
39
+ answer,
40
+ sources: sources?.length,
41
+ });
42
+
67
43
  res.set('Content-Type', 'application/json');
68
44
 
69
45
  try {
70
- const resp = await getVerifyClaimResponse(
71
- model,
72
- // TODO: map with citation id
73
- sources,
74
- answer,
75
- { ip: req.headers['x-forwarded-for'] || req.ip },
76
- );
77
- // log('Halloumi response', resp);
78
- res.send(resp);
46
+ const response = await fetch(`${RAG_FACT_CHECKER_URL}/halloumi/generate`, {
47
+ method: 'POST',
48
+ headers: {
49
+ 'Content-Type': 'application/json',
50
+ },
51
+ body: JSON.stringify({
52
+ answer,
53
+ sources,
54
+ max_context_segments: 0,
55
+ }),
56
+ });
57
+
58
+ const result = await response.json();
59
+
60
+ if (!response.ok) {
61
+ log('Halloumi error response', result);
62
+ res.status(response.status).send({
63
+ error: `Fact-checker error: ${result.detail || result.error || 'unknown error'}`,
64
+ });
65
+ return;
66
+ }
67
+
68
+ log('Halloumi response', result);
69
+ res.send(result);
79
70
  } catch (error) {
80
- res.status(500).send({
81
- error: `Halloumi error: ${error}`,
82
- traceback: error.stack || null,
71
+ log('Halloumi fetch error', error);
72
+ res.status(502).send({
73
+ error: `Fact-checker unavailable: ${error.message || 'connection failed'}`,
83
74
  });
84
- // throw error;
85
75
  }
86
76
  }