@eeacms/volto-eea-chatbot 1.0.12 → 1.0.13

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,6 +4,18 @@ 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
+ ### [1.0.13](https://github.com/eea/volto-eea-chatbot/compare/1.0.12...1.0.13) - 4 March 2026
8
+
9
+ #### :house: Internal changes
10
+
11
+ - style: Automated code fix [eea-jenkins - [`8a8c3c4`](https://github.com/eea/volto-eea-chatbot/commit/8a8c3c4172a4f669661378cf1b5a3569d85609e6)]
12
+ - chore: [JENKINSFILE] add package version in sonarqube [valentinab25 - [`535d986`](https://github.com/eea/volto-eea-chatbot/commit/535d986b7adc77743a668bc4ac63f835eef58df3)]
13
+
14
+ #### :hammer_and_wrench: Others
15
+
16
+ - update [Miu Razvan - [`04e6c3f`](https://github.com/eea/volto-eea-chatbot/commit/04e6c3f776bb9920b89a014cc97f8e5dbb284a48)]
17
+ - update [Miu Razvan - [`f9e9beb`](https://github.com/eea/volto-eea-chatbot/commit/f9e9beb0676b215a50226a0db8c5be7c540f26ff)]
18
+ - Forward client ip to onyx/llmgw requests, ref #298095 [Miu Razvan - [`e90a672`](https://github.com/eea/volto-eea-chatbot/commit/e90a672273f2220d2cced4ad53c4b3ed3f295691)]
7
19
  ### [1.0.12](https://github.com/eea/volto-eea-chatbot/compare/1.0.11...1.0.12) - 23 February 2026
8
20
 
9
21
  #### :house: Internal changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-eea-chatbot",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
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",
@@ -13,7 +13,7 @@ const filterModel = {
13
13
  apiKey: LLMGW_API_KEY,
14
14
  };
15
15
 
16
- export async function callLLM(apiUrl, apiKey, requestBody) {
16
+ export async function callLLM(apiUrl, apiKey, requestBody, { ip } = {}) {
17
17
  const headers = {
18
18
  'Content-Type': 'application/json',
19
19
  accept: 'application/json',
@@ -21,6 +21,9 @@ export async function callLLM(apiUrl, apiKey, requestBody) {
21
21
  if (apiKey) {
22
22
  headers['Authorization'] = `Bearer ${apiKey}`;
23
23
  }
24
+ if (ip) {
25
+ headers['X-Forwarded-For'] = ip;
26
+ }
24
27
 
25
28
  const response = await fetch(apiUrl, {
26
29
  method: 'POST',
@@ -92,24 +95,26 @@ export function parseExcludeIndices(content, maxIndex) {
92
95
  return excludeIndices;
93
96
  }
94
97
 
95
- async function callFilterModel(prompt) {
98
+ async function callFilterModel(prompt, { ip } = {}) {
96
99
  const data = {
97
100
  messages: [{ role: 'user', content: prompt }],
98
101
  temperature: 0.0,
99
102
  model: filterModel.name,
100
103
  };
101
- const jsonData = await callLLM(filterModel.apiUrl, filterModel.apiKey, data);
104
+ const jsonData = await callLLM(filterModel.apiUrl, filterModel.apiKey, data, {
105
+ ip,
106
+ });
102
107
  return jsonData.choices?.[0]?.message?.content || '';
103
108
  }
104
109
 
105
- export async function excludeClaimSentences(sentences) {
110
+ export async function excludeClaimSentences(sentences, { ip } = {}) {
106
111
  if (sentences.length === 0) {
107
112
  return new Set();
108
113
  }
109
114
 
110
115
  try {
111
116
  const prompt = buildClaimFilterPrompt(sentences);
112
- const content = await callFilterModel(prompt);
117
+ const content = await callFilterModel(prompt, { ip });
113
118
  const excludedIndices = parseExcludeIndices(content, sentences.length);
114
119
  log('Claim filter response', excludedIndices.size);
115
120
  return excludedIndices;
@@ -122,6 +127,7 @@ export async function excludeClaimSentences(sentences) {
122
127
  export async function excludeContextSentences(
123
128
  contextSentences,
124
129
  claimSentences,
130
+ { ip } = {},
125
131
  ) {
126
132
  if (contextSentences.length <= MIN_CONTEXT_SENTENCES_FOR_FILTERING) {
127
133
  return new Set();
@@ -129,7 +135,7 @@ export async function excludeContextSentences(
129
135
 
130
136
  try {
131
137
  const prompt = buildContextFilterPrompt(contextSentences, claimSentences);
132
- const content = await callFilterModel(prompt);
138
+ const content = await callFilterModel(prompt, { ip });
133
139
  const excludedIndices = parseExcludeIndices(
134
140
  content,
135
141
  contextSentences.length,
@@ -52,7 +52,12 @@ function mergeChunkClaims(chunkResults) {
52
52
  return Array.from(claimMap.values());
53
53
  }
54
54
 
55
- export async function getVerifyClaimResponse(model, sources, answer) {
55
+ export async function getVerifyClaimResponse(
56
+ model,
57
+ sources,
58
+ answer,
59
+ { ip } = {},
60
+ ) {
56
61
  const emptyResponse = {
57
62
  claims: [],
58
63
  segments: {},
@@ -67,7 +72,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
67
72
 
68
73
  // Filter claims and context in parallel
69
74
  const [excludeResponseIndices] = await Promise.all([
70
- excludeClaimSentences(responseSentences),
75
+ excludeClaimSentences(responseSentences, { ip }),
71
76
  ]);
72
77
 
73
78
  const contextSentences = [];
@@ -109,7 +114,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
109
114
  const chunkResults = await Promise.all(
110
115
  prompts.map((chunkPrompt, i) => {
111
116
  log(`Chunk ${i + 1} request`);
112
- return halloumiGenerativeAPI(model, chunkPrompt);
117
+ return halloumiGenerativeAPI(model, chunkPrompt, { ip });
113
118
  }),
114
119
  );
115
120
 
@@ -166,7 +171,7 @@ export async function getVerifyClaimResponse(model, sources, answer) {
166
171
  * - `DUMP_HALLOUMI_REQ_FILE_PATH`: If set, the LLM request (URL and parameters) is dumped to the specified file path.
167
172
  * - `DUMP_HALLOUMI_FILE_PATH`: If set, the LLM response is dumped to the specified file path.
168
173
  */
169
- async function getLLMResponse(model, prompt) {
174
+ async function getLLMResponse(model, prompt, { ip } = {}) {
170
175
  let jsonData;
171
176
 
172
177
  if (process.env.MOCK_HALLOUMI_FILE_PATH) {
@@ -194,7 +199,7 @@ async function getLLMResponse(model, prompt) {
194
199
  log(`Dumped halloumi request: ${filePath}`);
195
200
  }
196
201
 
197
- jsonData = await callLLM(model.apiUrl, model.apiKey, data);
202
+ jsonData = await callLLM(model.apiUrl, model.apiKey, data, { ip });
198
203
 
199
204
  if (process.env.DUMP_HALLOUMI_FILE_PATH) {
200
205
  const filePath = process.env.DUMP_HALLOUMI_FILE_PATH;
@@ -210,8 +215,8 @@ async function getLLMResponse(model, prompt) {
210
215
  * @param response A string containing all claims and their information.
211
216
  * @returns A list of claim objects.
212
217
  */
213
- export async function halloumiGenerativeAPI(model, prompt) {
214
- const jsonData = await getLLMResponse(model, prompt);
218
+ export async function halloumiGenerativeAPI(model, prompt, { ip } = {}) {
219
+ const jsonData = await getLLMResponse(model, prompt, { ip });
215
220
 
216
221
  // Todo: restore log
217
222
  // log('Generative response', jsonData);
@@ -56,6 +56,7 @@ export default async function middleware(req, res, next) {
56
56
  // TODO: map with citation id
57
57
  sources,
58
58
  answer,
59
+ { ip: req.headers['x-forwarded-for'] || req.ip },
59
60
  );
60
61
  // log('Halloumi response', resp);
61
62
  res.send(resp);
package/src/middleware.js CHANGED
@@ -151,6 +151,7 @@ async function send_onyx_request(
151
151
  res,
152
152
  { username, password, api_key, url, is_related_question },
153
153
  ) {
154
+ const forwardedFor = req.headers['x-forwarded-for'] || req.ip;
154
155
  let headers = {};
155
156
  if (!api_key) {
156
157
  await login(username, password);
@@ -166,11 +167,13 @@ async function send_onyx_request(
166
167
  headers = {
167
168
  Cookie: cached_auth_cookie,
168
169
  'Content-Type': 'application/json',
170
+ 'X-Forwarded-For': forwardedFor,
169
171
  };
170
172
  } else {
171
173
  headers = {
172
174
  Authorization: 'Bearer ' + api_key,
173
175
  'Content-Type': 'application/json',
176
+ 'X-Forwarded-For': forwardedFor,
174
177
  };
175
178
  }
176
179
 
@@ -59,6 +59,8 @@ describe('src/middleware', () => {
59
59
  url: '/_da/chat/send-message',
60
60
  method: 'POST',
61
61
  body: { message: 'hello' },
62
+ ip: '127.0.0.1',
63
+ headers: {},
62
64
  };
63
65
  res = {
64
66
  send: jest.fn(),