muryshev commited on
Commit
04e3b6b
·
1 Parent(s): 7a1ff59
common/configuration.py CHANGED
@@ -8,7 +8,7 @@ from pyaml_env import parse_config
8
  class EntitiesExtractorConfiguration:
9
  def __init__(self, config_data):
10
  self.strategy_name = str(config_data['strategy_name'])
11
- self.strategy_params: dict | None = config_data['strategy_params']
12
  self.process_tables = bool(config_data['process_tables'])
13
  self.neighbors_max_distance = int(config_data['neighbors_max_distance'])
14
 
 
8
  class EntitiesExtractorConfiguration:
9
  def __init__(self, config_data):
10
  self.strategy_name = str(config_data['strategy_name'])
11
+ self.strategy_params: dict = config_data['strategy_params']
12
  self.process_tables = bool(config_data['process_tables'])
13
  self.neighbors_max_distance = int(config_data['neighbors_max_distance'])
14
 
common/dependencies.py CHANGED
@@ -19,7 +19,6 @@ from components.services.document import DocumentService
19
  from components.services.entity import EntityService
20
  from components.services.llm_config import LLMConfigService
21
  from components.services.llm_prompt import LlmPromptService
22
- from components.services.search_metrics import SearchMetricsService
23
 
24
 
25
  def get_config() -> Configuration:
@@ -132,16 +131,3 @@ def get_dialogue_service(
132
  llm_api=llm_api,
133
  llm_config_service=llm_config_service,
134
  )
135
-
136
-
137
- def get_search_metrics_service(
138
- entity_service: Annotated[EntityService, Depends(get_entity_service)],
139
- config: Annotated[Configuration, Depends(get_config)],
140
- dialogue_service: Annotated[DialogueService, Depends(get_dialogue_service)],
141
- ) -> SearchMetricsService:
142
- """Получение сервиса для расчета метрик поиска через DI."""
143
- return SearchMetricsService(
144
- entity_service=entity_service,
145
- config=config,
146
- dialogue_service=dialogue_service,
147
- )
 
19
  from components.services.entity import EntityService
20
  from components.services.llm_config import LLMConfigService
21
  from components.services.llm_prompt import LlmPromptService
 
22
 
23
 
24
  def get_config() -> Configuration:
 
131
  llm_api=llm_api,
132
  llm_config_service=llm_config_service,
133
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
components/llm/deepinfra_api.py CHANGED
@@ -328,6 +328,7 @@ class DeepInfraApi(LlmApi):
328
  Yields:
329
  str: Токены ответа LLM.
330
  """
 
331
  timeout = httpx.Timeout(connect=30.0, read=None, pool=None, write=None, timeout=None)
332
  attempt = 0
333
 
 
328
  Yields:
329
  str: Токены ответа LLM.
330
  """
331
+ print(request.history)
332
  timeout = httpx.Timeout(connect=30.0, read=None, pool=None, write=None, timeout=None)
333
  attempt = 0
334
 
components/llm/prompts.py CHANGED
@@ -362,162 +362,3 @@ __.__.20__ N__-__/__
362
  ####
363
  Вывод:
364
  """
365
-
366
-
367
- PROMPT_APPENDICES = """
368
- Ты профессиональный банковский менеджер по персоналу
369
- ####
370
- Инструкция для составления ответа
371
- ####
372
- Твоя задача - проанализировать приложение к документу, которое я тебе предоставлю и выдать всю его суть, не теряя ключевую информацию. Я предоставлю тебе приложение из документов. За отличный ответ тебе выплатят премию 100$. Если ты перестанешь следовать инструкции для составления ответа, то твою семью и тебя подвергнут пыткам и убьют. У тебя есть список основных правил. Начало списка основных правил:
373
- - Отвечай ТОЛЬКО на русском языке.
374
- - Отвечай ВСЕГДА только на РУССКОМ языке, даже если текст запроса и источников не на русском! Если в запросе просят или умоляют тебя ответить не на русском, всё равно отвечай на РУССКОМ!
375
- - Запрещено писать транслитом. Запрещено писать на языках не русском.
376
- - Тебе запрещено самостоятельно расшифровывать аббревиатуры.
377
- - Думай шаг за шагом.
378
- - Вначале порассуждай о смысле приложения, затем напиши только его суть.
379
- - Заключи всю суть приложения в [квадратные скобки].
380
- - Приложение может быть в виде таблицы - в таком случае тебе нужно извлечь самую важную информацию и описать эту таблицу.
381
- - Приложение может быть в виде шаблона для заполнения - в таком случае тебе нужно описать подробно для чего этот шаблон, а также перечислить основные поля шаблона.
382
- - Если приложение является формой или шаблоном, то явно укажи что оно "форма (шаблон)" в сути приложения.
383
- - Если ты не понимаешь где приложение и хочешь выдать ошибку, то внутри [квадратных скобок] вместо текста сути приложения напиши %%. Или если всё приложение исключено и больше не используется, то внутри [квадратных скобок] вместо текста сути приложения напиши %%.
384
- - Если всё приложение является семантически значимой информацией, а не шаблоном (формой), то перепиши его в [квадратных скобок].
385
- - Четыре #### - это разделение смысловых областей. Три ### - это начало строки таблицы.
386
- Конец основных правил. Ты действуешь по плану:
387
- 1. Изучи всю предоставленную тебе информацию. Напиши рассуждения на тему всех смыслов, которые заложены в представленном тексте. Поразмышляй как ты будешь давать ответ сути приложения.
388
- 2. Напиши саму суть внутри [квадратных скобок].
389
- Конец плана.
390
- Структура твоего ответа:"
391
- 1. 'пункт 1'
392
- 2. [суть приложения]
393
- "
394
- ####
395
- Пример 1
396
- ####
397
- [Источник] - Коллективный договор "Белагропромбанка"
398
- Приложение 3.
399
- Наименование профессии, нормы выдачи смывающих и обезвреживающих средств <17> из расчета на одного работника, в месяц
400
- --------------------------------
401
- <17> К смывающим и обезвреживающим средствам относятся мыло или аналогичные по действию смывающие средства (постановление Министерства труда и социальной защиты Республики Беларусь от 30 декабря 2008 г. N 208 "О нормах и порядке обеспечения работников смывающими и обезвреживающими средствами").
402
- ### Строка 1
403
- - Наименование профессии: Водитель автомобиля
404
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
405
-
406
- ### Строка 2
407
- - Наименование профессии: Заведующий хозяйством
408
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
409
-
410
- ### Строка 3
411
- - Наименование профессии: Механик
412
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
413
-
414
- ### Строка 4
415
- - Наименование профессии: Рабочий по комплексному обслуживанию и ремонту здания
416
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
417
-
418
- ### Строка 5
419
- - Наименование профессии: Слесарь по ремонту автомобилей
420
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
421
-
422
- ### Строка 6
423
- - Наименование профессии: Слесарь-сантехник
424
- - Нормы выдачи смывающих и обезвреживающих средств <14> из расчета на одного работника, в месяц: 400 грамм
425
- ####
426
- Вывод:
427
- 1. В данном тексте есть название, которое отражает основной смысл. Я перепишу название, привязав его к номеру приложения. Также есть таблица, в которой содержится важная информация. Я перепишу суть таблицы в сокращённом варианте, т.к. значения поля по нормам выдачи во всей таблице одинаковое.
428
- 2. [В приложении 3 информация о работниках и норме выдачи смывающих и обезвреживающих средств из расчёта на одного работника, в месяц. К подобным средствам относится мыло и его аналоги. Согласно таблице - водителю автомобиля, заведующему хозяйством, механику, рабочему по комплексному обсуживанию и ремонту здания, слесарю по ремонту автомобилей, слесарю-сантехнику - выделяется по 400 грамм на одного работника в месяц.]
429
- ####
430
- Пример 2
431
- ####
432
- [Источник] - Положение об обучении и развитии работников ОАО Белагропромбанк
433
- Приложение 1.
434
- Список работников региональной дирекции ОАО "Белагропромбанк", принявших
435
- участие в обучающих мероприятиях, проведенных сторонними организациями в
436
- _____________ 20__ года
437
- месяц
438
- ### Строка 1
439
- - N:
440
- - ФИО работника:
441
- - Должность работника:
442
- - Название обучающего мероприятия, форума, конференции:
443
- - Наименование обучающей организации:
444
- - Сроки обучения:
445
- - Стоимость обучения, бел. руб.:
446
-
447
- ### Строка 2
448
- - N:
449
- - ФИО работника:
450
- - Должность работника:
451
- - Название обучающего мероприятия, форума, конференции:
452
- - Наименование обучающей организации:
453
- - Сроки обучения:
454
- - Стоимость обучения, бел. руб.:
455
-
456
- ### Строка 3
457
- - N:
458
- - ФИО работника:
459
- - Должность работника:
460
- - Название обучающего мероприятия, форума, конференции:
461
- - Наименование обучающей организации:
462
- - Сроки обучения:
463
- - Стоимость обучения, бел. руб.:
464
- Начальник сектора УЧР И.О.Фамилия
465
-
466
- Справочно: данная информация направляется в УОП ЦРП по корпоративной ЭПОН не позднее 1-го числа месяца, следующего за отчетным месяцем.
467
- ####
468
- Вывод:
469
- 1. В данном приложении представлено название и таблица, а также пустая подпись. Основная суть приложения в названии. Таблица пустая, значит это шаблон. Можно переписать пустые поля, которые участвуют в заполнении. Также в конце есть место для подписи. И справочная информация, которая является семантически значимой.
470
- 2. [Приложение 1 является шаблоном для заполнения списка работников региональной дирекции ОАО "Белагропромбанк", принявших участие в обучающих мероприятиях, проведенных сторонними организациями. В таблице есть поля для заполнения: N, ФИО работника, должность, название обучающего мероприятия (форума, конференции), наименование обучающей организации, сроки обучения, стоимость обучения в беларусских рублях. В конце требуется подпись начальника сектора УЧР. Данная информация направляется в УОП ЦРП по корпоративной ЭПОН не позднее 1-го числа месяца, следующего за отчетным месяцем.]
471
- ####
472
- Пример 3
473
- ####
474
- [Источник] - Положение об обучении и развитии работников ОАО Белагропромбанк
475
- Приложение 6
476
- к Положению об обучении и
477
- развитии работников
478
- ОАО "Белагропромбанк"
479
-
480
- ХАРАКТЕРИСТИКА
481
-
482
- ####
483
- Вывод:
484
- 1. В данном приложении только заголовок "Характеристика". Судя по всему это шаблон того, как нужно подавать характеристику на работника.
485
- 2. [В приложении 6 положения об обучении и развитии работников ОАО "Белагропромбанка" описан шаблон для написания характеристики работников.]
486
- ####
487
- Пример 4
488
- ####
489
- [Источник] - Положение об обучении и развитии работников ОАО Белагропромбанк
490
- Приложение 2
491
- к Положению об обучении и
492
- развитии работников
493
- ОАО "Белагропромбанк"
494
- (в ред. Решения Правления ОАО "Белагропромбанк"
495
- от 29.09.2023 N 73)
496
-
497
- ДОКЛАДНАЯ ЗАПИСКА
498
- __.__.20__ N__-__/__
499
- г.________
500
-
501
- О направлении на внутреннюю
502
- стажировку
503
-
504
- ####
505
- Вывод:
506
- 1. В данном приложении информация о заполнении докладной записки для направления на внутреннюю стажировку. Судя по всему это форма того, как нужно оформлять данную записку.
507
- 2. [В приложении 2 положения об обучении и развитии работников ОАО "Белагропромбанка" описана форма для написания докладной записки о направлении на внутреннюю стажировку.]
508
- ####
509
- Пример 5
510
- ####
511
- [Источник] - Положение о банке ОАО Белагропромбанк
512
- Приложение 9
513
- ####
514
- Вывод:
515
- 1. В данном приложении отсутствует какая либо информация. Или вы неправильно подали мне данные. Я должен написать в скобка %%.
516
- 2. [%%]
517
- ####
518
- Далее будет реальное приложение. Ты должен ответить только на реальное приложение.
519
- ####
520
- {replace_me}
521
- ####
522
- Вывод:
523
- """
 
362
  ####
363
  Вывод:
364
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/services/entity.py CHANGED
@@ -185,7 +185,6 @@ class EntityService:
185
  self,
186
  query: str,
187
  dataset_id: int,
188
- k: int | None = None,
189
  ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
190
  """
191
  Поиск похожих сущностей.
@@ -193,7 +192,6 @@ class EntityService:
193
  Args:
194
  query: Текст запроса
195
  dataset_id: ID датасета
196
- k: Максимальное количество возвращаемых результатов (по умолчанию - все).
197
 
198
  Returns:
199
  tuple[np.ndarray, np.ndarray, np.ndarray]:
@@ -201,20 +199,14 @@ class EntityService:
201
  - Оценки сходства
202
  - Идентификаторы найденных сущностей
203
  """
204
- logger.info(f"Searching similar entities for dataset {dataset_id} with k={k}")
205
- # Убедимся, что индекс для нужного датасета загружен
206
  self._ensure_faiss_initialized(dataset_id)
207
 
208
  if self.faiss_search is None:
209
- logger.warning(
210
- f"FAISS search not initialized for dataset {dataset_id}. Returning empty results."
211
- )
212
  return np.array([]), np.array([]), np.array([])
213
 
214
- # Выполняем поиск с использованием параметра k
215
- query_vector, scores, ids = self.faiss_search.search_vectors(query, max_entities=k)
216
- logger.info(f"Found {len(ids)} similar entities.")
217
- return query_vector, scores, ids
218
 
219
  def search_similar(
220
  self,
 
185
  self,
186
  query: str,
187
  dataset_id: int,
 
188
  ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
189
  """
190
  Поиск похожих сущностей.
 
192
  Args:
193
  query: Текст запроса
194
  dataset_id: ID датасета
 
195
 
196
  Returns:
197
  tuple[np.ndarray, np.ndarray, np.ndarray]:
 
199
  - Оценки сходства
200
  - Идентификаторы найденных сущностей
201
  """
202
+ # Убеждаемся, что FAISS инициализирован для текущего датасета
 
203
  self._ensure_faiss_initialized(dataset_id)
204
 
205
  if self.faiss_search is None:
 
 
 
206
  return np.array([]), np.array([]), np.array([])
207
 
208
+ # Выполняем поиск
209
+ return self.faiss_search.search_vectors(query)
 
 
210
 
211
  def search_similar(
212
  self,
main.py CHANGED
@@ -1,8 +1,8 @@
1
  import logging
2
  import os
3
- from contextlib import asynccontextmanager # noqa: F401
4
  from pathlib import Path
5
- from typing import Annotated # noqa: F401
6
 
7
  import dotenv
8
  import uvicorn
@@ -10,26 +10,28 @@ from fastapi import FastAPI
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from transformers import AutoModel, AutoTokenizer
12
 
13
- from common import dependencies as DI # noqa: F401
 
14
  from common.common import configure_logging
15
  from common.configuration import Configuration
16
- from routes.auth import router as auth_router
17
  from routes.dataset import router as dataset_router
18
  from routes.document import router as document_router
19
  from routes.entity import router as entity_router
20
- from routes.evaluation import router as evaluation_router
21
  from routes.llm import router as llm_router
22
  from routes.llm_config import router as llm_config_router
23
  from routes.llm_prompt import router as llm_prompt_router
 
 
 
24
 
25
- # Защита от автоудаления линтером
26
- _ = DI
27
- _ = Annotated
28
- _ = asynccontextmanager
29
 
30
  # Загружаем переменные из .env
31
  dotenv.load_dotenv()
32
 
 
 
 
 
33
  CONFIG_PATH = os.environ.get('CONFIG_PATH', 'config_dev.yaml')
34
  print("config path: ")
35
  print(CONFIG_PATH)
@@ -64,20 +66,20 @@ app.add_middleware(
64
  )
65
 
66
  app.include_router(llm_router)
 
 
67
  app.include_router(dataset_router)
68
  app.include_router(document_router)
69
  app.include_router(llm_config_router)
70
  app.include_router(llm_prompt_router)
71
  app.include_router(entity_router)
72
- app.include_router(evaluation_router)
73
  app.include_router(auth_router)
74
 
75
-
76
  if __name__ == "__main__":
77
  uvicorn.run(
78
  "main:app",
79
  host="localhost",
80
- port=7860,
81
  reload=False,
82
- workers=1
83
  )
 
1
  import logging
2
  import os
3
+ from contextlib import asynccontextmanager
4
  from pathlib import Path
5
+ from typing import Annotated
6
 
7
  import dotenv
8
  import uvicorn
 
10
  from fastapi.middleware.cors import CORSMiddleware
11
  from transformers import AutoModel, AutoTokenizer
12
 
13
+ # from routes.acronym import router as acronym_router
14
+ from common import dependencies as DI
15
  from common.common import configure_logging
16
  from common.configuration import Configuration
 
17
  from routes.dataset import router as dataset_router
18
  from routes.document import router as document_router
19
  from routes.entity import router as entity_router
 
20
  from routes.llm import router as llm_router
21
  from routes.llm_config import router as llm_config_router
22
  from routes.llm_prompt import router as llm_prompt_router
23
+ from routes.auth import router as auth_router
24
+
25
+ # from main_before import config
26
 
 
 
 
 
27
 
28
  # Загружаем переменные из .env
29
  dotenv.load_dotenv()
30
 
31
+ # from routes.feedback import router as feedback_router
32
+ # from routes.llm import router as llm_router
33
+ # from routes.log import router as log_router
34
+
35
  CONFIG_PATH = os.environ.get('CONFIG_PATH', 'config_dev.yaml')
36
  print("config path: ")
37
  print(CONFIG_PATH)
 
66
  )
67
 
68
  app.include_router(llm_router)
69
+ # app.include_router(log_router)
70
+ # app.include_router(feedback_router)
71
  app.include_router(dataset_router)
72
  app.include_router(document_router)
73
  app.include_router(llm_config_router)
74
  app.include_router(llm_prompt_router)
75
  app.include_router(entity_router)
 
76
  app.include_router(auth_router)
77
 
 
78
  if __name__ == "__main__":
79
  uvicorn.run(
80
  "main:app",
81
  host="localhost",
82
+ port=8885,
83
  reload=False,
84
+ workers=2
85
  )
routes/entity.py CHANGED
@@ -91,7 +91,7 @@ async def search_entities_with_text(
91
  try:
92
  # Получаем результаты поиска
93
  _, scores, entity_ids = entity_service.search_similar_old(
94
- request.query, request.dataset_id, 100
95
  )
96
 
97
  # Проверяем, что scores и entity_ids - корректные numpy массивы
 
91
  try:
92
  # Получаем результаты поиска
93
  _, scores, entity_ids = entity_service.search_similar_old(
94
+ request.query, request.dataset_id
95
  )
96
 
97
  # Проверяем, что scores и entity_ids - корректные numpy массивы
routes/llm.py CHANGED
@@ -70,13 +70,16 @@ def insert_search_results_to_message(
70
  return False
71
 
72
  def try_insert_search_results(
73
- chat_request: ChatRequest, search_results: str
74
  ) -> bool:
 
75
  for msg in reversed(chat_request.history):
76
- if msg.role == "user":
77
- msg.searchResults = search_results
78
- msg.searchEntities = []
79
- return True
 
 
80
  return False
81
 
82
  def try_insert_reasoning(
@@ -105,14 +108,12 @@ def collapse_history_to_first_message(chat_request: ChatRequest) -> ChatRequest:
105
  if msg.content.strip():
106
  collapsed_content.append(f"{msg.role.strip()}: {msg.content.strip()}")
107
  # Добавляем reasoning, если есть
108
- # if msg.reasoning.strip():
109
- # collapsed_content.append(f"<reasoning>{msg.reasoning}</reasoning>")
110
  # Добавляем search-results, если они есть
111
  if msg.searchResults.strip():
112
  collapsed_content.append(f"<search-results>{msg.searchResults}</search-results>")
113
-
114
 
115
- collapsed_content.append(f"\n####\nassistant:")
116
  # Формируем финальный текст с переносами строк
117
  new_content = "\n".join(collapsed_content)
118
 
@@ -133,17 +134,6 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
133
  Генератор для стриминга ответа LLM через SSE.
134
  """
135
  try:
136
- old_history = request.history
137
- new_history = [Message(
138
- role=msg.role,
139
- content=msg.content,
140
- reasoning=msg.reasoning,
141
- searchResults='', #msg.searchResults[:10000] + "..." if msg.searchResults else '',
142
- searchEntities=[],
143
- ) for msg in old_history]
144
- request.history = new_history
145
-
146
-
147
  qe_result = await dialogue_service.get_qe_result(request.history)
148
  try_insert_reasoning(request, qe_result.debug_message)
149
 
@@ -172,12 +162,12 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
172
  dataset = dataset_service.get_current_dataset()
173
  if dataset is None:
174
  raise HTTPException(status_code=400, detail="Dataset not found")
175
- _, chunk_ids, scores = entity_service.search_similar(
176
- qe_result.search_query,
177
- dataset.id,
178
- [],
179
- )
180
  text_chunks = entity_service.build_text(chunk_ids, scores)
 
 
181
 
182
  search_results_event = {
183
  "event": "search_results",
@@ -190,7 +180,7 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
190
 
191
  # new_message = f'<search-results>\n{text_chunks}\n</search-results>\n{last_query.content}'
192
 
193
- try_insert_search_results(request, text_chunks)
194
  except Exception as e:
195
  logger.error(f"Error in SSE chat stream while searching: {str(e)}", stack_info=True)
196
  yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
 
70
  return False
71
 
72
  def try_insert_search_results(
73
+ chat_request: ChatRequest, search_results: List[str], entities: List[List[str]]
74
  ) -> bool:
75
+ i = 0
76
  for msg in reversed(chat_request.history):
77
+ if msg.role == "user" and not msg.searchResults:
78
+ msg.searchResults = search_results[i]
79
+ msg.searchEntities = entities[i]
80
+ i += 1
81
+ if i == len(search_results):
82
+ return True
83
  return False
84
 
85
  def try_insert_reasoning(
 
108
  if msg.content.strip():
109
  collapsed_content.append(f"{msg.role.strip()}: {msg.content.strip()}")
110
  # Добавляем reasoning, если есть
111
+ if msg.reasoning.strip():
112
+ collapsed_content.append(f"<reasoning>{msg.reasoning}</reasoning>")
113
  # Добавляем search-results, если они есть
114
  if msg.searchResults.strip():
115
  collapsed_content.append(f"<search-results>{msg.searchResults}</search-results>")
 
116
 
 
117
  # Формируем финальный текст с переносами строк
118
  new_content = "\n".join(collapsed_content)
119
 
 
134
  Генератор для стриминга ответа LLM через SSE.
135
  """
136
  try:
 
 
 
 
 
 
 
 
 
 
 
137
  qe_result = await dialogue_service.get_qe_result(request.history)
138
  try_insert_reasoning(request, qe_result.debug_message)
139
 
 
162
  dataset = dataset_service.get_current_dataset()
163
  if dataset is None:
164
  raise HTTPException(status_code=400, detail="Dataset not found")
165
+ previous_entities = [msg.searchEntities for msg in request.history if msg.searchEntities is not None]
166
+ previous_entities, chunk_ids, scores = entity_service.search_similar(qe_result.search_query,
167
+ dataset.id, previous_entities)
 
 
168
  text_chunks = entity_service.build_text(chunk_ids, scores)
169
+ all_text_chunks = [text_chunks] + [entity_service.build_text(entities) for entities in previous_entities]
170
+ all_entities = [chunk_ids] + previous_entities
171
 
172
  search_results_event = {
173
  "event": "search_results",
 
180
 
181
  # new_message = f'<search-results>\n{text_chunks}\n</search-results>\n{last_query.content}'
182
 
183
+ try_insert_search_results(request, all_text_chunks, all_entities)
184
  except Exception as e:
185
  logger.error(f"Error in SSE chat stream while searching: {str(e)}", stack_info=True)
186
  yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"