Added possibility to test connection
Browse files- vianu/spock/__main__.py +34 -7
- vianu/spock/app/app.py +114 -53
- vianu/spock/app/formatter.py +6 -3
- vianu/spock/settings.py +2 -1
- vianu/spock/src/cli.py +1 -1
- vianu/spock/src/ner.py +87 -40
- vianu/spock/src/scraping.py +4 -4
vianu/spock/__main__.py
CHANGED
|
@@ -2,19 +2,34 @@ from argparse import Namespace
|
|
| 2 |
import asyncio
|
| 3 |
from datetime import datetime
|
| 4 |
import logging
|
|
|
|
| 5 |
import sys
|
| 6 |
-
from typing import List, Tuple
|
|
|
|
|
|
|
| 7 |
|
| 8 |
from vianu import LOG_FMT
|
| 9 |
-
from vianu.spock.settings import SCRAPING_SOURCES, LOG_LEVEL
|
| 10 |
from vianu.spock.src.cli import parse_args
|
| 11 |
from vianu.spock.src.base import Setup, Document, SpoCK, FileHandler
|
| 12 |
from vianu.spock.src import scraping as scp
|
| 13 |
from vianu.spock.src import ner
|
| 14 |
|
| 15 |
-
|
| 16 |
logging.basicConfig(format=LOG_FMT, level=LOG_LEVEL)
|
| 17 |
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
async def _orchestrator(
|
|
@@ -73,7 +88,7 @@ async def _orchestrator(
|
|
| 73 |
await ner_queue.put(None)
|
| 74 |
|
| 75 |
|
| 76 |
-
def setup_asyncio_framework(args_: Namespace) -> Tuple[asyncio.Queue, List[asyncio.Task], List[asyncio.Task], asyncio.Task]:
|
| 77 |
"""Set up the asyncio framework for the SpoCK application."""
|
| 78 |
# Set up arguments
|
| 79 |
if args_.source is None:
|
|
@@ -86,7 +101,7 @@ def setup_asyncio_framework(args_: Namespace) -> Tuple[asyncio.Queue, List[async
|
|
| 86 |
|
| 87 |
# Start tasks
|
| 88 |
scp_tasks = scp.create_tasks(args_=args_, queue_in=src_queue, queue_out=scp_queue)
|
| 89 |
-
ner_tasks = ner.create_tasks(args_=args_, queue_in=scp_queue, queue_out=ner_queue)
|
| 90 |
orc_task = asyncio.create_task(
|
| 91 |
_orchestrator(
|
| 92 |
args_=args_,
|
|
@@ -125,10 +140,22 @@ async def main(args_: Namespace | None = None, save: bool = True) -> None:
|
|
| 125 |
args_= parse_args(sys.argv[1:])
|
| 126 |
|
| 127 |
logging.basicConfig(level=args_.log_level.upper(), format=LOG_FMT)
|
| 128 |
-
logger.info(f'starting SpoCK (args_={args_})')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
# Set up async structure (scraping queue/tasks, NER queue/tasks, orchestrator task)
|
| 131 |
-
ner_queue, _, _, _ = setup_asyncio_framework(args_)
|
| 132 |
|
| 133 |
# Set up collector task and wait for it to finish
|
| 134 |
# NOTE: if collector task is finished, the orchestrator is also finished (because of the sentinel in `ner_queue`)
|
|
|
|
| 2 |
import asyncio
|
| 3 |
from datetime import datetime
|
| 4 |
import logging
|
| 5 |
+
import os
|
| 6 |
import sys
|
| 7 |
+
from typing import List, Tuple, Dict, Any
|
| 8 |
+
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
|
| 11 |
from vianu import LOG_FMT
|
| 12 |
+
from vianu.spock.settings import SCRAPING_SOURCES, LOG_LEVEL, MODEL_TEST_QUESTION
|
| 13 |
from vianu.spock.src.cli import parse_args
|
| 14 |
from vianu.spock.src.base import Setup, Document, SpoCK, FileHandler
|
| 15 |
from vianu.spock.src import scraping as scp
|
| 16 |
from vianu.spock.src import ner
|
| 17 |
|
|
|
|
| 18 |
logging.basicConfig(format=LOG_FMT, level=LOG_LEVEL)
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
+
load_dotenv()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def get_model_config() -> Dict[str, Dict[str, Any]]:
|
| 24 |
+
"""Get model configuration."""
|
| 25 |
+
return {
|
| 26 |
+
'openai': {
|
| 27 |
+
'api_key': os.getenv('OPENAI_API_KEY'),
|
| 28 |
+
},
|
| 29 |
+
'llama': {
|
| 30 |
+
'base_url': os.getenv('OLLAMA_BASE_URL'),
|
| 31 |
+
},
|
| 32 |
+
}
|
| 33 |
|
| 34 |
|
| 35 |
async def _orchestrator(
|
|
|
|
| 88 |
await ner_queue.put(None)
|
| 89 |
|
| 90 |
|
| 91 |
+
def setup_asyncio_framework(args_: Namespace, model_config: Dict[str, Any]) -> Tuple[asyncio.Queue, List[asyncio.Task], List[asyncio.Task], asyncio.Task]:
|
| 92 |
"""Set up the asyncio framework for the SpoCK application."""
|
| 93 |
# Set up arguments
|
| 94 |
if args_.source is None:
|
|
|
|
| 101 |
|
| 102 |
# Start tasks
|
| 103 |
scp_tasks = scp.create_tasks(args_=args_, queue_in=src_queue, queue_out=scp_queue)
|
| 104 |
+
ner_tasks = ner.create_tasks(args_=args_, queue_in=scp_queue, queue_out=ner_queue, model_config=model_config)
|
| 105 |
orc_task = asyncio.create_task(
|
| 106 |
_orchestrator(
|
| 107 |
args_=args_,
|
|
|
|
| 140 |
args_= parse_args(sys.argv[1:])
|
| 141 |
|
| 142 |
logging.basicConfig(level=args_.log_level.upper(), format=LOG_FMT)
|
| 143 |
+
logger.info(f'starting SpoCK (args_={args_})')
|
| 144 |
+
|
| 145 |
+
# Test availability of NER model
|
| 146 |
+
model = args_.model
|
| 147 |
+
model_config = get_model_config()
|
| 148 |
+
try:
|
| 149 |
+
_ner = ner.NERFactory.create(model=model, config=model_config)
|
| 150 |
+
test_task = asyncio.create_task(_ner.test_model_endpoint())
|
| 151 |
+
test_answer = await test_task
|
| 152 |
+
logger.debug(f"test model endpoint of model='{model}': '{MODEL_TEST_QUESTION}' was answered with '{test_answer}'")
|
| 153 |
+
except Exception as e:
|
| 154 |
+
logger.error(f'could not reach model endpoint: {e}')
|
| 155 |
+
raise e
|
| 156 |
|
| 157 |
# Set up async structure (scraping queue/tasks, NER queue/tasks, orchestrator task)
|
| 158 |
+
ner_queue, _, _, _ = setup_asyncio_framework(args_=args_, model_config=model_config)
|
| 159 |
|
| 160 |
# Set up collector task and wait for it to finish
|
| 161 |
# NOTE: if collector task is finished, the orchestrator is also finished (because of the sentinel in `ner_queue`)
|
vianu/spock/app/app.py
CHANGED
|
@@ -10,26 +10,30 @@ from dotenv import load_dotenv
|
|
| 10 |
import gradio as gr
|
| 11 |
|
| 12 |
from vianu.spock.settings import LOG_LEVEL, N_SCP_TASKS, N_NER_TASKS
|
| 13 |
-
from vianu.spock.settings import LARGE_LANGUAGE_MODELS, SCRAPING_SOURCES, MAX_DOCS_SRC
|
| 14 |
from vianu.spock.settings import GRADIO_APP_NAME, GRADIO_SERVER_PORT, GRADIO_MAX_JOBS, GRADIO_UPDATE_INTERVAL
|
| 15 |
-
from vianu.spock.settings import OLLAMA_BASE_URL_ENV_NAME, OPENAI_API_KEY_ENV_NAME
|
| 16 |
from vianu.spock.src.base import Setup, SpoCK, SpoCKList, QueueItem # noqa: F401
|
| 17 |
from vianu import BaseApp
|
| 18 |
-
from vianu.spock.__main__ import setup_asyncio_framework
|
| 19 |
import vianu.spock.app.formatter as fmt
|
|
|
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
load_dotenv()
|
| 23 |
|
| 24 |
# App settings
|
| 25 |
_ASSETS_PATH = Path(__file__).parents[1] / "assets"
|
| 26 |
-
|
| 27 |
-
|
| 28 |
if not len(_UI_SETTINGS_LLM_CHOICES) == len(LARGE_LANGUAGE_MODELS):
|
| 29 |
raise ValueError('LARGE_LANGUAGE_MODELS and _UI_SETTINGS_LLM_CHOICES must have the same length')
|
|
|
|
|
|
|
| 30 |
if not len(_UI_SETTINGS_SOURCE_CHOICES) == len(SCRAPING_SOURCES):
|
| 31 |
raise ValueError('SCRAPING_SOURCES and _UI_SETTINGS_SOURCE_CHOICES must have the same length')
|
| 32 |
|
|
|
|
|
|
|
| 33 |
|
| 34 |
@dataclass
|
| 35 |
class LocalState:
|
|
@@ -55,6 +59,12 @@ class SessionState:
|
|
| 55 |
# Data
|
| 56 |
is_running: bool = False
|
| 57 |
spocks: SpoCKList = field(default_factory=list)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
_index_running_spock: int | None = None
|
| 59 |
_index_active_spock: int | None = None
|
| 60 |
|
|
@@ -113,25 +123,11 @@ class App(BaseApp):
|
|
| 113 |
with gr.Column(scale=1):
|
| 114 |
with gr.Accordion(label='LLM Endpoint'):
|
| 115 |
self._components['settings.llm_radio'] = gr.Radio(
|
| 116 |
-
label='Model', show_label=False, choices=_UI_SETTINGS_LLM_CHOICES, value='
|
| 117 |
)
|
| 118 |
|
| 119 |
-
# 'llama' specific settings
|
| 120 |
-
with gr.Group(visible=True) as self._components['settings.ollama_group']:
|
| 121 |
-
value = os.environ.get("OLLAMA_BASE_URL")
|
| 122 |
-
placeholder = 'base_url of ollama endpoint' if value is None else None
|
| 123 |
-
gr.Markdown('---')
|
| 124 |
-
self._components['settings.ollama_base_url'] = gr.Textbox(
|
| 125 |
-
label='base_url',
|
| 126 |
-
show_label=False,
|
| 127 |
-
info='base_url',
|
| 128 |
-
placeholder=placeholder,
|
| 129 |
-
value=value,
|
| 130 |
-
interactive=True,
|
| 131 |
-
)
|
| 132 |
-
|
| 133 |
# 'openai' specific settings
|
| 134 |
-
with gr.Group(visible=
|
| 135 |
value = os.environ.get("OPENAI_API_KEY")
|
| 136 |
placeholder = 'api_key of openai endpoint' if value is None else None
|
| 137 |
logger.debug(f'openai api_key={value}')
|
|
@@ -146,6 +142,23 @@ class App(BaseApp):
|
|
| 146 |
type='password',
|
| 147 |
)
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
with gr.Accordion(label='Sources', open=True):
|
| 150 |
self._components['settings.source'] = gr.CheckboxGroup(
|
| 151 |
label='Sources', show_label=False, choices=_UI_SETTINGS_SOURCE_CHOICES, value=SCRAPING_SOURCES, interactive=True
|
|
@@ -193,27 +206,48 @@ class App(BaseApp):
|
|
| 193 |
# Helpers
|
| 194 |
# --------------------------------------------------------------------------
|
| 195 |
@staticmethod
|
| 196 |
-
def _show_llm_settings(llm: str) -> Tuple[dict[str, Any], dict[str, Any]]:
|
|
|
|
| 197 |
logger.debug(f'show {llm} model settings')
|
|
|
|
| 198 |
if llm == 'llama':
|
| 199 |
-
return gr.update(visible=True), gr.update(visible=False)
|
| 200 |
elif llm == 'openai':
|
| 201 |
-
return gr.update(visible=False), gr.update(visible=True)
|
| 202 |
else:
|
| 203 |
-
return gr.update(visible=False), gr.update(visible=False)
|
| 204 |
|
| 205 |
@staticmethod
|
| 206 |
-
def
|
| 207 |
-
"""Setup
|
| 208 |
-
|
| 209 |
-
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
@staticmethod
|
| 212 |
-
def
|
| 213 |
-
"""Setup
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
@staticmethod
|
| 219 |
def _feed_cards_to_ui(local_state: dict, session_state: SessionState) -> List[dict[str, Any]]:
|
|
@@ -243,15 +277,20 @@ class App(BaseApp):
|
|
| 243 |
logger.debug(f'feeding details to UI (len(data)={len(active_spock.data)})')
|
| 244 |
return fmt.get_details_html(active_spock.data)
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
if llm == '
|
| 250 |
-
if
|
| 251 |
-
raise gr.Error('
|
| 252 |
-
elif llm == '
|
| 253 |
-
if
|
| 254 |
-
raise gr.Error('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
@staticmethod
|
| 257 |
def _toggle_button(session_state: SessionState) -> Tuple[SessionState, dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
@@ -348,7 +387,7 @@ class App(BaseApp):
|
|
| 348 |
|
| 349 |
# Setup asyncio tasks as in `vianu.spock.__main__`
|
| 350 |
args_ = session_state.get_running_spock().setup.to_namespace()
|
| 351 |
-
ner_queue, scp_tasks, ner_tasks, orc_task = setup_asyncio_framework(args_=args_)
|
| 352 |
session_state.ner_queue = ner_queue
|
| 353 |
session_state.scp_tasks = scp_tasks
|
| 354 |
session_state.ner_tasks = ner_tasks
|
|
@@ -442,25 +481,42 @@ class App(BaseApp):
|
|
| 442 |
"""Choose LLM model show the correspoding settings."""
|
| 443 |
self._components['settings.llm_radio'].change(
|
| 444 |
fn=self._show_llm_settings,
|
| 445 |
-
inputs=
|
|
|
|
|
|
|
|
|
|
| 446 |
outputs=[
|
| 447 |
self._components['settings.ollama_group'],
|
| 448 |
self._components['settings.openai_group'],
|
|
|
|
| 449 |
],
|
| 450 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
def _event_settings_ollama(self):
|
| 453 |
"""Callback of the ollama settings."""
|
| 454 |
-
self._components['settings.ollama_base_url'].
|
| 455 |
fn=self._set_ollama_base_url,
|
| 456 |
-
inputs=self._components['settings.ollama_base_url'],
|
|
|
|
| 457 |
)
|
| 458 |
|
| 459 |
-
def
|
| 460 |
-
"""
|
| 461 |
-
self._components['settings.
|
| 462 |
-
fn=self.
|
| 463 |
-
inputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 464 |
)
|
| 465 |
|
| 466 |
def _event_start_spock(self) -> None:
|
|
@@ -471,7 +527,11 @@ class App(BaseApp):
|
|
| 471 |
gr.on(
|
| 472 |
triggers=[search_term.submit, start_button.click],
|
| 473 |
fn=self._check_llm_settings,
|
| 474 |
-
inputs=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 475 |
).success(
|
| 476 |
fn=self._toggle_button,
|
| 477 |
inputs=self._session_state,
|
|
@@ -568,6 +628,7 @@ class App(BaseApp):
|
|
| 568 |
self._event_choose_llm()
|
| 569 |
self._event_settings_ollama()
|
| 570 |
self._event_settings_openai()
|
|
|
|
| 571 |
|
| 572 |
# Start/Stop events
|
| 573 |
self._event_start_spock()
|
|
|
|
| 10 |
import gradio as gr
|
| 11 |
|
| 12 |
from vianu.spock.settings import LOG_LEVEL, N_SCP_TASKS, N_NER_TASKS
|
| 13 |
+
from vianu.spock.settings import LARGE_LANGUAGE_MODELS, SCRAPING_SOURCES, MAX_DOCS_SRC, MODEL_TEST_QUESTION
|
| 14 |
from vianu.spock.settings import GRADIO_APP_NAME, GRADIO_SERVER_PORT, GRADIO_MAX_JOBS, GRADIO_UPDATE_INTERVAL
|
|
|
|
| 15 |
from vianu.spock.src.base import Setup, SpoCK, SpoCKList, QueueItem # noqa: F401
|
| 16 |
from vianu import BaseApp
|
| 17 |
+
from vianu.spock.__main__ import get_model_config, setup_asyncio_framework
|
| 18 |
import vianu.spock.app.formatter as fmt
|
| 19 |
+
from vianu.spock.src.ner import NERFactory
|
| 20 |
|
| 21 |
logger = logging.getLogger(__name__)
|
| 22 |
load_dotenv()
|
| 23 |
|
| 24 |
# App settings
|
| 25 |
_ASSETS_PATH = Path(__file__).parents[1] / "assets"
|
| 26 |
+
|
| 27 |
+
_UI_SETTINGS_LLM_CHOICES = [(name, value) for name, value in zip(['OpenAI', 'Ollama'], LARGE_LANGUAGE_MODELS)]
|
| 28 |
if not len(_UI_SETTINGS_LLM_CHOICES) == len(LARGE_LANGUAGE_MODELS):
|
| 29 |
raise ValueError('LARGE_LANGUAGE_MODELS and _UI_SETTINGS_LLM_CHOICES must have the same length')
|
| 30 |
+
|
| 31 |
+
_UI_SETTINGS_SOURCE_CHOICES = [(name, value) for name, value in zip(['PubMed', 'EMA', 'MHRA', 'FDA'], SCRAPING_SOURCES)]
|
| 32 |
if not len(_UI_SETTINGS_SOURCE_CHOICES) == len(SCRAPING_SOURCES):
|
| 33 |
raise ValueError('SCRAPING_SOURCES and _UI_SETTINGS_SOURCE_CHOICES must have the same length')
|
| 34 |
|
| 35 |
+
_MODEL_CONFIG = get_model_config()
|
| 36 |
+
|
| 37 |
|
| 38 |
@dataclass
|
| 39 |
class LocalState:
|
|
|
|
| 59 |
# Data
|
| 60 |
is_running: bool = False
|
| 61 |
spocks: SpoCKList = field(default_factory=list)
|
| 62 |
+
|
| 63 |
+
# Model config
|
| 64 |
+
model_config: Dict[str, Dict[str, Any]] = field(default_factory=lambda: _MODEL_CONFIG)
|
| 65 |
+
connection_is_valid: bool = False
|
| 66 |
+
|
| 67 |
+
# Indexes
|
| 68 |
_index_running_spock: int | None = None
|
| 69 |
_index_active_spock: int | None = None
|
| 70 |
|
|
|
|
| 123 |
with gr.Column(scale=1):
|
| 124 |
with gr.Accordion(label='LLM Endpoint'):
|
| 125 |
self._components['settings.llm_radio'] = gr.Radio(
|
| 126 |
+
label='Model', show_label=False, choices=_UI_SETTINGS_LLM_CHOICES, value='openai', interactive=True
|
| 127 |
)
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
# 'openai' specific settings
|
| 130 |
+
with gr.Group(visible=True) as self._components['settings.openai_group']:
|
| 131 |
value = os.environ.get("OPENAI_API_KEY")
|
| 132 |
placeholder = 'api_key of openai endpoint' if value is None else None
|
| 133 |
logger.debug(f'openai api_key={value}')
|
|
|
|
| 142 |
type='password',
|
| 143 |
)
|
| 144 |
|
| 145 |
+
# 'llama' specific settings
|
| 146 |
+
with gr.Group(visible=False) as self._components['settings.ollama_group']:
|
| 147 |
+
value = os.environ.get("OLLAMA_BASE_URL")
|
| 148 |
+
placeholder = 'base_url of ollama endpoint' if value is None else None
|
| 149 |
+
gr.Markdown('---')
|
| 150 |
+
self._components['settings.ollama_base_url'] = gr.Textbox(
|
| 151 |
+
label='base_url',
|
| 152 |
+
show_label=False,
|
| 153 |
+
info='base_url',
|
| 154 |
+
placeholder=placeholder,
|
| 155 |
+
value=value,
|
| 156 |
+
interactive=True,
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
self._components['settings.test_connection_button'] = gr.Button(value='Test connection', interactive=True)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
with gr.Accordion(label='Sources', open=True):
|
| 163 |
self._components['settings.source'] = gr.CheckboxGroup(
|
| 164 |
label='Sources', show_label=False, choices=_UI_SETTINGS_SOURCE_CHOICES, value=SCRAPING_SOURCES, interactive=True
|
|
|
|
| 206 |
# Helpers
|
| 207 |
# --------------------------------------------------------------------------
|
| 208 |
@staticmethod
|
| 209 |
+
def _show_llm_settings(llm: str, session_state: SessionState) -> Tuple[dict[str, Any], dict[str, Any], SessionState]:
|
| 210 |
+
"""Show the settings for the selected LLM model."""
|
| 211 |
logger.debug(f'show {llm} model settings')
|
| 212 |
+
session_state.connection_is_valid = False
|
| 213 |
if llm == 'llama':
|
| 214 |
+
return gr.update(visible=True), gr.update(visible=False), session_state
|
| 215 |
elif llm == 'openai':
|
| 216 |
+
return gr.update(visible=False), gr.update(visible=True), session_state
|
| 217 |
else:
|
| 218 |
+
return gr.update(visible=False), gr.update(visible=False), session_state
|
| 219 |
|
| 220 |
@staticmethod
|
| 221 |
+
def _set_openai_api_key(api_key: str, session_state: SessionState) -> SessionState:
|
| 222 |
+
"""Setup openai api_key"""
|
| 223 |
+
log_key = '*****' if api_key else 'None'
|
| 224 |
+
logger.debug(f'set openai api_key {log_key}')
|
| 225 |
+
session_state.model_config['openai'] = {'api_key': api_key}
|
| 226 |
+
session_state.connection_is_valid = False
|
| 227 |
+
return session_state
|
| 228 |
|
| 229 |
@staticmethod
|
| 230 |
+
def _set_ollama_base_url(base_url: str, session_state: SessionState) -> SessionState:
|
| 231 |
+
"""Setup ollama base_url"""
|
| 232 |
+
logger.debug(f'set ollama base_url={base_url}')
|
| 233 |
+
session_state.model_config['llama'] = {'base_url': base_url}
|
| 234 |
+
session_state.connection_is_valid = False
|
| 235 |
+
return session_state
|
| 236 |
+
|
| 237 |
+
@staticmethod
|
| 238 |
+
async def _test_connection(llm: str, session_state: SessionState) -> SessionState:
|
| 239 |
+
logger.debug(f'test connection to model={llm}')
|
| 240 |
+
try:
|
| 241 |
+
ner = NERFactory.create(model=llm, config=session_state.model_config)
|
| 242 |
+
test_task = asyncio.create_task(ner.test_model_endpoint())
|
| 243 |
+
test_answer = await test_task
|
| 244 |
+
gr.Info(f"connection to model={llm} is valid: '{MODEL_TEST_QUESTION}' was answered with '{test_answer}'")
|
| 245 |
+
session_state.connection_is_valid = True
|
| 246 |
+
except Exception as e:
|
| 247 |
+
session_state.connection_is_valid = False
|
| 248 |
+
raise gr.Error(f'connection to model={llm} failed: {e}')
|
| 249 |
+
return session_state
|
| 250 |
+
|
| 251 |
|
| 252 |
@staticmethod
|
| 253 |
def _feed_cards_to_ui(local_state: dict, session_state: SessionState) -> List[dict[str, Any]]:
|
|
|
|
| 277 |
logger.debug(f'feeding details to UI (len(data)={len(active_spock.data)})')
|
| 278 |
return fmt.get_details_html(active_spock.data)
|
| 279 |
|
| 280 |
+
async def _check_llm_settings(self, llm: str, session_state: SessionState) -> SessionState:
|
| 281 |
+
"""Check if the LLM settings are set correcly."""
|
| 282 |
+
# Check if the settings are set
|
| 283 |
+
if llm == 'openai':
|
| 284 |
+
if session_state.model_config.get(llm) is None or session_state.model_config[llm].get('api_key') is None:
|
| 285 |
+
raise gr.Error('OpenAI api_key is not set')
|
| 286 |
+
elif llm == 'llama':
|
| 287 |
+
if session_state.model_config.get(llm) is None or session_state.model_config[llm].get('base_url') is None:
|
| 288 |
+
raise gr.Error('Ollama base_url is not set')
|
| 289 |
+
|
| 290 |
+
# Check connection to the model
|
| 291 |
+
if not session_state.connection_is_valid:
|
| 292 |
+
session_state = await self._test_connection(llm=llm, session_state=session_state)
|
| 293 |
+
return session_state
|
| 294 |
|
| 295 |
@staticmethod
|
| 296 |
def _toggle_button(session_state: SessionState) -> Tuple[SessionState, dict[str, Any], dict[str, Any], dict[str, Any]]:
|
|
|
|
| 387 |
|
| 388 |
# Setup asyncio tasks as in `vianu.spock.__main__`
|
| 389 |
args_ = session_state.get_running_spock().setup.to_namespace()
|
| 390 |
+
ner_queue, scp_tasks, ner_tasks, orc_task = setup_asyncio_framework(args_=args_, model_config=session_state.model_config)
|
| 391 |
session_state.ner_queue = ner_queue
|
| 392 |
session_state.scp_tasks = scp_tasks
|
| 393 |
session_state.ner_tasks = ner_tasks
|
|
|
|
| 481 |
"""Choose LLM model show the correspoding settings."""
|
| 482 |
self._components['settings.llm_radio'].change(
|
| 483 |
fn=self._show_llm_settings,
|
| 484 |
+
inputs=[
|
| 485 |
+
self._components['settings.llm_radio'],
|
| 486 |
+
self._session_state,
|
| 487 |
+
],
|
| 488 |
outputs=[
|
| 489 |
self._components['settings.ollama_group'],
|
| 490 |
self._components['settings.openai_group'],
|
| 491 |
+
self._session_state,
|
| 492 |
],
|
| 493 |
)
|
| 494 |
+
|
| 495 |
+
def _event_settings_openai(self):
|
| 496 |
+
"""Callback of the openai settings."""
|
| 497 |
+
self._components['settings.openai_api_key'].change(
|
| 498 |
+
fn=self._set_openai_api_key,
|
| 499 |
+
inputs=[self._components['settings.openai_api_key'], self._session_state],
|
| 500 |
+
outputs=self._session_state,
|
| 501 |
+
)
|
| 502 |
|
| 503 |
def _event_settings_ollama(self):
|
| 504 |
"""Callback of the ollama settings."""
|
| 505 |
+
self._components['settings.ollama_base_url'].change(
|
| 506 |
fn=self._set_ollama_base_url,
|
| 507 |
+
inputs=[self._components['settings.ollama_base_url'], self._session_state],
|
| 508 |
+
outputs=self._session_state,
|
| 509 |
)
|
| 510 |
|
| 511 |
+
def _event_test_connection(self):
|
| 512 |
+
"""Test the connection to the LLM model."""
|
| 513 |
+
self._components['settings.test_connection_button'].click(
|
| 514 |
+
fn=self._test_connection,
|
| 515 |
+
inputs=[
|
| 516 |
+
self._components['settings.llm_radio'],
|
| 517 |
+
self._session_state,
|
| 518 |
+
],
|
| 519 |
+
outputs=self._session_state,
|
| 520 |
)
|
| 521 |
|
| 522 |
def _event_start_spock(self) -> None:
|
|
|
|
| 527 |
gr.on(
|
| 528 |
triggers=[search_term.submit, start_button.click],
|
| 529 |
fn=self._check_llm_settings,
|
| 530 |
+
inputs=[
|
| 531 |
+
self._components['settings.llm_radio'],
|
| 532 |
+
self._session_state,
|
| 533 |
+
],
|
| 534 |
+
outputs=self._session_state,
|
| 535 |
).success(
|
| 536 |
fn=self._toggle_button,
|
| 537 |
inputs=self._session_state,
|
|
|
|
| 628 |
self._event_choose_llm()
|
| 629 |
self._event_settings_ollama()
|
| 630 |
self._event_settings_openai()
|
| 631 |
+
self._event_test_connection()
|
| 632 |
|
| 633 |
# Start/Stop events
|
| 634 |
self._event_start_spock()
|
vianu/spock/app/formatter.py
CHANGED
|
@@ -11,6 +11,7 @@ JOBS_CONTAINER_CARD_TEMPLATE = """
|
|
| 11 |
<div class="card" onclick="cardClickHandler(this)">
|
| 12 |
<div class="title">{title} {status}</div>
|
| 13 |
<div class="info">Date: {date}</div>
|
|
|
|
| 14 |
<div class="info">Sources: {sources}</div>
|
| 15 |
<div class="info">#docs: {n_doc} | #adr: {n_adr}</div>
|
| 16 |
</div>
|
|
@@ -78,13 +79,14 @@ def _get_status_html(status: str) -> str:
|
|
| 78 |
|
| 79 |
def get_job_card_html(card_nmbr: int, spock: SpoCK):
|
| 80 |
"""Get the HTML for the job card."""
|
| 81 |
-
|
| 82 |
data = spock.data
|
| 83 |
|
| 84 |
title = spock.setup.term
|
| 85 |
status = _get_status_html(spock.status)
|
| 86 |
-
|
| 87 |
-
|
|
|
|
| 88 |
n_doc = len(data)
|
| 89 |
n_adr = sum([len(d.adverse_reactions) for d in data])
|
| 90 |
return JOBS_CONTAINER_CARD_TEMPLATE.format(
|
|
@@ -92,6 +94,7 @@ def get_job_card_html(card_nmbr: int, spock: SpoCK):
|
|
| 92 |
title=title,
|
| 93 |
status=status,
|
| 94 |
date=date,
|
|
|
|
| 95 |
sources=sources,
|
| 96 |
n_doc=n_doc,
|
| 97 |
n_adr=n_adr,
|
|
|
|
| 11 |
<div class="card" onclick="cardClickHandler(this)">
|
| 12 |
<div class="title">{title} {status}</div>
|
| 13 |
<div class="info">Date: {date}</div>
|
| 14 |
+
<div class="info">Model: {model}</div>
|
| 15 |
<div class="info">Sources: {sources}</div>
|
| 16 |
<div class="info">#docs: {n_doc} | #adr: {n_adr}</div>
|
| 17 |
</div>
|
|
|
|
| 79 |
|
| 80 |
def get_job_card_html(card_nmbr: int, spock: SpoCK):
|
| 81 |
"""Get the HTML for the job card."""
|
| 82 |
+
setup = spock.setup
|
| 83 |
data = spock.data
|
| 84 |
|
| 85 |
title = spock.setup.term
|
| 86 |
status = _get_status_html(spock.status)
|
| 87 |
+
date = setup.submission.strftime(DATE_FORMAT)
|
| 88 |
+
model = setup.model
|
| 89 |
+
sources = ", ".join(setup.source)
|
| 90 |
n_doc = len(data)
|
| 91 |
n_adr = sum([len(d.adverse_reactions) for d in data])
|
| 92 |
return JOBS_CONTAINER_CARD_TEMPLATE.format(
|
|
|
|
| 94 |
title=title,
|
| 95 |
status=status,
|
| 96 |
date=date,
|
| 97 |
+
model=model,
|
| 98 |
sources=sources,
|
| 99 |
n_doc=n_doc,
|
| 100 |
n_adr=n_adr,
|
vianu/spock/settings.py
CHANGED
|
@@ -23,10 +23,11 @@ PUBMED_DB = 'pubmed'
|
|
| 23 |
PUBMED_BATCH_SIZE = 20
|
| 24 |
|
| 25 |
# NER settings
|
| 26 |
-
LARGE_LANGUAGE_MODELS = ['
|
| 27 |
MAX_TOKENS = 128.000
|
| 28 |
LLAMA_MODEL='llama3.2'
|
| 29 |
OPENAI_MODEL='gpt-4o'
|
| 30 |
N_NER_TASKS = 2
|
| 31 |
OLLAMA_BASE_URL_ENV_NAME = "OLLAMA_BASE_URL"
|
| 32 |
OPENAI_API_KEY_ENV_NAME = "OPENAI_API_KEY"
|
|
|
|
|
|
| 23 |
PUBMED_BATCH_SIZE = 20
|
| 24 |
|
| 25 |
# NER settings
|
| 26 |
+
LARGE_LANGUAGE_MODELS = ['openai', 'llama']
|
| 27 |
MAX_TOKENS = 128.000
|
| 28 |
LLAMA_MODEL='llama3.2'
|
| 29 |
OPENAI_MODEL='gpt-4o'
|
| 30 |
N_NER_TASKS = 2
|
| 31 |
OLLAMA_BASE_URL_ENV_NAME = "OLLAMA_BASE_URL"
|
| 32 |
OPENAI_API_KEY_ENV_NAME = "OPENAI_API_KEY"
|
| 33 |
+
MODEL_TEST_QUESTION = 'Are you available?'
|
vianu/spock/src/cli.py
CHANGED
|
@@ -27,7 +27,7 @@ def parse_args(args_: Sequence) -> argparse.Namespace:
|
|
| 27 |
|
| 28 |
# Add NER group
|
| 29 |
ner_gp = parser.add_argument_group('ner')
|
| 30 |
-
ner_gp.add_argument('--model', '-m', type=str, choices=LARGE_LANGUAGE_MODELS, default='
|
| 31 |
ner_gp.add_argument('--n-ner-tasks', metavar='', type=int, default=N_NER_TASKS, help='number of async ner tasks')
|
| 32 |
|
| 33 |
return parser.parse_args(args_)
|
|
|
|
| 27 |
|
| 28 |
# Add NER group
|
| 29 |
ner_gp = parser.add_argument_group('ner')
|
| 30 |
+
ner_gp.add_argument('--model', '-m', type=str, choices=LARGE_LANGUAGE_MODELS, default='openai', help='NER model')
|
| 31 |
ner_gp.add_argument('--n-ner-tasks', metavar='', type=int, default=N_NER_TASKS, help='number of async ner tasks')
|
| 32 |
|
| 33 |
return parser.parse_args(args_)
|
vianu/spock/src/ner.py
CHANGED
|
@@ -7,14 +7,12 @@ import os
|
|
| 7 |
import re
|
| 8 |
from typing import List
|
| 9 |
|
| 10 |
-
from dotenv import load_dotenv
|
| 11 |
from openai import AsyncOpenAI
|
| 12 |
|
| 13 |
from vianu.spock.src.base import NamedEntity, QueueItem # noqa: F401
|
| 14 |
-
from vianu.spock.settings import N_CHAR_DOC_ID, LLAMA_MODEL, OPENAI_MODEL
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
-
load_dotenv()
|
| 18 |
|
| 19 |
NAMED_ENTITY_PROMPT = """
|
| 20 |
You are an expert in Natural Language Processing. Your task is to identify named entities (NER) in a given text.
|
|
@@ -89,12 +87,27 @@ class NER(ABC):
|
|
| 89 |
"content": text,
|
| 90 |
},
|
| 91 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
|
| 93 |
|
| 94 |
@abstractmethod
|
| 95 |
-
async def
|
| 96 |
raise NotImplementedError('OpenAINER._get_ner_model_answer is not implemented yet')
|
| 97 |
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
async def apply(self, queue_in: asyncio.Queue, queue_out: asyncio.Queue) -> None:
|
| 100 |
"""Apply NER to a text received from input queue and put the results in an output queue."""
|
|
@@ -114,7 +127,7 @@ class NER(ABC):
|
|
| 114 |
self.logger.debug(f'starting ner for item.id_={id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]})')
|
| 115 |
try:
|
| 116 |
text = doc.text
|
| 117 |
-
content = await self.
|
| 118 |
except Exception as e:
|
| 119 |
self.logger.error(f'error during ner for item.id_={item.id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]}): {e}')
|
| 120 |
queue_in.task_done()
|
|
@@ -147,16 +160,38 @@ class NER(ABC):
|
|
| 147 |
# Put the document in the output queue
|
| 148 |
await queue_out.put(item)
|
| 149 |
queue_in.task_done()
|
| 150 |
-
self.logger.info(f'finished NER task for item.id_={id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]})')
|
| 151 |
|
| 152 |
|
| 153 |
-
class
|
| 154 |
|
| 155 |
-
def __init__(self,
|
| 156 |
super().__init__()
|
| 157 |
-
self._base_url = base_url
|
| 158 |
self._model = model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
def _get_http_data(self, text: str, stream: bool = False) -> dict:
|
| 162 |
messages = self._get_messages(text=text)
|
|
@@ -166,9 +201,8 @@ class OllamaNER(NER):
|
|
| 166 |
"stream": stream,
|
| 167 |
}
|
| 168 |
return data
|
| 169 |
-
|
| 170 |
|
| 171 |
-
async def
|
| 172 |
data = self._get_http_data(text=text)
|
| 173 |
async with aiohttp.ClientSession() as session:
|
| 174 |
url = f'{self._base_url}/api/chat/'
|
|
@@ -177,44 +211,57 @@ class OllamaNER(NER):
|
|
| 177 |
resp_json = await response.json()
|
| 178 |
content = resp_json['message']['content']
|
| 179 |
return content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
|
| 182 |
-
class
|
|
|
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
-
|
| 190 |
-
async def _get_ner_model_answer(self, text: str) -> str:
|
| 191 |
-
messages = self._get_messages(text=text)
|
| 192 |
-
chat_completion = await self._client.chat.completions.create(
|
| 193 |
-
messages=messages,
|
| 194 |
-
model=self._model,
|
| 195 |
-
)
|
| 196 |
-
return chat_completion.choices[0].message.content
|
| 197 |
|
| 198 |
|
| 199 |
-
def create_tasks(args_: Namespace, queue_in: asyncio.Queue, queue_out: asyncio.Queue) -> List[asyncio.Task]:
|
| 200 |
"""Create asyncio NER tasks."""
|
| 201 |
n_ner_tasks = args_.n_ner_tasks
|
| 202 |
model = args_.model
|
| 203 |
-
|
| 204 |
-
if model == 'llama':
|
| 205 |
-
base_url = os.environ.get('OLLAMA_BASE_URL')
|
| 206 |
-
if base_url is None:
|
| 207 |
-
raise EnvironmentError("The ollama endpoint must be set by the OLLAMA_ENDPOINT environment variable")
|
| 208 |
-
ner = OllamaNER(base_url=base_url, model=LLAMA_MODEL)
|
| 209 |
-
|
| 210 |
-
elif model == 'openai':
|
| 211 |
-
api_key = os.environ.get('OPENAI_API_KEY')
|
| 212 |
-
if api_key is None:
|
| 213 |
-
raise EnvironmentError("The api_key for the OpenAI client must be set by the OPENAI_API_KEY environment variable")
|
| 214 |
-
ner = OpenAINER(api_key=api_key, model=OPENAI_MODEL)
|
| 215 |
-
|
| 216 |
-
else:
|
| 217 |
-
raise ValueError(f"unknown ner model '{args_.model}'")
|
| 218 |
|
| 219 |
logger.info(f'setting up {n_ner_tasks} NER task(s)')
|
| 220 |
tasks = [asyncio.create_task(ner.apply(queue_in=queue_in, queue_out=queue_out)) for _ in range(n_ner_tasks)]
|
|
|
|
| 7 |
import re
|
| 8 |
from typing import List
|
| 9 |
|
|
|
|
| 10 |
from openai import AsyncOpenAI
|
| 11 |
|
| 12 |
from vianu.spock.src.base import NamedEntity, QueueItem # noqa: F401
|
| 13 |
+
from vianu.spock.settings import N_CHAR_DOC_ID, LLAMA_MODEL, OPENAI_MODEL, MODEL_TEST_QUESTION
|
| 14 |
|
| 15 |
logger = logging.getLogger(__name__)
|
|
|
|
| 16 |
|
| 17 |
NAMED_ENTITY_PROMPT = """
|
| 18 |
You are an expert in Natural Language Processing. Your task is to identify named entities (NER) in a given text.
|
|
|
|
| 87 |
"content": text,
|
| 88 |
},
|
| 89 |
]
|
| 90 |
+
|
| 91 |
+
@staticmethod
|
| 92 |
+
def _get_test_messages(text: str) -> List[dict]:
|
| 93 |
+
return [
|
| 94 |
+
{
|
| 95 |
+
"role": "system",
|
| 96 |
+
"content": "Answer very briefly."},
|
| 97 |
+
{
|
| 98 |
+
"role": "user",
|
| 99 |
+
"content": text,
|
| 100 |
+
},
|
| 101 |
+
]
|
| 102 |
|
| 103 |
|
| 104 |
@abstractmethod
|
| 105 |
+
async def _get_model_answer(self, text: str) -> str:
|
| 106 |
raise NotImplementedError('OpenAINER._get_ner_model_answer is not implemented yet')
|
| 107 |
|
| 108 |
+
@abstractmethod
|
| 109 |
+
async def test_model_endpoint(self) -> str:
|
| 110 |
+
pass
|
| 111 |
|
| 112 |
async def apply(self, queue_in: asyncio.Queue, queue_out: asyncio.Queue) -> None:
|
| 113 |
"""Apply NER to a text received from input queue and put the results in an output queue."""
|
|
|
|
| 127 |
self.logger.debug(f'starting ner for item.id_={id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]})')
|
| 128 |
try:
|
| 129 |
text = doc.text
|
| 130 |
+
content = await self._get_model_answer(text=text)
|
| 131 |
except Exception as e:
|
| 132 |
self.logger.error(f'error during ner for item.id_={item.id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]}): {e}')
|
| 133 |
queue_in.task_done()
|
|
|
|
| 160 |
# Put the document in the output queue
|
| 161 |
await queue_out.put(item)
|
| 162 |
queue_in.task_done()
|
| 163 |
+
self.logger.info(f'finished NER task for item.id_={id_} (doc.id_={doc.id_[:N_CHAR_DOC_ID]})')
|
| 164 |
|
| 165 |
|
| 166 |
+
class OpenAINER(NER):
|
| 167 |
|
| 168 |
+
def __init__(self, model: str, api_key: str):
|
| 169 |
super().__init__()
|
|
|
|
| 170 |
self._model = model
|
| 171 |
+
self._client = AsyncOpenAI(api_key=api_key)
|
| 172 |
+
|
| 173 |
+
async def _chat_completion(self, messages: List[dict]) -> str:
|
| 174 |
+
chat_completion = await self._client.chat.completions.create(
|
| 175 |
+
messages=messages,
|
| 176 |
+
model=self._model,
|
| 177 |
+
)
|
| 178 |
+
return chat_completion.choices[0].message.content
|
| 179 |
|
| 180 |
+
async def _get_model_answer(self, text: str) -> str:
|
| 181 |
+
messages = self._get_messages(text=text)
|
| 182 |
+
return await self._chat_completion(messages=messages)
|
| 183 |
+
|
| 184 |
+
async def test_model_endpoint(self) -> str:
|
| 185 |
+
messages = self._get_test_messages(text=MODEL_TEST_QUESTION)
|
| 186 |
+
return await self._chat_completion(messages=messages)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class OllamaNER(NER):
|
| 190 |
+
|
| 191 |
+
def __init__(self, model: str, base_url: str):
|
| 192 |
+
super().__init__()
|
| 193 |
+
self._model = model
|
| 194 |
+
self._base_url = base_url
|
| 195 |
|
| 196 |
def _get_http_data(self, text: str, stream: bool = False) -> dict:
|
| 197 |
messages = self._get_messages(text=text)
|
|
|
|
| 201 |
"stream": stream,
|
| 202 |
}
|
| 203 |
return data
|
|
|
|
| 204 |
|
| 205 |
+
async def _get_model_answer(self, text: str) -> str:
|
| 206 |
data = self._get_http_data(text=text)
|
| 207 |
async with aiohttp.ClientSession() as session:
|
| 208 |
url = f'{self._base_url}/api/chat/'
|
|
|
|
| 211 |
resp_json = await response.json()
|
| 212 |
content = resp_json['message']['content']
|
| 213 |
return content
|
| 214 |
+
|
| 215 |
+
async def test_model_endpoint(self) -> str:
|
| 216 |
+
messages = self._get_test_messages(text=MODEL_TEST_QUESTION)
|
| 217 |
+
data = {
|
| 218 |
+
'model': self._model,
|
| 219 |
+
"messages": messages,
|
| 220 |
+
'stream': False,
|
| 221 |
+
}
|
| 222 |
+
async with aiohttp.ClientSession() as session:
|
| 223 |
+
url = f'{self._base_url}/api/chat/'
|
| 224 |
+
async with session.post(url, json=data) as response:
|
| 225 |
+
response.raise_for_status()
|
| 226 |
+
resp_json = await response.json()
|
| 227 |
+
content = resp_json['message']['content']
|
| 228 |
+
return content
|
| 229 |
|
| 230 |
|
| 231 |
+
class NERFactory:
|
| 232 |
+
"""Factory for NER models."""
|
| 233 |
|
| 234 |
+
@staticmethod
|
| 235 |
+
def create(model: str, config: dict) -> NER:
|
| 236 |
+
"""Create a NER model from the `model` keyword."""
|
| 237 |
+
|
| 238 |
+
# create a :class:`OllamaNER` instance
|
| 239 |
+
if model == 'openai':
|
| 240 |
+
api_key = config[model].get('api_key')
|
| 241 |
+
if api_key is None:
|
| 242 |
+
api_key = os.environ.get('OPENAI_API_KEY')
|
| 243 |
+
if api_key is None:
|
| 244 |
+
raise ValueError("The api_key for the OpenAI client is missing (set it by the OPENAI_API_KEY environment variable or in the settings)")
|
| 245 |
+
ner = OpenAINER(model=OPENAI_MODEL, api_key=api_key)
|
| 246 |
+
|
| 247 |
+
elif model == 'llama':
|
| 248 |
+
base_url = config[model].get('base_url')
|
| 249 |
+
if base_url is None:
|
| 250 |
+
base_url = os.environ.get('OLLAMA_BASE_URL')
|
| 251 |
+
if base_url is None:
|
| 252 |
+
raise ValueError("The base_url for the ollama endpoint is missing")
|
| 253 |
+
ner = OllamaNER(model=LLAMA_MODEL, base_url=base_url)
|
| 254 |
+
else:
|
| 255 |
+
raise ValueError(f"unknown ner model '{model}'")
|
| 256 |
|
| 257 |
+
return ner
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
|
| 260 |
+
def create_tasks(args_: Namespace, queue_in: asyncio.Queue, queue_out: asyncio.Queue, model_config: dict) -> List[asyncio.Task]:
|
| 261 |
"""Create asyncio NER tasks."""
|
| 262 |
n_ner_tasks = args_.n_ner_tasks
|
| 263 |
model = args_.model
|
| 264 |
+
ner = NERFactory.create(model=model, config=model_config)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
logger.info(f'setting up {n_ner_tasks} NER task(s)')
|
| 267 |
tasks = [asyncio.create_task(ner.apply(queue_in=queue_in, queue_out=queue_out)) for _ in range(n_ner_tasks)]
|
vianu/spock/src/scraping.py
CHANGED
|
@@ -9,7 +9,7 @@ The module contains three main classes:
|
|
| 9 |
from abc import ABC, abstractmethod
|
| 10 |
from argparse import Namespace
|
| 11 |
import asyncio
|
| 12 |
-
from dataclasses import dataclass
|
| 13 |
from datetime import datetime
|
| 14 |
from io import BytesIO
|
| 15 |
import logging
|
|
@@ -284,9 +284,9 @@ class PubmedScraper(Scraper):
|
|
| 284 |
@dataclass
|
| 285 |
class SearchResults:
|
| 286 |
"""Class for storing the search results from different databases."""
|
| 287 |
-
count: int | None
|
| 288 |
-
n_pages: int | None
|
| 289 |
-
items: List[Tag]
|
| 290 |
|
| 291 |
|
| 292 |
class EMAScraper(Scraper):
|
|
|
|
| 9 |
from abc import ABC, abstractmethod
|
| 10 |
from argparse import Namespace
|
| 11 |
import asyncio
|
| 12 |
+
from dataclasses import dataclass, field
|
| 13 |
from datetime import datetime
|
| 14 |
from io import BytesIO
|
| 15 |
import logging
|
|
|
|
| 284 |
@dataclass
|
| 285 |
class SearchResults:
|
| 286 |
"""Class for storing the search results from different databases."""
|
| 287 |
+
count: int | None = None
|
| 288 |
+
n_pages: int | None = None
|
| 289 |
+
items: List[Tag] = field(default_factory=list)
|
| 290 |
|
| 291 |
|
| 292 |
class EMAScraper(Scraper):
|