Spaces:
Runtime error
Runtime error
update
Browse files- Dockerfile +1 -1
- alembic.ini +119 -0
- common/db.py +1 -9
- common/db_schemas.py +10 -0
- common/dependencies.py +6 -0
- components/dbo/alembic/README +1 -0
- components/dbo/alembic/autoupdate_db.py +58 -0
- components/dbo/alembic/env.py +81 -0
- components/dbo/alembic/script.py.mako +28 -0
- components/dbo/alembic/versions/12bb1ebae3ff_logs_refactoring.py +56 -0
- components/dbo/alembic/versions/6635b061c086_init.py +32 -0
- components/dbo/models/feedback.py +0 -2
- components/dbo/models/log.py +7 -8
- components/services/document.py +1 -0
- components/services/log.py +106 -0
- config_hf.yaml +39 -0
- docker-compose-example.yaml +1 -1
- main.py +25 -16
- requirements.txt +2 -1
- routes/dataset.py +24 -24
- routes/llm.py +39 -5
- routes/log.py +15 -102
- schemas/entity.py +1 -0
- schemas/log.py +47 -8
Dockerfile
CHANGED
|
@@ -2,7 +2,7 @@ FROM nvidia/cuda:12.6.0-runtime-ubuntu22.04
|
|
| 2 |
|
| 3 |
ARG PORT=7860
|
| 4 |
ENV PORT=${PORT}
|
| 5 |
-
ENV CONFIG_PATH=
|
| 6 |
ENV SQLALCHEMY_DATABASE_URL=sqlite:////data/logs.db
|
| 7 |
|
| 8 |
ENV PYTHONUNBUFFERED=1
|
|
|
|
| 2 |
|
| 3 |
ARG PORT=7860
|
| 4 |
ENV PORT=${PORT}
|
| 5 |
+
ENV CONFIG_PATH=config_hf.yaml
|
| 6 |
ENV SQLALCHEMY_DATABASE_URL=sqlite:////data/logs.db
|
| 7 |
|
| 8 |
ENV PYTHONUNBUFFERED=1
|
alembic.ini
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# A generic, single database configuration.
|
| 2 |
+
|
| 3 |
+
[alembic]
|
| 4 |
+
# path to migration scripts
|
| 5 |
+
# Use forward slashes (/) also on windows to provide an os agnostic path
|
| 6 |
+
script_location = components/dbo/alembic
|
| 7 |
+
|
| 8 |
+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
| 9 |
+
# Uncomment the line below if you want the files to be prepended with date and time
|
| 10 |
+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
| 11 |
+
# for all available tokens
|
| 12 |
+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
| 13 |
+
|
| 14 |
+
# sys.path path, will be prepended to sys.path if present.
|
| 15 |
+
# defaults to the current working directory.
|
| 16 |
+
prepend_sys_path = .
|
| 17 |
+
|
| 18 |
+
# timezone to use when rendering the date within the migration file
|
| 19 |
+
# as well as the filename.
|
| 20 |
+
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
| 21 |
+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
| 22 |
+
# string value is passed to ZoneInfo()
|
| 23 |
+
# leave blank for localtime
|
| 24 |
+
# timezone =
|
| 25 |
+
|
| 26 |
+
# max length of characters to apply to the "slug" field
|
| 27 |
+
# truncate_slug_length = 40
|
| 28 |
+
|
| 29 |
+
# set to 'true' to run the environment during
|
| 30 |
+
# the 'revision' command, regardless of autogenerate
|
| 31 |
+
# revision_environment = false
|
| 32 |
+
|
| 33 |
+
# set to 'true' to allow .pyc and .pyo files without
|
| 34 |
+
# a source .py file to be detected as revisions in the
|
| 35 |
+
# versions/ directory
|
| 36 |
+
# sourceless = false
|
| 37 |
+
|
| 38 |
+
# version location specification; This defaults
|
| 39 |
+
# to alembic/versions. When using multiple version
|
| 40 |
+
# directories, initial revisions must be specified with --version-path.
|
| 41 |
+
# The path separator used here should be the separator specified by "version_path_separator" below.
|
| 42 |
+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
|
| 43 |
+
|
| 44 |
+
# version path separator; As mentioned above, this is the character used to split
|
| 45 |
+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
| 46 |
+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
| 47 |
+
# Valid values for version_path_separator are:
|
| 48 |
+
#
|
| 49 |
+
# version_path_separator = :
|
| 50 |
+
# version_path_separator = ;
|
| 51 |
+
# version_path_separator = space
|
| 52 |
+
# version_path_separator = newline
|
| 53 |
+
#
|
| 54 |
+
# Use os.pathsep. Default configuration used for new projects.
|
| 55 |
+
version_path_separator = os
|
| 56 |
+
|
| 57 |
+
# set to 'true' to search source files recursively
|
| 58 |
+
# in each "version_locations" directory
|
| 59 |
+
# new in Alembic version 1.10
|
| 60 |
+
# recursive_version_locations = false
|
| 61 |
+
|
| 62 |
+
# the output encoding used when revision files
|
| 63 |
+
# are written from script.py.mako
|
| 64 |
+
# output_encoding = utf-8
|
| 65 |
+
|
| 66 |
+
sqlalchemy.url = sqlite:///../data/logs.db
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
[post_write_hooks]
|
| 70 |
+
# post_write_hooks defines scripts or Python functions that are run
|
| 71 |
+
# on newly generated revision scripts. See the documentation for further
|
| 72 |
+
# detail and examples
|
| 73 |
+
|
| 74 |
+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
| 75 |
+
# hooks = black
|
| 76 |
+
# black.type = console_scripts
|
| 77 |
+
# black.entrypoint = black
|
| 78 |
+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
| 79 |
+
|
| 80 |
+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
| 81 |
+
# hooks = ruff
|
| 82 |
+
# ruff.type = exec
|
| 83 |
+
# ruff.executable = %(here)s/.venv/bin/ruff
|
| 84 |
+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
|
| 85 |
+
|
| 86 |
+
# Logging configuration
|
| 87 |
+
[loggers]
|
| 88 |
+
keys = root,sqlalchemy,alembic
|
| 89 |
+
|
| 90 |
+
[handlers]
|
| 91 |
+
keys = console
|
| 92 |
+
|
| 93 |
+
[formatters]
|
| 94 |
+
keys = generic
|
| 95 |
+
|
| 96 |
+
[logger_root]
|
| 97 |
+
level = WARNING
|
| 98 |
+
handlers = console
|
| 99 |
+
qualname =
|
| 100 |
+
|
| 101 |
+
[logger_sqlalchemy]
|
| 102 |
+
level = WARNING
|
| 103 |
+
handlers =
|
| 104 |
+
qualname = sqlalchemy.engine
|
| 105 |
+
|
| 106 |
+
[logger_alembic]
|
| 107 |
+
level = INFO
|
| 108 |
+
handlers =
|
| 109 |
+
qualname = alembic
|
| 110 |
+
|
| 111 |
+
[handler_console]
|
| 112 |
+
class = StreamHandler
|
| 113 |
+
args = (sys.stderr,)
|
| 114 |
+
level = NOTSET
|
| 115 |
+
formatter = generic
|
| 116 |
+
|
| 117 |
+
[formatter_generic]
|
| 118 |
+
format = %(levelname)-5.5s [%(name)s] %(message)s
|
| 119 |
+
datefmt = %H:%M:%S
|
common/db.py
CHANGED
|
@@ -8,15 +8,7 @@ from sqlalchemy.orm import sessionmaker, scoped_session, Session
|
|
| 8 |
|
| 9 |
from common.configuration import Configuration
|
| 10 |
from components.dbo.models.base import Base
|
| 11 |
-
import
|
| 12 |
-
import components.dbo.models.acronym
|
| 13 |
-
import components.dbo.models.dataset
|
| 14 |
-
import components.dbo.models.dataset_document
|
| 15 |
-
import components.dbo.models.document
|
| 16 |
-
import components.dbo.models.log
|
| 17 |
-
import components.dbo.models.llm_prompt
|
| 18 |
-
import components.dbo.models.llm_config
|
| 19 |
-
import components.dbo.models.entity
|
| 20 |
|
| 21 |
CONFIG_PATH = os.environ.get('CONFIG_PATH', './config_dev.yaml')
|
| 22 |
config = Configuration(CONFIG_PATH)
|
|
|
|
| 8 |
|
| 9 |
from common.configuration import Configuration
|
| 10 |
from components.dbo.models.base import Base
|
| 11 |
+
import common.db_schemas
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
CONFIG_PATH = os.environ.get('CONFIG_PATH', './config_dev.yaml')
|
| 14 |
config = Configuration(CONFIG_PATH)
|
common/db_schemas.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from components.dbo.models.base import Base
|
| 2 |
+
import components.dbo.models.feedback
|
| 3 |
+
import components.dbo.models.acronym
|
| 4 |
+
import components.dbo.models.dataset
|
| 5 |
+
import components.dbo.models.dataset_document
|
| 6 |
+
import components.dbo.models.document
|
| 7 |
+
import components.dbo.models.log
|
| 8 |
+
import components.dbo.models.llm_prompt
|
| 9 |
+
import components.dbo.models.llm_config
|
| 10 |
+
import components.dbo.models.entity
|
common/dependencies.py
CHANGED
|
@@ -4,6 +4,7 @@ from logging import Logger
|
|
| 4 |
from typing import Annotated
|
| 5 |
|
| 6 |
from fastapi import Depends
|
|
|
|
| 7 |
from ntr_text_fragmentation import InjectionBuilder
|
| 8 |
from sqlalchemy.orm import Session, sessionmaker
|
| 9 |
|
|
@@ -34,6 +35,11 @@ def get_logger() -> Logger:
|
|
| 34 |
return logging.getLogger(__name__)
|
| 35 |
|
| 36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
def get_embedding_extractor(
|
| 38 |
config: Annotated[Configuration, Depends(get_config)],
|
| 39 |
) -> EmbeddingExtractor:
|
|
|
|
| 4 |
from typing import Annotated
|
| 5 |
|
| 6 |
from fastapi import Depends
|
| 7 |
+
from components.services.log import LogService
|
| 8 |
from ntr_text_fragmentation import InjectionBuilder
|
| 9 |
from sqlalchemy.orm import Session, sessionmaker
|
| 10 |
|
|
|
|
| 35 |
return logging.getLogger(__name__)
|
| 36 |
|
| 37 |
|
| 38 |
+
def get_log_service(
|
| 39 |
+
db: Annotated[sessionmaker, Depends(get_db)],
|
| 40 |
+
) -> LogService:
|
| 41 |
+
return LogService(db)
|
| 42 |
+
|
| 43 |
def get_embedding_extractor(
|
| 44 |
config: Annotated[Configuration, Depends(get_config)],
|
| 45 |
) -> EmbeddingExtractor:
|
components/dbo/alembic/README
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Generic single-database configuration.
|
components/dbo/alembic/autoupdate_db.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import re
|
| 3 |
+
import logging
|
| 4 |
+
|
| 5 |
+
from sqlalchemy import inspect
|
| 6 |
+
from sqlalchemy.sql import text
|
| 7 |
+
from alembic.config import Config
|
| 8 |
+
from alembic import command
|
| 9 |
+
|
| 10 |
+
import common.dependencies as DI
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
def get_old_versions():
|
| 16 |
+
old_versions = list()
|
| 17 |
+
migration_dir = 'components/dbo/alembic/versions'
|
| 18 |
+
for file in os.listdir(migration_dir):
|
| 19 |
+
if not file.endswith('.py'):
|
| 20 |
+
continue
|
| 21 |
+
file_path = os.path.join(migration_dir, file)
|
| 22 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 23 |
+
content = f.read()
|
| 24 |
+
match = re.search(
|
| 25 |
+
r"^(down_revision: Union\[str, None\] = )(None|'[^']*')",
|
| 26 |
+
content,
|
| 27 |
+
re.MULTILINE)
|
| 28 |
+
if match:
|
| 29 |
+
old_versions.append(match.group(2).replace("'", ""))
|
| 30 |
+
return old_versions
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def get_cur_version():
|
| 34 |
+
session_factory = DI.get_db()
|
| 35 |
+
session: Session = session_factory()
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
inspector = inspect(session.bind)
|
| 39 |
+
if 'alembic_version' not in inspector.get_table_names():
|
| 40 |
+
return None
|
| 41 |
+
|
| 42 |
+
result = session.execute(text("SELECT version_num FROM alembic_version")).scalar()
|
| 43 |
+
return result
|
| 44 |
+
|
| 45 |
+
finally:
|
| 46 |
+
session.close()
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def update():
|
| 50 |
+
old_versions = get_old_versions()
|
| 51 |
+
cur_version = get_cur_version()
|
| 52 |
+
if cur_version not in old_versions and cur_version is not None:
|
| 53 |
+
return
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
logger.info(f"Updating the database from migration {cur_version}")
|
| 57 |
+
config = Config("alembic.ini")
|
| 58 |
+
command.upgrade(config, "head")
|
components/dbo/alembic/env.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from logging.config import fileConfig
|
| 2 |
+
|
| 3 |
+
from sqlalchemy import engine_from_config
|
| 4 |
+
from sqlalchemy import pool
|
| 5 |
+
|
| 6 |
+
from alembic import context
|
| 7 |
+
|
| 8 |
+
from components.dbo.models.base import Base
|
| 9 |
+
import common.db_schemas
|
| 10 |
+
|
| 11 |
+
# this is the Alembic Config object, which provides
|
| 12 |
+
# access to the values within the .ini file in use.
|
| 13 |
+
config = context.config
|
| 14 |
+
|
| 15 |
+
# Interpret the config file for Python logging.
|
| 16 |
+
# This line sets up loggers basically.
|
| 17 |
+
if config.config_file_name is not None:
|
| 18 |
+
fileConfig(config.config_file_name)
|
| 19 |
+
|
| 20 |
+
# add your model's MetaData object here
|
| 21 |
+
# for 'autogenerate' support
|
| 22 |
+
# from myapp import mymodel
|
| 23 |
+
# target_metadata = mymodel.Base.metadata
|
| 24 |
+
target_metadata = Base.metadata
|
| 25 |
+
|
| 26 |
+
# other values from the config, defined by the needs of env.py,
|
| 27 |
+
# can be acquired:
|
| 28 |
+
# my_important_option = config.get_main_option("my_important_option")
|
| 29 |
+
# ... etc.
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def run_migrations_offline() -> None:
|
| 33 |
+
"""Run migrations in 'offline' mode.
|
| 34 |
+
|
| 35 |
+
This configures the context with just a URL
|
| 36 |
+
and not an Engine, though an Engine is acceptable
|
| 37 |
+
here as well. By skipping the Engine creation
|
| 38 |
+
we don't even need a DBAPI to be available.
|
| 39 |
+
|
| 40 |
+
Calls to context.execute() here emit the given string to the
|
| 41 |
+
script output.
|
| 42 |
+
|
| 43 |
+
"""
|
| 44 |
+
url = config.get_main_option("sqlalchemy.url")
|
| 45 |
+
context.configure(
|
| 46 |
+
url=url,
|
| 47 |
+
target_metadata=target_metadata,
|
| 48 |
+
literal_binds=True,
|
| 49 |
+
dialect_opts={"paramstyle": "named"},
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
with context.begin_transaction():
|
| 53 |
+
context.run_migrations()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def run_migrations_online() -> None:
|
| 57 |
+
"""Run migrations in 'online' mode.
|
| 58 |
+
|
| 59 |
+
In this scenario we need to create an Engine
|
| 60 |
+
and associate a connection with the context.
|
| 61 |
+
|
| 62 |
+
"""
|
| 63 |
+
connectable = engine_from_config(
|
| 64 |
+
config.get_section(config.config_ini_section, {}),
|
| 65 |
+
prefix="sqlalchemy.",
|
| 66 |
+
poolclass=pool.NullPool,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
with connectable.connect() as connection:
|
| 70 |
+
context.configure(
|
| 71 |
+
connection=connection, target_metadata=target_metadata
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
with context.begin_transaction():
|
| 75 |
+
context.run_migrations()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
if context.is_offline_mode():
|
| 79 |
+
run_migrations_offline()
|
| 80 |
+
else:
|
| 81 |
+
run_migrations_online()
|
components/dbo/alembic/script.py.mako
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""${message}
|
| 2 |
+
|
| 3 |
+
Revision ID: ${up_revision}
|
| 4 |
+
Revises: ${down_revision | comma,n}
|
| 5 |
+
Create Date: ${create_date}
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
${imports if imports else ""}
|
| 13 |
+
|
| 14 |
+
# revision identifiers, used by Alembic.
|
| 15 |
+
revision: str = ${repr(up_revision)}
|
| 16 |
+
down_revision: Union[str, None] = ${repr(down_revision)}
|
| 17 |
+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
| 18 |
+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def upgrade() -> None:
|
| 22 |
+
"""Upgrade schema."""
|
| 23 |
+
${upgrades if upgrades else "pass"}
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def downgrade() -> None:
|
| 27 |
+
"""Downgrade schema."""
|
| 28 |
+
${downgrades if downgrades else "pass"}
|
components/dbo/alembic/versions/12bb1ebae3ff_logs_refactoring.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Logs refactoring
|
| 2 |
+
|
| 3 |
+
Revision ID: 12bb1ebae3ff
|
| 4 |
+
Revises: 6635b061c086
|
| 5 |
+
Create Date: 2025-04-16 12:00:40.247356
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# revision identifiers, used by Alembic.
|
| 15 |
+
revision: str = '12bb1ebae3ff'
|
| 16 |
+
down_revision: Union[str, None] = '6635b061c086'
|
| 17 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
| 18 |
+
depends_on: Union[str, Sequence[str], None] = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def upgrade() -> None:
|
| 22 |
+
"""Upgrade schema."""
|
| 23 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
| 24 |
+
op.add_column('log', sa.Column('user_request', sa.String(), nullable=True))
|
| 25 |
+
op.add_column('log', sa.Column('qe_result', sa.String(), nullable=True))
|
| 26 |
+
op.add_column('log', sa.Column('search_result', sa.String(), nullable=True))
|
| 27 |
+
op.add_column('log', sa.Column('llm_result', sa.String(), nullable=True))
|
| 28 |
+
op.add_column('log', sa.Column('llm_settings', sa.String(), nullable=True))
|
| 29 |
+
op.add_column('log', sa.Column('user_name', sa.String(), nullable=True))
|
| 30 |
+
op.add_column('log', sa.Column('error', sa.String(), nullable=True))
|
| 31 |
+
op.drop_column('log', 'query_type')
|
| 32 |
+
op.drop_column('log', 'llm_classifier')
|
| 33 |
+
op.drop_column('log', 'llmResponse')
|
| 34 |
+
op.drop_column('log', 'userRequest')
|
| 35 |
+
op.drop_column('log', 'userName')
|
| 36 |
+
op.drop_column('log', 'llmPrompt')
|
| 37 |
+
# ### end Alembic commands ###
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def downgrade() -> None:
|
| 41 |
+
"""Downgrade schema."""
|
| 42 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
| 43 |
+
op.add_column('log', sa.Column('llmPrompt', sa.VARCHAR(), nullable=True))
|
| 44 |
+
op.add_column('log', sa.Column('userName', sa.VARCHAR(), nullable=True))
|
| 45 |
+
op.add_column('log', sa.Column('userRequest', sa.VARCHAR(), nullable=True))
|
| 46 |
+
op.add_column('log', sa.Column('llmResponse', sa.VARCHAR(), nullable=True))
|
| 47 |
+
op.add_column('log', sa.Column('llm_classifier', sa.VARCHAR(), nullable=True))
|
| 48 |
+
op.add_column('log', sa.Column('query_type', sa.VARCHAR(), nullable=True))
|
| 49 |
+
op.drop_column('log', 'error')
|
| 50 |
+
op.drop_column('log', 'user_name')
|
| 51 |
+
op.drop_column('log', 'llm_settings')
|
| 52 |
+
op.drop_column('log', 'llm_result')
|
| 53 |
+
op.drop_column('log', 'search_result')
|
| 54 |
+
op.drop_column('log', 'qe_result')
|
| 55 |
+
op.drop_column('log', 'user_request')
|
| 56 |
+
# ### end Alembic commands ###
|
components/dbo/alembic/versions/6635b061c086_init.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Init
|
| 2 |
+
|
| 3 |
+
Revision ID: 6635b061c086
|
| 4 |
+
Revises:
|
| 5 |
+
Create Date: 2025-04-09 09:21:08.157225
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
from typing import Sequence, Union
|
| 9 |
+
|
| 10 |
+
from alembic import op
|
| 11 |
+
import sqlalchemy as sa
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
# revision identifiers, used by Alembic.
|
| 15 |
+
revision: str = '6635b061c086'
|
| 16 |
+
down_revision: Union[str, None] = None
|
| 17 |
+
branch_labels: Union[str, Sequence[str], None] = None
|
| 18 |
+
depends_on: Union[str, Sequence[str], None] = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def upgrade() -> None:
|
| 22 |
+
"""Upgrade schema."""
|
| 23 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
| 24 |
+
pass
|
| 25 |
+
# ### end Alembic commands ###
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def downgrade() -> None:
|
| 29 |
+
"""Downgrade schema."""
|
| 30 |
+
# ### commands auto generated by Alembic - please adjust! ###
|
| 31 |
+
pass
|
| 32 |
+
# ### end Alembic commands ###
|
components/dbo/models/feedback.py
CHANGED
|
@@ -23,5 +23,3 @@ class Feedback(Base):
|
|
| 23 |
llmEstimate = mapped_column(Integer)
|
| 24 |
|
| 25 |
log_id = mapped_column(Integer, ForeignKey('log.id'), index=True)
|
| 26 |
-
|
| 27 |
-
log = relationship("Log", back_populates="feedback")
|
|
|
|
| 23 |
llmEstimate = mapped_column(Integer)
|
| 24 |
|
| 25 |
log_id = mapped_column(Integer, ForeignKey('log.id'), index=True)
|
|
|
|
|
|
components/dbo/models/log.py
CHANGED
|
@@ -9,11 +9,10 @@ from components.dbo.models.base import Base
|
|
| 9 |
class Log(Base):
|
| 10 |
__tablename__ = 'log'
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
feedback = relationship("Feedback", back_populates="log")
|
|
|
|
| 9 |
class Log(Base):
|
| 10 |
__tablename__ = 'log'
|
| 11 |
|
| 12 |
+
user_request = mapped_column(String)
|
| 13 |
+
qe_result = mapped_column(String)
|
| 14 |
+
search_result = mapped_column(String)
|
| 15 |
+
llm_result = mapped_column(String)
|
| 16 |
+
llm_settings = mapped_column(String)
|
| 17 |
+
user_name = mapped_column(String)
|
| 18 |
+
error = mapped_column(String)
|
|
|
components/services/document.py
CHANGED
|
@@ -93,6 +93,7 @@ class DocumentService:
|
|
| 93 |
file_location.parent.mkdir(parents=True, exist_ok=True)
|
| 94 |
with open(file_location, 'wb') as buffer:
|
| 95 |
buffer.write(file.file.read())
|
|
|
|
| 96 |
|
| 97 |
source_format = get_source_format(file.filename)
|
| 98 |
|
|
|
|
| 93 |
file_location.parent.mkdir(parents=True, exist_ok=True)
|
| 94 |
with open(file_location, 'wb') as buffer:
|
| 95 |
buffer.write(file.file.read())
|
| 96 |
+
file.file.close()
|
| 97 |
|
| 98 |
source_format = get_source_format(file.filename)
|
| 99 |
|
components/services/log.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
from fastapi import HTTPException
|
| 4 |
+
from sqlalchemy.orm import Session
|
| 5 |
+
|
| 6 |
+
from components.dbo.models.log import Log as LogSQL
|
| 7 |
+
from schemas.log import LogCreateSchema, LogFilterSchema, LogSchema, PaginatedLogResponse
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class LogService:
|
| 14 |
+
"""
|
| 15 |
+
Сервис для работы с параметрами LLM.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
def __init__(self, db: Session):
|
| 19 |
+
logger.info("LogService initializing")
|
| 20 |
+
self.db = db
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def create(self, log_schema: LogCreateSchema):
|
| 24 |
+
logger.info("Creating a new log")
|
| 25 |
+
with self.db() as session:
|
| 26 |
+
new_log: LogSQL = LogSQL(**log_schema.model_dump())
|
| 27 |
+
session.add(new_log)
|
| 28 |
+
session.commit()
|
| 29 |
+
session.refresh(new_log)
|
| 30 |
+
|
| 31 |
+
return LogSchema(**new_log.to_dict())
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def get_list(self, filters: LogFilterSchema) -> PaginatedLogResponse:
|
| 35 |
+
logger.info(f"Fetching logs with filters: {filters.model_dump(exclude_none=True)}")
|
| 36 |
+
with self.db() as session:
|
| 37 |
+
query = session.query(LogSQL)
|
| 38 |
+
|
| 39 |
+
# Применение фильтра по user_name
|
| 40 |
+
if filters.user_name:
|
| 41 |
+
query = query.filter(LogSQL.user_name == filters.user_name)
|
| 42 |
+
|
| 43 |
+
# Применение фильтра по диапазону date_created
|
| 44 |
+
if filters.date_from:
|
| 45 |
+
query = query.filter(LogSQL.date_created >= filters.date_from)
|
| 46 |
+
if filters.date_to:
|
| 47 |
+
query = query.filter(LogSQL.date_created <= filters.date_to)
|
| 48 |
+
|
| 49 |
+
total = query.count()
|
| 50 |
+
|
| 51 |
+
# Применение пагинации
|
| 52 |
+
offset = (filters.page - 1) * filters.page_size
|
| 53 |
+
logs = query.offset(offset).limit(filters.page_size).all()
|
| 54 |
+
|
| 55 |
+
# Вычисление общего количества страниц
|
| 56 |
+
total_pages = (total + filters.page_size - 1) // filters.page_size
|
| 57 |
+
|
| 58 |
+
# Формирование ответа
|
| 59 |
+
return PaginatedLogResponse(
|
| 60 |
+
data=[LogSchema(**log.to_dict()) for log in logs],
|
| 61 |
+
total=total,
|
| 62 |
+
page=filters.page,
|
| 63 |
+
page_size=filters.page_size,
|
| 64 |
+
total_pages=total_pages
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def get_by_id(self, id: int) -> LogSchema:
|
| 68 |
+
with self.db() as session:
|
| 69 |
+
log: LogSQL = session.query(LogSQL).filter(LogSQL.id == id).first()
|
| 70 |
+
|
| 71 |
+
if not log:
|
| 72 |
+
raise HTTPException(
|
| 73 |
+
status_code=400, detail=f"Item with id {id} not found"
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
return LogSchema(**log.to_dict())
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def update(self, id: int, new_log: LogSchema):
|
| 80 |
+
logger.info("Updating log")
|
| 81 |
+
with self.db() as session:
|
| 82 |
+
log: LogSQL = session.query(LogSQL).filter(LogSQL.id == id).first()
|
| 83 |
+
|
| 84 |
+
if not log:
|
| 85 |
+
raise HTTPException(
|
| 86 |
+
status_code=400, detail=f"Item with id {id} not found"
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
update_data = new_log.model_dump(exclude_unset=True)
|
| 90 |
+
|
| 91 |
+
for key, value in update_data.items():
|
| 92 |
+
if hasattr(log, key):
|
| 93 |
+
setattr(log, key, value)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
session.commit()
|
| 97 |
+
session.refresh(log)
|
| 98 |
+
return log
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def delete(self, id: int):
|
| 102 |
+
logger.info("Deleting log: {id}")
|
| 103 |
+
with self.db() as session:
|
| 104 |
+
log_to_del: LogSQL = session.query(LogSQL).get(id)
|
| 105 |
+
session.delete(log_to_del)
|
| 106 |
+
session.commit()
|
config_hf.yaml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
common:
|
| 2 |
+
log_file_path: !ENV ${LOG_FILE_PATH:/data/logs/common.log}
|
| 3 |
+
log_sql_path: !ENV ${SQLALCHEMY_DATABASE_URL:sqlite:////data/logs.db}
|
| 4 |
+
log_level: !ENV ${LOG_LEVEL:INFO}
|
| 5 |
+
|
| 6 |
+
bd:
|
| 7 |
+
entities:
|
| 8 |
+
# Варианты: fixed_size, sentence, paragraph, blm_sentence, blm_paragraph
|
| 9 |
+
strategy_name: !ENV ${ENTITIES_STRATEGY_NAME:paragraph}
|
| 10 |
+
strategy_params:
|
| 11 |
+
# words_per_chunk: 50
|
| 12 |
+
# overlap_words: 25
|
| 13 |
+
# respect_sentence_boundaries: true
|
| 14 |
+
process_tables: true
|
| 15 |
+
neighbors_max_distance: 1
|
| 16 |
+
|
| 17 |
+
search:
|
| 18 |
+
use_qe: true
|
| 19 |
+
use_vector_search: true
|
| 20 |
+
vectorizer_path: !ENV ${EMBEDDING_MODEL_PATH:BAAI/bge-m3}
|
| 21 |
+
device: !ENV ${DEVICE:cuda}
|
| 22 |
+
max_entities_per_message: 150
|
| 23 |
+
max_entities_per_dialogue: 300
|
| 24 |
+
|
| 25 |
+
files:
|
| 26 |
+
empty_start: true
|
| 27 |
+
documents_path: /data/documents
|
| 28 |
+
|
| 29 |
+
llm:
|
| 30 |
+
base_url: !ENV ${LLM_BASE_URL:https://api.deepinfra.com}
|
| 31 |
+
api_key_env: !ENV ${API_KEY_ENV:DEEPINFRA_API_KEY}
|
| 32 |
+
model: !ENV ${MODEL_NAME:meta-llama/Llama-3.3-70B-Instruct}
|
| 33 |
+
tokenizer_name: !ENV ${TOKENIZER_NAME:unsloth/Llama-3.3-70B-Instruct}
|
| 34 |
+
temperature: 0.14
|
| 35 |
+
top_p: 0.95
|
| 36 |
+
min_p: 0.05
|
| 37 |
+
frequency_penalty: -0.001
|
| 38 |
+
presence_penalty: 1.3
|
| 39 |
+
seed: 42
|
docker-compose-example.yaml
CHANGED
|
@@ -7,7 +7,7 @@ services:
|
|
| 7 |
args:
|
| 8 |
PORT: ${PORT:-8885}
|
| 9 |
environment:
|
| 10 |
-
- CONFIG_PATH=/app/
|
| 11 |
- SQLALCHEMY_DATABASE_URL=sqlite:////data/logs.db # Путь к БД
|
| 12 |
- PORT=${PORT:-8885}
|
| 13 |
- HF_HOME=/data/hf_cache
|
|
|
|
| 7 |
args:
|
| 8 |
PORT: ${PORT:-8885}
|
| 9 |
environment:
|
| 10 |
+
- CONFIG_PATH=/app/config_hf.yaml # Конфиг
|
| 11 |
- SQLALCHEMY_DATABASE_URL=sqlite:////data/logs.db # Путь к БД
|
| 12 |
- PORT=${PORT:-8885}
|
| 13 |
- HF_HOME=/data/hf_cache
|
main.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 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,29 +10,34 @@ from fastapi import FastAPI
|
|
| 10 |
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
from transformers import AutoModel, AutoTokenizer
|
| 12 |
|
| 13 |
-
|
| 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 |
-
#
|
| 26 |
-
|
|
|
|
|
|
|
| 27 |
|
| 28 |
# Загружаем переменные из .env
|
| 29 |
dotenv.load_dotenv()
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 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)
|
| 38 |
config = Configuration(CONFIG_PATH)
|
|
@@ -48,10 +53,14 @@ configure_logging(
|
|
| 48 |
tmp_path = Path(os.environ.get("APP_TMP_PATH", '.')) / 'tmp.json'
|
| 49 |
tmp_path.unlink(missing_ok=True)
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
app = FastAPI(title="Assistant control panel")
|
| 57 |
|
|
@@ -66,20 +75,20 @@ app.add_middleware(
|
|
| 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=
|
| 83 |
reload=False,
|
| 84 |
workers=2
|
| 85 |
)
|
|
|
|
| 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 |
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 |
+
from routes.log import router as log_router
|
| 25 |
from routes.auth import router as auth_router
|
| 26 |
+
from components.dbo.alembic import autoupdate_db
|
| 27 |
|
| 28 |
+
# Защита от автоудаления линтером
|
| 29 |
+
_ = DI
|
| 30 |
+
_ = Annotated
|
| 31 |
+
_ = asynccontextmanager
|
| 32 |
|
| 33 |
# Загружаем переменные из .env
|
| 34 |
dotenv.load_dotenv()
|
| 35 |
|
| 36 |
+
autoupdate_db.update()
|
| 37 |
+
|
|
|
|
| 38 |
|
| 39 |
CONFIG_PATH = os.environ.get('CONFIG_PATH', 'config_dev.yaml')
|
| 40 |
+
|
| 41 |
print("config path: ")
|
| 42 |
print(CONFIG_PATH)
|
| 43 |
config = Configuration(CONFIG_PATH)
|
|
|
|
| 53 |
tmp_path = Path(os.environ.get("APP_TMP_PATH", '.')) / 'tmp.json'
|
| 54 |
tmp_path.unlink(missing_ok=True)
|
| 55 |
|
| 56 |
+
try:
|
| 57 |
+
print("Downloading model to cache...")
|
| 58 |
+
AutoTokenizer.from_pretrained(config.db_config.search.vectorizer_path)
|
| 59 |
+
AutoModel.from_pretrained(config.db_config.search.vectorizer_path)
|
| 60 |
+
print("Model cached successfully.")
|
| 61 |
+
except Exception as e:
|
| 62 |
+
logger.error(f"Error downloading model from huggingface {config.db_config.search.vectorizer_path}: {str(e)}")
|
| 63 |
+
|
| 64 |
|
| 65 |
app = FastAPI(title="Assistant control panel")
|
| 66 |
|
|
|
|
| 75 |
)
|
| 76 |
|
| 77 |
app.include_router(llm_router)
|
|
|
|
|
|
|
| 78 |
app.include_router(dataset_router)
|
| 79 |
app.include_router(document_router)
|
| 80 |
app.include_router(llm_config_router)
|
| 81 |
app.include_router(llm_prompt_router)
|
| 82 |
app.include_router(entity_router)
|
| 83 |
+
app.include_router(evaluation_router)
|
| 84 |
app.include_router(auth_router)
|
| 85 |
+
app.include_router(log_router)
|
| 86 |
|
| 87 |
if __name__ == "__main__":
|
| 88 |
uvicorn.run(
|
| 89 |
"main:app",
|
| 90 |
host="localhost",
|
| 91 |
+
port=7860,
|
| 92 |
reload=False,
|
| 93 |
workers=2
|
| 94 |
)
|
requirements.txt
CHANGED
|
@@ -24,4 +24,5 @@ uvicorn==0.34.0
|
|
| 24 |
python-multipart==0.0.20
|
| 25 |
python-dotenv==1.1.0
|
| 26 |
pyjwt==2.10.1
|
| 27 |
-
fuzzywuzzy[speedup]
|
|
|
|
|
|
| 24 |
python-multipart==0.0.20
|
| 25 |
python-dotenv==1.1.0
|
| 26 |
pyjwt==2.10.1
|
| 27 |
+
fuzzywuzzy[speedup]
|
| 28 |
+
alembic==1.15.2
|
routes/dataset.py
CHANGED
|
@@ -42,31 +42,31 @@ async def get_processing(dataset_service: Annotated[DatasetService, Depends(DI.g
|
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
-
def try_create_default_dataset(dataset_service: DatasetService):
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
-
@router.get('/try_init_default_dataset')
|
| 61 |
-
async def try_init_default_dataset(dataset_service: Annotated[DatasetService, Depends(DI.get_dataset_service)],
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
|
| 71 |
|
| 72 |
@router.get('/{dataset_id}')
|
|
|
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
+
# def try_create_default_dataset(dataset_service: DatasetService):
|
| 46 |
+
# """
|
| 47 |
+
# Создаёт датасет по умолчанию, если такого нет.
|
| 48 |
+
# """
|
| 49 |
+
|
| 50 |
+
# if not dataset_service.get_default_dataset():
|
| 51 |
+
# print('creating default dataset')
|
| 52 |
+
# if dataset_service.config.db_config.files.empty_start:
|
| 53 |
+
# dataset_service.create_empty_dataset(is_default=True)
|
| 54 |
+
# else:
|
| 55 |
+
# dataset_service.create_dataset_from_directory(
|
| 56 |
+
# is_default=True,
|
| 57 |
+
# directory_with_documents=dataset_service.config.db_config.files.documents_path,
|
| 58 |
+
# )
|
| 59 |
|
| 60 |
+
# @router.get('/try_init_default_dataset')
|
| 61 |
+
# async def try_init_default_dataset(dataset_service: Annotated[DatasetService, Depends(DI.get_dataset_service)],
|
| 62 |
+
# current_user: Annotated[any, Depends(auth.get_current_user)]):
|
| 63 |
+
# logger.info(f"Handling GET request try_init_default_dataset")
|
| 64 |
+
# try_create_default_dataset(dataset_service)
|
| 65 |
+
# try:
|
| 66 |
+
# return {"ok": True}
|
| 67 |
+
# except Exception as e:
|
| 68 |
+
# logger.error(f"Error creating default dataset: {str(e)}")
|
| 69 |
+
# raise
|
| 70 |
|
| 71 |
|
| 72 |
@router.get('/{dataset_id}')
|
routes/llm.py
CHANGED
|
@@ -18,6 +18,8 @@ from components.services.dialogue import DialogueService
|
|
| 18 |
from components.services.entity import EntityService
|
| 19 |
from components.services.llm_config import LLMConfigService
|
| 20 |
from components.services.llm_prompt import LlmPromptService
|
|
|
|
|
|
|
| 21 |
|
| 22 |
router = APIRouter(prefix='/llm', tags=['LLM chat'])
|
| 23 |
logger = logging.getLogger(__name__)
|
|
@@ -53,7 +55,6 @@ def get_last_user_message(chat_request: ChatRequest) -> Optional[Message]:
|
|
| 53 |
msg
|
| 54 |
for msg in reversed(chat_request.history)
|
| 55 |
if msg.role == "user"
|
| 56 |
-
and (msg.searchResults is None or not msg.searchResults)
|
| 57 |
),
|
| 58 |
None,
|
| 59 |
)
|
|
@@ -165,12 +166,22 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
|
|
| 165 |
predict_params: LlmPredictParams,
|
| 166 |
dataset_service: DatasetService,
|
| 167 |
entity_service: EntityService,
|
| 168 |
-
dialogue_service: DialogueService
|
|
|
|
|
|
|
| 169 |
"""
|
| 170 |
Генератор для стриминга ответа LLM через SSE.
|
| 171 |
"""
|
|
|
|
|
|
|
|
|
|
| 172 |
try:
|
| 173 |
old_history = request.history
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
new_history = [Message(
|
| 175 |
role=msg.role,
|
| 176 |
content=msg.content,
|
|
@@ -182,6 +193,10 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
|
|
| 182 |
|
| 183 |
|
| 184 |
qe_result = await dialogue_service.get_qe_result(request.history)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
try_insert_reasoning(request, qe_result.debug_message)
|
| 186 |
|
| 187 |
# qe_debug_event = {
|
|
@@ -200,6 +215,9 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
|
|
| 200 |
}
|
| 201 |
yield f"data: {json.dumps(qe_event, ensure_ascii=False)}\n\n"
|
| 202 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
| 203 |
logger.error(f"Error in SSE chat stream while dialogue_service.get_qe_result: {str(e)}", stack_info=True)
|
| 204 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
| 205 |
qe_result = dialogue_service.get_qe_result_from_chat(request.history)
|
|
@@ -216,6 +234,9 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
|
|
| 216 |
)
|
| 217 |
text_chunks = await entity_service.build_text_async(chunk_ids, dataset.id, scores)
|
| 218 |
|
|
|
|
|
|
|
|
|
|
| 219 |
search_results_event = {
|
| 220 |
"event": "search_results",
|
| 221 |
"data": {
|
|
@@ -229,23 +250,35 @@ async def sse_generator(request: ChatRequest, llm_api: DeepInfraApi, system_prom
|
|
| 229 |
|
| 230 |
try_insert_search_results(request, text_chunks)
|
| 231 |
except Exception as e:
|
|
|
|
|
|
|
|
|
|
| 232 |
logger.error(f"Error in SSE chat stream while searching: {str(e)}", stack_info=True)
|
| 233 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
|
|
|
|
|
|
| 234 |
try:
|
| 235 |
# Сворачиваем историю в первое сообщение
|
| 236 |
collapsed_request = collapse_history_to_first_message(request)
|
| 237 |
-
|
|
|
|
|
|
|
| 238 |
# Стриминг токенов ответа
|
| 239 |
async for token in llm_api.get_predict_chat_generator(collapsed_request, system_prompt, predict_params):
|
| 240 |
token_event = {"event": "token", "data": token}
|
| 241 |
-
|
|
|
|
|
|
|
| 242 |
yield f"data: {json.dumps(token_event, ensure_ascii=False)}\n\n"
|
| 243 |
|
| 244 |
# Финальное событие
|
| 245 |
yield "data: {\"event\": \"done\"}\n\n"
|
| 246 |
except Exception as e:
|
|
|
|
| 247 |
logger.error(f"Error in SSE chat stream while generating response: {str(e)}", stack_info=True)
|
| 248 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
|
|
|
|
|
|
| 249 |
|
| 250 |
|
| 251 |
@router.post("/chat/stream")
|
|
@@ -258,6 +291,7 @@ async def chat_stream(
|
|
| 258 |
entity_service: Annotated[EntityService, Depends(DI.get_entity_service)],
|
| 259 |
dataset_service: Annotated[DatasetService, Depends(DI.get_dataset_service)],
|
| 260 |
dialogue_service: Annotated[DialogueService, Depends(DI.get_dialogue_service)],
|
|
|
|
| 261 |
current_user: Annotated[any, Depends(auth.get_current_user)]
|
| 262 |
):
|
| 263 |
try:
|
|
@@ -282,7 +316,7 @@ async def chat_stream(
|
|
| 282 |
"Access-Control-Allow-Origin": "*",
|
| 283 |
}
|
| 284 |
return StreamingResponse(
|
| 285 |
-
sse_generator(request, llm_api, system_prompt.text, predict_params, dataset_service, entity_service, dialogue_service),
|
| 286 |
media_type="text/event-stream",
|
| 287 |
headers=headers
|
| 288 |
)
|
|
|
|
| 18 |
from components.services.entity import EntityService
|
| 19 |
from components.services.llm_config import LLMConfigService
|
| 20 |
from components.services.llm_prompt import LlmPromptService
|
| 21 |
+
from components.services.log import LogService
|
| 22 |
+
from schemas.log import LogCreateSchema
|
| 23 |
|
| 24 |
router = APIRouter(prefix='/llm', tags=['LLM chat'])
|
| 25 |
logger = logging.getLogger(__name__)
|
|
|
|
| 55 |
msg
|
| 56 |
for msg in reversed(chat_request.history)
|
| 57 |
if msg.role == "user"
|
|
|
|
| 58 |
),
|
| 59 |
None,
|
| 60 |
)
|
|
|
|
| 166 |
predict_params: LlmPredictParams,
|
| 167 |
dataset_service: DatasetService,
|
| 168 |
entity_service: EntityService,
|
| 169 |
+
dialogue_service: DialogueService,
|
| 170 |
+
log_service: LogService,
|
| 171 |
+
current_user: auth.User) -> AsyncGenerator[str, None]:
|
| 172 |
"""
|
| 173 |
Генератор для стриминга ответа LLM через SSE.
|
| 174 |
"""
|
| 175 |
+
# Создаем экземпляр "сквозного" лога через весь процесс
|
| 176 |
+
log = LogCreateSchema(user_name=current_user.username)
|
| 177 |
+
|
| 178 |
try:
|
| 179 |
old_history = request.history
|
| 180 |
+
|
| 181 |
+
# Сохраняем последнее сообщение в лог как исходный пользовательский запрос
|
| 182 |
+
last_message = get_last_user_message(request)
|
| 183 |
+
log.user_request = last_message.content if last_message is not None else None
|
| 184 |
+
|
| 185 |
new_history = [Message(
|
| 186 |
role=msg.role,
|
| 187 |
content=msg.content,
|
|
|
|
| 193 |
|
| 194 |
|
| 195 |
qe_result = await dialogue_service.get_qe_result(request.history)
|
| 196 |
+
|
| 197 |
+
# Запись результата qe в лог
|
| 198 |
+
log.qe_result = qe_result.model_dump_json()
|
| 199 |
+
|
| 200 |
try_insert_reasoning(request, qe_result.debug_message)
|
| 201 |
|
| 202 |
# qe_debug_event = {
|
|
|
|
| 215 |
}
|
| 216 |
yield f"data: {json.dumps(qe_event, ensure_ascii=False)}\n\n"
|
| 217 |
except Exception as e:
|
| 218 |
+
log.error = "Error in QE block: " + str(e)
|
| 219 |
+
log_service.create(log)
|
| 220 |
+
|
| 221 |
logger.error(f"Error in SSE chat stream while dialogue_service.get_qe_result: {str(e)}", stack_info=True)
|
| 222 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
| 223 |
qe_result = dialogue_service.get_qe_result_from_chat(request.history)
|
|
|
|
| 234 |
)
|
| 235 |
text_chunks = await entity_service.build_text_async(chunk_ids, dataset.id, scores)
|
| 236 |
|
| 237 |
+
# Запись результатов поиска в лог
|
| 238 |
+
log.search_result = text_chunks
|
| 239 |
+
|
| 240 |
search_results_event = {
|
| 241 |
"event": "search_results",
|
| 242 |
"data": {
|
|
|
|
| 250 |
|
| 251 |
try_insert_search_results(request, text_chunks)
|
| 252 |
except Exception as e:
|
| 253 |
+
log.error = "Error in vector search block: " + str(e)
|
| 254 |
+
log_service.create(log)
|
| 255 |
+
|
| 256 |
logger.error(f"Error in SSE chat stream while searching: {str(e)}", stack_info=True)
|
| 257 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
| 258 |
+
|
| 259 |
+
log_error = None
|
| 260 |
try:
|
| 261 |
# Сворачиваем историю в первое сообщение
|
| 262 |
collapsed_request = collapse_history_to_first_message(request)
|
| 263 |
+
|
| 264 |
+
log.llm_result = ''
|
| 265 |
+
|
| 266 |
# Стриминг токенов ответа
|
| 267 |
async for token in llm_api.get_predict_chat_generator(collapsed_request, system_prompt, predict_params):
|
| 268 |
token_event = {"event": "token", "data": token}
|
| 269 |
+
|
| 270 |
+
log.llm_result += token
|
| 271 |
+
|
| 272 |
yield f"data: {json.dumps(token_event, ensure_ascii=False)}\n\n"
|
| 273 |
|
| 274 |
# Финальное событие
|
| 275 |
yield "data: {\"event\": \"done\"}\n\n"
|
| 276 |
except Exception as e:
|
| 277 |
+
log.error = "Error in llm inference block: " + str(e)
|
| 278 |
logger.error(f"Error in SSE chat stream while generating response: {str(e)}", stack_info=True)
|
| 279 |
yield "data: {\"event\": \"error\", \"data\":\""+str(e)+"\" }\n\n"
|
| 280 |
+
finally:
|
| 281 |
+
log_service.create(log)
|
| 282 |
|
| 283 |
|
| 284 |
@router.post("/chat/stream")
|
|
|
|
| 291 |
entity_service: Annotated[EntityService, Depends(DI.get_entity_service)],
|
| 292 |
dataset_service: Annotated[DatasetService, Depends(DI.get_dataset_service)],
|
| 293 |
dialogue_service: Annotated[DialogueService, Depends(DI.get_dialogue_service)],
|
| 294 |
+
log_service: Annotated[LogService, Depends(DI.get_log_service)],
|
| 295 |
current_user: Annotated[any, Depends(auth.get_current_user)]
|
| 296 |
):
|
| 297 |
try:
|
|
|
|
| 316 |
"Access-Control-Allow-Origin": "*",
|
| 317 |
}
|
| 318 |
return StreamingResponse(
|
| 319 |
+
sse_generator(request, llm_api, system_prompt.text, predict_params, dataset_service, entity_service, dialogue_service, log_service, current_user),
|
| 320 |
media_type="text/event-stream",
|
| 321 |
headers=headers
|
| 322 |
)
|
routes/log.py
CHANGED
|
@@ -1,119 +1,32 @@
|
|
| 1 |
import logging
|
| 2 |
from datetime import datetime
|
| 3 |
-
from typing import Annotated, Optional
|
| 4 |
|
| 5 |
-
from fastapi import APIRouter, Depends, Query
|
| 6 |
-
from
|
| 7 |
-
from starlette import status
|
| 8 |
|
| 9 |
from common import auth
|
| 10 |
from common.common import configure_logging
|
| 11 |
-
from
|
| 12 |
-
from
|
| 13 |
-
from components.dbo.models.log import Log
|
| 14 |
-
from schemas.log import LogCreate
|
| 15 |
import common.dependencies as DI
|
| 16 |
-
from sqlalchemy.orm import sessionmaker
|
| 17 |
|
| 18 |
router = APIRouter(tags=['Logs'])
|
| 19 |
|
| 20 |
logger = logging.getLogger(__name__)
|
| 21 |
configure_logging()
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
@router.get('/logs', status_code=status.HTTP_200_OK)
|
| 25 |
async def get_all_logs(
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
date_end: Optional[datetime] = Query(None, alias="date_end")
|
| 30 |
):
|
| 31 |
-
logger.info(f'GET /logs
|
| 32 |
-
logger.info(f'GET /logs: start_date={date_start}, end_date={date_end}')
|
| 33 |
-
feedback_alias = aliased(Feedback)
|
| 34 |
-
|
| 35 |
-
query = db.query(Log)
|
| 36 |
-
|
| 37 |
-
if date_start and date_end:
|
| 38 |
-
query = query.filter(Log.dateCreated.between(date_start, date_end))
|
| 39 |
-
elif date_start:
|
| 40 |
-
query = query.filter(Log.dateCreated >= date_start)
|
| 41 |
-
elif date_end:
|
| 42 |
-
query = query.filter(Log.dateCreated <= date_end)
|
| 43 |
-
|
| 44 |
-
query = query.outerjoin(feedback_alias, Log.id == feedback_alias.log_id)
|
| 45 |
-
|
| 46 |
-
logs_with_feedback = query.all()
|
| 47 |
-
|
| 48 |
-
combined_logs = []
|
| 49 |
-
for log in logs_with_feedback:
|
| 50 |
-
if log.feedback:
|
| 51 |
-
for feedback in log.feedback:
|
| 52 |
-
combined_logs.append(
|
| 53 |
-
{
|
| 54 |
-
"log_id": log.id,
|
| 55 |
-
"llmPrompt": log.llmPrompt,
|
| 56 |
-
"llmResponse": log.llmResponse,
|
| 57 |
-
"llm_classifier": log.llm_classifier,
|
| 58 |
-
"dateCreated": log.dateCreated,
|
| 59 |
-
"userRequest": log.userRequest,
|
| 60 |
-
"userName": log.userName,
|
| 61 |
-
"query_type": log.query_type,
|
| 62 |
-
"feedback_id": feedback.feedback_id,
|
| 63 |
-
"userComment": feedback.userComment,
|
| 64 |
-
"userScore": feedback.userScore,
|
| 65 |
-
"manualEstimate": feedback.manualEstimate,
|
| 66 |
-
"llmEstimate": feedback.llmEstimate,
|
| 67 |
-
}
|
| 68 |
-
)
|
| 69 |
-
else:
|
| 70 |
-
combined_logs.append(
|
| 71 |
-
{
|
| 72 |
-
"log_id": log.id,
|
| 73 |
-
"llmPrompt": log.llmPrompt,
|
| 74 |
-
"llmResponse": log.llmResponse,
|
| 75 |
-
"llm_classifier": log.llm_classifier,
|
| 76 |
-
"dateCreated": log.dateCreated,
|
| 77 |
-
"userRequest": log.userRequest,
|
| 78 |
-
"userName": log.userName,
|
| 79 |
-
"query_type": log.query_type,
|
| 80 |
-
"feedback_id": None,
|
| 81 |
-
"userComment": None,
|
| 82 |
-
"userScore": None,
|
| 83 |
-
"manualEstimate": None,
|
| 84 |
-
"llmEstimate": None,
|
| 85 |
-
}
|
| 86 |
-
)
|
| 87 |
-
return combined_logs
|
| 88 |
|
| 89 |
-
|
| 90 |
-
@router.get('/log/{log_id}', status_code=status.HTTP_200_OK)
|
| 91 |
-
async def get_log(db: Annotated[sessionmaker, Depends(DI.get_db)],
|
| 92 |
-
current_user: Annotated[any, Depends(auth.get_current_user)], log_id):
|
| 93 |
-
log = db.query(Log).filter(Log.id == log_id).first()
|
| 94 |
-
if log is None:
|
| 95 |
-
raise LogNotFoundException(log_id)
|
| 96 |
-
return log
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
@router.post('/log', status_code=status.HTTP_201_CREATED)
|
| 100 |
-
async def create_log(log: LogCreate, db: Annotated[sessionmaker, Depends(DI.get_db)]):
|
| 101 |
-
logger.info("Handling POST request to /log")
|
| 102 |
try:
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
llmResponse=log.llmResponse,
|
| 106 |
-
llm_classifier=log.llm_classifier,
|
| 107 |
-
userRequest=log.userRequest,
|
| 108 |
-
userName=log.userName,
|
| 109 |
-
)
|
| 110 |
-
|
| 111 |
-
db.add(new_log)
|
| 112 |
-
db.commit()
|
| 113 |
-
db.refresh(new_log)
|
| 114 |
-
|
| 115 |
-
logger.info(f"Successfully created log with ID: {new_log.id}")
|
| 116 |
-
return new_log
|
| 117 |
-
except Exception as e:
|
| 118 |
-
logger.error(f"Error creating log: {str(e)}")
|
| 119 |
raise e
|
|
|
|
|
|
|
|
|
| 1 |
import logging
|
| 2 |
from datetime import datetime
|
| 3 |
+
from typing import Annotated, List, Optional
|
| 4 |
|
| 5 |
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
| 6 |
+
from pydantic import BaseModel
|
|
|
|
| 7 |
|
| 8 |
from common import auth
|
| 9 |
from common.common import configure_logging
|
| 10 |
+
from components.services.log import LogService
|
| 11 |
+
from schemas.log import LogCreateSchema, LogFilterSchema, LogSchema, PaginatedLogResponse
|
|
|
|
|
|
|
| 12 |
import common.dependencies as DI
|
|
|
|
| 13 |
|
| 14 |
router = APIRouter(tags=['Logs'])
|
| 15 |
|
| 16 |
logger = logging.getLogger(__name__)
|
| 17 |
configure_logging()
|
| 18 |
+
|
| 19 |
+
@router.get('/logs', response_model=PaginatedLogResponse)
|
|
|
|
| 20 |
async def get_all_logs(
|
| 21 |
+
filters: Annotated[LogFilterSchema, Depends()],
|
| 22 |
+
log_service: Annotated[LogService, Depends(DI.get_log_service)],
|
| 23 |
+
current_user: Annotated[any, Depends(auth.get_current_user)]
|
|
|
|
| 24 |
):
|
| 25 |
+
logger.info(f'GET /logs')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
try:
|
| 28 |
+
return log_service.get_list(filters)
|
| 29 |
+
except HTTPException as e:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
raise e
|
| 31 |
+
except Exception as e:
|
| 32 |
+
raise HTTPException(status_code=500, detail=str(e))
|
schemas/entity.py
CHANGED
|
@@ -41,6 +41,7 @@ class EntityTextRequest(BaseModel):
|
|
| 41 |
chunk_scores: Optional[dict[str, float]] = None
|
| 42 |
include_tables: bool = True
|
| 43 |
max_documents: Optional[int] = None
|
|
|
|
| 44 |
|
| 45 |
|
| 46 |
class EntityTextResponse(BaseModel):
|
|
|
|
| 41 |
chunk_scores: Optional[dict[str, float]] = None
|
| 42 |
include_tables: bool = True
|
| 43 |
max_documents: Optional[int] = None
|
| 44 |
+
dataset_id: int
|
| 45 |
|
| 46 |
|
| 47 |
class EntityTextResponse(BaseModel):
|
schemas/log.py
CHANGED
|
@@ -1,12 +1,51 @@
|
|
| 1 |
-
from
|
|
|
|
| 2 |
|
| 3 |
from pydantic import BaseModel
|
| 4 |
|
| 5 |
|
| 6 |
-
class
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from datetime import datetime
|
| 2 |
+
from typing import List, Optional
|
| 3 |
|
| 4 |
from pydantic import BaseModel
|
| 5 |
|
| 6 |
|
| 7 |
+
class LogSchema(BaseModel):
|
| 8 |
+
id: int
|
| 9 |
+
date_created: datetime
|
| 10 |
+
user_request: Optional[str] = None
|
| 11 |
+
qe_result: Optional[str] = None
|
| 12 |
+
search_result: Optional[str] = None
|
| 13 |
+
llm_result: Optional[str] = None
|
| 14 |
+
llm_settings: Optional[str] = None
|
| 15 |
+
user_name: Optional[str] = None
|
| 16 |
+
error: Optional[str] = None
|
| 17 |
+
|
| 18 |
+
class LogCreateSchema(BaseModel):
|
| 19 |
+
user_request: Optional[str] = None
|
| 20 |
+
qe_result: Optional[str] = None
|
| 21 |
+
search_result: Optional[str] = None
|
| 22 |
+
llm_result: Optional[str] = None
|
| 23 |
+
llm_settings: Optional[str] = None
|
| 24 |
+
user_name: Optional[str] = None
|
| 25 |
+
error: Optional[str] = None
|
| 26 |
+
|
| 27 |
+
class LogFilterSchema(BaseModel):
|
| 28 |
+
user_name: Optional[str] = None
|
| 29 |
+
date_from: Optional[datetime] = None
|
| 30 |
+
date_to: Optional[datetime] = None
|
| 31 |
+
|
| 32 |
+
page: int = 1 # Номер страницы, по умолчанию 1
|
| 33 |
+
page_size: int = 50 # Размер страницы, по умолчанию 50
|
| 34 |
+
|
| 35 |
+
class Config:
|
| 36 |
+
json_schema_extra = {
|
| 37 |
+
"example": {
|
| 38 |
+
"user_name": "demo",
|
| 39 |
+
"date_from": "2024-01-01T00:00:00",
|
| 40 |
+
"date_to": "2026-12-31T23:59:59",
|
| 41 |
+
"page": 1,
|
| 42 |
+
"page_size": 50
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
class PaginatedLogResponse(BaseModel):
|
| 47 |
+
data: List[LogSchema]
|
| 48 |
+
total: int
|
| 49 |
+
page: int
|
| 50 |
+
page_size: int
|
| 51 |
+
total_pages: int
|