From 98bb7cbb9c34c3a22c5e031410ea3e65b47f9a23 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Thu, 12 Oct 2023 01:50:28 +0100 Subject: [PATCH 01/18] build: in progress agent --- src/assistant/alembic/env.py | 5 + ...-10-02_9749b063b095_added_initial_table.py | 37 --- .../2023-10-12_3d448c6327cd_init_commit.py | 171 +++++++++++++ src/assistant/lib/agent/__init__.py | 3 + src/assistant/lib/agent/chat_repository.py | 41 +++ src/assistant/lib/agent/models.py | 18 ++ src/assistant/lib/agent/openai_functions.py | 58 +++++ src/assistant/lib/agent/orm_models.py | 92 +++++++ src/assistant/lib/agent/repositories.py | 40 +++ src/assistant/lib/agent/services.py | 89 +++++++ src/assistant/lib/api/v1/handlers/__init__.py | 3 +- src/assistant/lib/api/v1/handlers/agent.py | 23 ++ src/assistant/lib/app/app.py | 4 + .../lib/app/split_settings/__init__.py | 1 - src/assistant/lib/models/__init__.py | 4 +- src/assistant/lib/models/chat_history.py | 9 + src/assistant/lib/models/orm/__init__.py | 3 - src/assistant/lib/orm_models/__init__.py | 13 + .../lib/{models/orm => orm_models}/base.py | 12 +- src/assistant/lib/orm_models/movies.py | 92 +++++++ src/assistant/poetry.lock | 241 +++++++++++++++++- src/assistant/pyproject.toml | 2 + 22 files changed, 906 insertions(+), 55 deletions(-) delete mode 100644 src/assistant/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py create mode 100644 src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py create mode 100644 src/assistant/lib/agent/__init__.py create mode 100644 src/assistant/lib/agent/chat_repository.py create mode 100644 src/assistant/lib/agent/models.py create mode 100644 src/assistant/lib/agent/openai_functions.py create mode 100644 src/assistant/lib/agent/orm_models.py create mode 100644 src/assistant/lib/agent/repositories.py create mode 100644 src/assistant/lib/agent/services.py create mode 100644 src/assistant/lib/api/v1/handlers/agent.py create mode 100644 src/assistant/lib/models/chat_history.py delete mode 100644 src/assistant/lib/models/orm/__init__.py create mode 100644 src/assistant/lib/orm_models/__init__.py rename src/assistant/lib/{models/orm => orm_models}/base.py (66%) create mode 100644 src/assistant/lib/orm_models/movies.py diff --git a/src/assistant/alembic/env.py b/src/assistant/alembic/env.py index 1786b59..b506519 100644 --- a/src/assistant/alembic/env.py +++ b/src/assistant/alembic/env.py @@ -7,6 +7,7 @@ from sqlalchemy.ext.asyncio import async_engine_from_config import lib.app.settings as app_settings import lib.models as models +import lib.orm_models as orm_models from alembic import context # this is the Alembic Config object, which provides @@ -19,6 +20,10 @@ if config.config_file_name is not None: config.set_main_option("sqlalchemy.url", app_settings.Settings().postgres.dsn) +print("BASE: ", orm_models.Base.metadata.schema) +for t in orm_models.Base.metadata.sorted_tables: + print(t.name) + target_metadata = models.Base.metadata diff --git a/src/assistant/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py b/src/assistant/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py deleted file mode 100644 index e300471..0000000 --- a/src/assistant/alembic/versions/2023-10-02_9749b063b095_added_initial_table.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Added initial table - -Revision ID: 9749b063b095 -Revises: -Create Date: 2023-10-02 19:46:05.078494 - -""" -from typing import Sequence, Union - -import sqlalchemy as sa - -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "9749b063b095" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "joke", - sa.Column("type", sa.String(), nullable=False), - sa.Column("setup", sa.String(), nullable=False), - sa.Column("punchline", sa.String(), nullable=False), - sa.Column("id", sa.Uuid(), nullable=False), - sa.PrimaryKeyConstraint("id"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("joke") - # ### end Alembic commands ### diff --git a/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py b/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py new file mode 100644 index 0000000..71b2cac --- /dev/null +++ b/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py @@ -0,0 +1,171 @@ +"""init commit + +Revision ID: 3d448c6327cd +Revises: +Create Date: 2023-10-12 00:01:42.248941 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "3d448c6327cd" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "chat_history", + sa.Column("id", sa.Uuid(), nullable=False), + sa.Column("session_id", sa.String(), nullable=False), + sa.Column("channel", sa.String(), nullable=False), + sa.Column("user_id", sa.String(), nullable=False), + sa.Column("content", sa.JSON(), nullable=False), + sa.Column("created", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), + sa.Column("modified", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False), + sa.PrimaryKeyConstraint("id"), + schema="content", + ) + op.drop_table("auth_group") + op.drop_table("auth_user_groups") + op.drop_table("auth_group_permissions") + op.drop_table("auth_user_user_permissions") + op.drop_table("auth_user") + op.drop_table("django_content_type") + op.drop_table("auth_permission") + op.drop_table("django_session") + op.drop_table("django_admin_log") + op.drop_table("django_migrations") + op.alter_column("film_work", "title", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) + op.alter_column("film_work", "description", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) + op.alter_column("film_work", "creation_date", existing_type=sa.DATE(), type_=sa.DateTime(), existing_nullable=True) + op.alter_column("film_work", "file_path", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) + op.alter_column("film_work", "type", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) + op.alter_column("film_work", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.alter_column("film_work", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.alter_column("genre", "name", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) + op.alter_column("genre", "description", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) + op.alter_column("genre", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.alter_column("genre", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.create_foreign_key(None, "genre_film_work", "genre", ["genre_id"], ["id"], referent_schema="content") + op.create_foreign_key(None, "genre_film_work", "film_work", ["film_work_id"], ["id"], referent_schema="content") + op.alter_column("person", "full_name", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) + op.alter_column("person", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.alter_column("person", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) + op.alter_column( + "person_film_work", "role", existing_type=sa.TEXT(), type_=sa.String(length=50), existing_nullable=False + ) + op.create_foreign_key(None, "person_film_work", "film_work", ["film_work_id"], ["id"], referent_schema="content") + op.create_foreign_key(None, "person_film_work", "person", ["person_id"], ["id"], referent_schema="content") + op.drop_column("person_film_work", "id") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column("person_film_work", sa.Column("id", sa.UUID(), autoincrement=False, nullable=False)) + op.drop_constraint(None, "person_film_work", type_="foreignkey") + op.drop_constraint(None, "person_film_work", type_="foreignkey") + op.alter_column( + "person_film_work", "role", existing_type=sa.String(length=50), type_=sa.TEXT(), existing_nullable=False + ) + op.alter_column("person", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("person", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("person", "full_name", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=False) + op.drop_constraint(None, "genre_film_work", type_="foreignkey") + op.drop_constraint(None, "genre_film_work", type_="foreignkey") + op.alter_column("genre", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("genre", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("genre", "description", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=True) + op.alter_column("genre", "name", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=False) + op.alter_column("film_work", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("film_work", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=True) + op.alter_column("film_work", "type", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=False) + op.alter_column("film_work", "file_path", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=True) + op.alter_column("film_work", "creation_date", existing_type=sa.DateTime(), type_=sa.DATE(), existing_nullable=True) + op.alter_column("film_work", "description", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=True) + op.alter_column("film_work", "title", existing_type=sa.String(), type_=sa.TEXT(), existing_nullable=False) + op.create_table( + "django_migrations", + sa.Column("id", sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column("app", sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column("name", sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column("applied", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False), + ) + op.create_table( + "django_admin_log", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("action_time", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False), + sa.Column("object_id", sa.TEXT(), autoincrement=False, nullable=True), + sa.Column("object_repr", sa.VARCHAR(length=200), autoincrement=False, nullable=False), + sa.Column("action_flag", sa.SMALLINT(), autoincrement=False, nullable=False), + sa.Column("change_message", sa.TEXT(), autoincrement=False, nullable=False), + sa.Column("content_type_id", sa.INTEGER(), autoincrement=False, nullable=True), + sa.Column("user_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.CheckConstraint("action_flag >= 0", name="django_admin_log_action_flag_check"), + ) + op.create_table( + "django_session", + sa.Column("session_key", sa.VARCHAR(length=40), autoincrement=False, nullable=False), + sa.Column("session_data", sa.TEXT(), autoincrement=False, nullable=False), + sa.Column("expire_date", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_permission", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("name", sa.VARCHAR(length=255), autoincrement=False, nullable=False), + sa.Column("content_type_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("codename", sa.VARCHAR(length=100), autoincrement=False, nullable=False), + ) + op.create_table( + "django_content_type", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("app_label", sa.VARCHAR(length=100), autoincrement=False, nullable=False), + sa.Column("model", sa.VARCHAR(length=100), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_user", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("password", sa.VARCHAR(length=128), autoincrement=False, nullable=False), + sa.Column("last_login", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True), + sa.Column("is_superuser", sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column("username", sa.VARCHAR(length=150), autoincrement=False, nullable=False), + sa.Column("first_name", sa.VARCHAR(length=150), autoincrement=False, nullable=False), + sa.Column("last_name", sa.VARCHAR(length=150), autoincrement=False, nullable=False), + sa.Column("email", sa.VARCHAR(length=254), autoincrement=False, nullable=False), + sa.Column("is_staff", sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column("is_active", sa.BOOLEAN(), autoincrement=False, nullable=False), + sa.Column("date_joined", postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_user_user_permissions", + sa.Column("id", sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column("user_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("permission_id", sa.INTEGER(), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_group_permissions", + sa.Column("id", sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column("group_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("permission_id", sa.INTEGER(), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_user_groups", + sa.Column("id", sa.BIGINT(), autoincrement=True, nullable=False), + sa.Column("user_id", sa.INTEGER(), autoincrement=False, nullable=False), + sa.Column("group_id", sa.INTEGER(), autoincrement=False, nullable=False), + ) + op.create_table( + "auth_group", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("name", sa.VARCHAR(length=150), autoincrement=False, nullable=False), + ) + op.drop_table("chat_history", schema="content") + # ### end Alembic commands ### diff --git a/src/assistant/lib/agent/__init__.py b/src/assistant/lib/agent/__init__.py new file mode 100644 index 0000000..ff2f391 --- /dev/null +++ b/src/assistant/lib/agent/__init__.py @@ -0,0 +1,3 @@ +from .chat_repository import ChatHistoryRepository + +__all__ = ["ChatHistoryRepository"] diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/chat_repository.py new file mode 100644 index 0000000..4fd311b --- /dev/null +++ b/src/assistant/lib/agent/chat_repository.py @@ -0,0 +1,41 @@ +import logging +import uuid + +import sqlalchemy as sa +import sqlalchemy.exc +import sqlalchemy.ext.asyncio as sa_asyncio + +import lib.models as models +import lib.orm_models as orm_models + + +class ChatHistoryRepository: + def __init__(self, pg_async_session: sa_asyncio.async_sessionmaker[sa_asyncio.AsyncSession]) -> None: + self.pg_async_session = pg_async_session + self.logger = logging.getLogger(__name__) + + async def get_last_session_id(self, request: models.RequestLastSessionId) -> uuid.UUID | None: + """Get a new session ID.""" + + try: + async with self.pg_async_session() as session: + statement = ( + sa.select(orm_models.ChatHistory) + .filter_by(channel=request.channel, user_id=request.user_id) + .filter( + ( + sa.func.extract("epoch", orm_models.ChatHistory.created) + - sa.func.extract("epoch", orm_models.ChatHistory.modified) / 60 + ) + <= request.minutes_ago + ) + .order_by(orm_models.ChatHistory.created.desc()) + .limit(1) + ) + result = await session.execute(statement) + + chat_session = result.scalars().first() + if chat_session: + return chat_session.id + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) diff --git a/src/assistant/lib/agent/models.py b/src/assistant/lib/agent/models.py new file mode 100644 index 0000000..a1fc33c --- /dev/null +++ b/src/assistant/lib/agent/models.py @@ -0,0 +1,18 @@ +import datetime +import uuid + +import pydantic + + +class Movie(pydantic.BaseModel): + id: uuid.UUID + title: str + description: str | None = None + rating: float + type: str + created: datetime.datetime + modified: datetime.datetime + + +class Embedding(pydantic.RootModel[list[float]]): + root: list[float] diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/openai_functions.py new file mode 100644 index 0000000..4da4362 --- /dev/null +++ b/src/assistant/lib/agent/openai_functions.py @@ -0,0 +1,58 @@ +import logging +import uuid + +import langchain.agents +import models +import orm_models +import sqlalchemy as sa +import sqlalchemy.exc +import sqlalchemy.ext.asyncio as sa_asyncio + +import lib.agent.repositories as repositories + + +class OpenAIFunctions: + """OpenAI Functions for langchain agents.""" + + def __init__( + self, + repository: repositories.EmbeddingRepository, + pg_async_session: sa_asyncio.async_sessionmaker[sa_asyncio.AsyncSession], + ) -> None: + self.logger = logging.getLogger(__name__) + self.pg_async_session = pg_async_session + self.repository = repository + + @langchain.agents.tool + async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: + """Returns a movie data by description.""" + + self.logger.info("Request to get movie by description: %s", description) + embedded_description = await self.repository.aget_embedding(description) + try: + async with self.pg_async_session() as session: + result: list[models.Movie] = [] + stmt = ( + sa.select(orm_models.FilmWork) + .order_by(orm_models.FilmWork.embedding.cosine_distance(embedded_description)) + .limit(5) + ) + neighbours = session.scalars(stmt) + for neighbour in await neighbours: + print(neighbour.title) + result.append(models.Movie(**neighbour.__dict__)) + return result + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) + + @langchain.agents.tool + def get_movie_by_id(self, id: uuid.UUID) -> models.Movie | None: + """Returns a movie data by movie id.""" + self.logger.info("Request to get movie by id: %s", id) + return None + + @langchain.agents.tool + def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None: + """Returns a similar movies to movie with movie id.""" + self.logger.info("Request to get movie by id: %s", id) + return None diff --git a/src/assistant/lib/agent/orm_models.py b/src/assistant/lib/agent/orm_models.py new file mode 100644 index 0000000..fe61e56 --- /dev/null +++ b/src/assistant/lib/agent/orm_models.py @@ -0,0 +1,92 @@ +import datetime +import uuid + +import pgvector.sqlalchemy +import sqlalchemy as sa +import sqlalchemy.orm as sa_orm +import sqlalchemy.sql as sa_sql + +import lib.models.orm as base_models + + +class Genre(base_models.Base): + __tablename__: str = "genre" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + name: sa_orm.Mapped[str] = sa_orm.mapped_column() + description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + + +class Person(base_models.Base): + __tablename__: str = "person" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + full_name: sa_orm.Mapped[str] = sa_orm.mapped_column() + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + + +class FilmWork(base_models.Base): + __tablename__: str = "film_work" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + title: sa_orm.Mapped[str] + description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + creation_date: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(nullable=True) + file_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + rating: sa_orm.Mapped[float] = sa_orm.mapped_column(nullable=True) + type: sa_orm.Mapped[str] = sa_orm.mapped_column() + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + embedding: sa_orm.Mapped[list[float]] = sa_orm.mapped_column(pgvector.sqlalchemy.Vector(1536)) + genres: sa_orm.Mapped[list[Genre]] = sa_orm.relationship(secondary="genre_film_work") + + +GenreFilmWork = sa.Table( + "genre_film_work", + base_models.Base.metadata, + sa.Column("id", sa.UUID, primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("genre_id", sa.ForeignKey(Genre.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), +) + + +PersonFilmWork = sa.Table( + "person_film_work", + base_models.Base.metadata, + sa.Column("person_id", sa.ForeignKey(Person.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("role", sa.String(50), nullable=False), + sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), +) + + +class ChatHistory(base_models.Base): + __tablename__: str = "chat_history" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + session_id: sa_orm.Mapped[str] = sa_orm.mapped_column() + channel: sa_orm.Mapped[str] = sa_orm.mapped_column() + user_id: sa_orm.Mapped[str] = sa_orm.mapped_column() + content: sa_orm.Mapped[sa.JSON] = sa_orm.mapped_column(sa.JSON) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) diff --git a/src/assistant/lib/agent/repositories.py b/src/assistant/lib/agent/repositories.py new file mode 100644 index 0000000..af25bbe --- /dev/null +++ b/src/assistant/lib/agent/repositories.py @@ -0,0 +1,40 @@ +import logging +import typing + +import openai +import openai.error + +import lib.agent.models as models +import lib.app.settings as app_settings + + +class EmbeddingRepository: + """A service for getting embeddings from OpenAI.""" + + def __init__(self, settings: app_settings.Settings) -> None: + """Initialize the service with an OpenAI API key.""" + self.llm = openai.api_key = settings.openai.api_key + self.logger = logging.getLogger(__name__) + + def get_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None: + """Get the embedding for a given text.""" + try: + response: dict[str, typing.Any] = openai.Embedding.create( + input=text, + model=model, + ) # type: ignore[reportGeneralTypeIssues] + return response["data"][0]["embedding"] + except openai.error.OpenAIError: + self.logger.exception("Failed to get async embedding for: %s", text) + + async def aget_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None: + """Get the embedding for a given text.""" + try: + response: dict[str, typing.Any] = await openai.Embedding.acreate( + input=text, + model=model, + ) # type: ignore[reportGeneralTypeIssues] + return models.Embedding(**response["data"][0]["embedding"]) + + except openai.error.OpenAIError: + self.logger.exception("Failed to get async embedding for: %s", text) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py new file mode 100644 index 0000000..f86248a --- /dev/null +++ b/src/assistant/lib/agent/services.py @@ -0,0 +1,89 @@ +import asyncio +import logging +import uuid + +import langchain.agents +import langchain.agents.format_scratchpad +import langchain.agents.output_parsers +import langchain.chat_models +import langchain.prompts +import langchain.schema.agent +import langchain.schema.messages +import langchain.tools.render +import models +import varname + +import lib.agent.openai_functions as openai_functions +import lib.app.settings as app_settings + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + + +class AgentService: + def __init__(self, settings: app_settings.Settings, tools: openai_functions.OpenAIFunctions) -> None: + self.settings = settings + self.tools = tools + + async def process_request(self, request: str, chat_history: list[langchain.schema.messages.Message]) -> str: + llm = langchain.chat_models.ChatOpenAI(temperature=0.7, openai_api_key=self.settings.openai.api_key) + tools = [self.tools.get_movie_by_description, self.tools.get_movie_by_id, self.tools.get_similar_movies] + + chat_history = [] + chat_history_name = f"{chat_history=}".partition("=")[0] + prompt = langchain.prompts.ChatPromptTemplate.from_messages( + [ + ( + "system", + "You are very powerful assistant. If you are asked about movies you will you provided functions.", + ), + langchain.prompts.MessagesPlaceholder(variable_name=chat_history_name), + ("user", "{input}"), + langchain.prompts.MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + + llm_with_tools = llm.bind( + functions=[langchain.tools.render.format_tool_to_openai_function(tool) for tool in tools] + ) + + chat_history = [] + + agent = ( + { + "input": lambda _: _["input"], + "agent_scratchpad": lambda _: langchain.agents.format_scratchpad.format_to_openai_functions( + _["intermediate_steps"] + ), + "chat_history": lambda _: _["chat_history"], + } + | prompt + | llm_with_tools + | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() + ) + + agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) + + return await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) + + +# async def main(): +# agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) + +# # first_question = "What is the movie where halfling bring the ring to the volcano?" +# first_question = ( +# "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" +# ) +# second_question = "So what is the rating of the movie? Do you recommend it?" +# third_question = "What are the similar movies?" +# first_result = await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) +# chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) +# chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) +# second_result = await agent_executor.ainvoke({"input": second_question, "chat_history": chat_history}) +# chat_history.append(langchain.schema.messages.HumanMessage(content=second_question)) +# chat_history.append(langchain.schema.messages.AIMessage(content=second_result["output"])) +# final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history}) + + +# if __name__ == "__main__": +# asyncio.run(main()) diff --git a/src/assistant/lib/api/v1/handlers/__init__.py b/src/assistant/lib/api/v1/handlers/__init__.py index 4711f4b..c312d05 100644 --- a/src/assistant/lib/api/v1/handlers/__init__.py +++ b/src/assistant/lib/api/v1/handlers/__init__.py @@ -1,3 +1,4 @@ +from .agent import AgentHandler from .health import basic_router -__all__ = ["basic_router"] +__all__ = ["AgentHandler", "basic_router"] diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py new file mode 100644 index 0000000..40d90be --- /dev/null +++ b/src/assistant/lib/api/v1/handlers/agent.py @@ -0,0 +1,23 @@ +import fastapi + +import lib.agent as agent +import lib.models as models + + +class AgentHandler: + def __init__(self, chat_history_repository: agent.ChatHistoryRepository): + self.chat_history_repository = chat_history_repository + self.router = fastapi.APIRouter() + self.router.add_api_route( + "/", + self.get_agent, + methods=["GET"], + summary="Статус работоспособности", + description="Проверяет доступность сервиса FastAPI.", + ) + + async def get_agent(self): + request = models.RequestLastSessionId(channel="test", user_id="test", minutes_ago=3) + response = await self.chat_history_repository.get_last_session_id(request=request) + print("RESPONSE: ", response) + return {"response": response} diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index c6183d7..0656b32 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -6,6 +6,7 @@ import typing import fastapi import uvicorn +import lib.agent as agent import lib.api.v1.handlers as api_v1_handlers import lib.app.errors as app_errors import lib.app.settings as app_settings @@ -73,6 +74,7 @@ class Application: logger.info("Initializing repositories") stt_repository: stt.STTProtocol = stt.OpenaiSpeechRepository(settings=settings) + chat_history_repository = agent.ChatHistoryRepository(pg_async_session=postgres_client.get_async_session()) # Caches @@ -87,6 +89,7 @@ class Application: logger.info("Initializing handlers") liveness_probe_handler = api_v1_handlers.basic_router + agent_handler = api_v1_handlers.AgentHandler(chat_history_repository=chat_history_repository).router logger.info("Creating application") @@ -100,6 +103,7 @@ class Application: # Routes fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) + fastapi_app.include_router(agent_handler, prefix="/api/v1/agent", tags=["testing"]) application = Application( settings=settings, diff --git a/src/assistant/lib/app/split_settings/__init__.py b/src/assistant/lib/app/split_settings/__init__.py index 90343d5..3aa53b4 100644 --- a/src/assistant/lib/app/split_settings/__init__.py +++ b/src/assistant/lib/app/split_settings/__init__.py @@ -7,7 +7,6 @@ from .project import * from .proxy import * from .voice import * - __all__ = [ "ApiSettings", "AppSettings", diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index 8641d86..a7cb462 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -1,4 +1,4 @@ -from .orm import Base, IdCreatedUpdatedBaseMixin +from .chat_history import RequestLastSessionId from .token import Token -__all__ = ["Base", "IdCreatedUpdatedBaseMixin", "Token"] +__all__ = ["RequestLastSessionId", "Token"] diff --git a/src/assistant/lib/models/chat_history.py b/src/assistant/lib/models/chat_history.py new file mode 100644 index 0000000..264eaa5 --- /dev/null +++ b/src/assistant/lib/models/chat_history.py @@ -0,0 +1,9 @@ +import pydantic + + +class RequestLastSessionId(pydantic.BaseModel): + """Request for a new session ID.""" + + channel: str + user_id: str + minutes_ago: int diff --git a/src/assistant/lib/models/orm/__init__.py b/src/assistant/lib/models/orm/__init__.py deleted file mode 100644 index 0a1fd51..0000000 --- a/src/assistant/lib/models/orm/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .base import Base, IdCreatedUpdatedBaseMixin - -__all__ = ["Base", "IdCreatedUpdatedBaseMixin"] diff --git a/src/assistant/lib/orm_models/__init__.py b/src/assistant/lib/orm_models/__init__.py new file mode 100644 index 0000000..afb551e --- /dev/null +++ b/src/assistant/lib/orm_models/__init__.py @@ -0,0 +1,13 @@ +from .base import Base, IdCreatedUpdatedBaseMixin +from .movies import ChatHistory, FilmWork, Genre, GenreFilmWork, Person, PersonFilmWork + +__all__ = [ + "Base", + "ChatHistory", + "FilmWork", + "Genre", + "GenreFilmWork", + "IdCreatedUpdatedBaseMixin", + "Person", + "PersonFilmWork", +] diff --git a/src/assistant/lib/models/orm/base.py b/src/assistant/lib/orm_models/base.py similarity index 66% rename from src/assistant/lib/models/orm/base.py rename to src/assistant/lib/orm_models/base.py index 3d1ace3..b3929f6 100644 --- a/src/assistant/lib/models/orm/base.py +++ b/src/assistant/lib/orm_models/base.py @@ -16,20 +16,12 @@ class Base(sa_orm.DeclarativeBase): return cls.__name__.lower() __mapper_args__ = {"eager_defaults": True} - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + __table_args__ = {"schema": "content"} class IdCreatedUpdatedBaseMixin: - # id: sa_orm.Mapped[int] = sa_orm.mapped_column(primary_key=True) - # id_field: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(name="uuid", primary_key=True, unique=True, default=uuid.uuid4, nullable=False) + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(server_default=sa_sql.func.now()) updated: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() ) - - # __mapper_args__ = {"eager_defaults": True} - - # @sqlalchemy.ext.declarative.declared_attr.directive - # def __tablename__(cls) -> str: - # return cls.__name__.lower() diff --git a/src/assistant/lib/orm_models/movies.py b/src/assistant/lib/orm_models/movies.py new file mode 100644 index 0000000..ee26811 --- /dev/null +++ b/src/assistant/lib/orm_models/movies.py @@ -0,0 +1,92 @@ +import datetime +import uuid + +import pgvector.sqlalchemy +import sqlalchemy as sa +import sqlalchemy.orm as sa_orm +import sqlalchemy.sql as sa_sql + +import lib.orm_models.base as base_models + + +class Genre(base_models.Base): + __tablename__: str = "genre" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + name: sa_orm.Mapped[str] = sa_orm.mapped_column() + description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + + +class Person(base_models.Base): + __tablename__: str = "person" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + full_name: sa_orm.Mapped[str] = sa_orm.mapped_column() + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + + +class FilmWork(base_models.Base): + __tablename__: str = "film_work" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + title: sa_orm.Mapped[str] + description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + creation_date: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(nullable=True) + file_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) + rating: sa_orm.Mapped[float] = sa_orm.mapped_column(nullable=True) + type: sa_orm.Mapped[str] = sa_orm.mapped_column() + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) + embedding: sa_orm.Mapped[list[float]] = sa_orm.mapped_column(pgvector.sqlalchemy.Vector(1536)) + genres: sa_orm.Mapped[list[Genre]] = sa_orm.relationship(secondary="genre_film_work") + + +GenreFilmWork = sa.Table( + "genre_film_work", + base_models.Base.metadata, + sa.Column("id", sa.UUID, primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("genre_id", sa.ForeignKey(Genre.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), +) + + +PersonFilmWork = sa.Table( + "person_film_work", + base_models.Base.metadata, + sa.Column("person_id", sa.ForeignKey(Person.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] + sa.Column("role", sa.String(50), nullable=False), + sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), +) + + +class ChatHistory(base_models.Base): + __tablename__: str = "chat_history" + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + session_id: sa_orm.Mapped[str] = sa_orm.mapped_column() + channel: sa_orm.Mapped[str] = sa_orm.mapped_column() + user_id: sa_orm.Mapped[str] = sa_orm.mapped_column() + content: sa_orm.Mapped[sa.JSON] = sa_orm.mapped_column(sa.JSON) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) diff --git a/src/assistant/poetry.lock b/src/assistant/poetry.lock index 055d58f..d5810e4 100644 --- a/src/assistant/poetry.lock +++ b/src/assistant/poetry.lock @@ -448,6 +448,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataclasses-json" +version = "0.6.1" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "dataclasses_json-0.6.1-py3-none-any.whl", hash = "sha256:1bd8418a61fe3d588bb0079214d7fb71d44937da40742b787256fd53b26b6c80"}, + {file = "dataclasses_json-0.6.1.tar.gz", hash = "sha256:a53c220c35134ce08211a1057fd0e5bf76dc5331627c6b241cacbc570a89faae"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + [[package]] name = "dill" version = "0.3.7" @@ -775,6 +790,85 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "langchain" +version = "0.0.312" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain-0.0.312-py3-none-any.whl", hash = "sha256:2c7ea6e80195b8747c25ca4b905bd4814f26f47719a27edcb369cb6cd2186df3"}, + {file = "langchain-0.0.312.tar.gz", hash = "sha256:4629233c158f23dcfb0cbc249b27d7d8bde1e71ce1d8972d53ae54c7504fc78a"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +anyio = "<4.0" +dataclasses-json = ">=0.5.7,<0.7" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.0.43,<0.1.0" +numpy = ">=1,<2" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.6.8,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "libdeeplake (>=0.0.60,<0.0.61)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.10.1,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"] +clarifai = ["clarifai (>=9.1.0)"] +cli = ["typer (>=0.9.0,<0.10.0)"] +cohere = ["cohere (>=4,<5)"] +docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] +embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-schema-pydantic (>=1.2,<2.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +javascript = ["esprima (>=4.0.1,<5.0.0)"] +llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] +openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +text-helpers = ["chardet (>=5.1.0,<6.0.0)"] + +[[package]] +name = "langsmith" +version = "0.0.43" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langsmith-0.0.43-py3-none-any.whl", hash = "sha256:27854bebdae6a35c88e1c1172e6abba27592287b70511aca2a953a59fade0e87"}, + {file = "langsmith-0.0.43.tar.gz", hash = "sha256:f7705f13eb8ce3b8eb16c4d2b2760c62cfb9a3b3ab6aa0728afa84d26b2a6e55"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + [[package]] name = "lazy-object-proxy" version = "1.9.0" @@ -898,6 +992,26 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "marshmallow" +version = "3.20.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.7.0" @@ -1017,6 +1131,40 @@ files = [ [package.dependencies] setuptools = "*" +[[package]] +name = "numpy" +version = "1.25.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +files = [ + {file = "numpy-1.25.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db3ccc4e37a6873045580d413fe79b68e47a681af8db2e046f1dacfa11f86eb3"}, + {file = "numpy-1.25.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:90319e4f002795ccfc9050110bbbaa16c944b1c37c0baeea43c5fb881693ae1f"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe4a913e29b418d096e696ddd422d8a5d13ffba4ea91f9f60440a3b759b0187"}, + {file = "numpy-1.25.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f08f2e037bba04e707eebf4bc934f1972a315c883a9e0ebfa8a7756eabf9e357"}, + {file = "numpy-1.25.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bec1e7213c7cb00d67093247f8c4db156fd03075f49876957dca4711306d39c9"}, + {file = "numpy-1.25.2-cp310-cp310-win32.whl", hash = "sha256:7dc869c0c75988e1c693d0e2d5b26034644399dd929bc049db55395b1379e044"}, + {file = "numpy-1.25.2-cp310-cp310-win_amd64.whl", hash = "sha256:834b386f2b8210dca38c71a6e0f4fd6922f7d3fcff935dbe3a570945acb1b545"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5462d19336db4560041517dbb7759c21d181a67cb01b36ca109b2ae37d32418"}, + {file = "numpy-1.25.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c5652ea24d33585ea39eb6a6a15dac87a1206a692719ff45d53c5282e66d4a8f"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2"}, + {file = "numpy-1.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:60e7f0f7f6d0eee8364b9a6304c2845b9c491ac706048c7e8cf47b83123b8dbf"}, + {file = "numpy-1.25.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bb33d5a1cf360304754913a350edda36d5b8c5331a8237268c48f91253c3a364"}, + {file = "numpy-1.25.2-cp311-cp311-win32.whl", hash = "sha256:5883c06bb92f2e6c8181df7b39971a5fb436288db58b5a1c3967702d4278691d"}, + {file = "numpy-1.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:5c97325a0ba6f9d041feb9390924614b60b99209a71a69c876f71052521d42a4"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b79e513d7aac42ae918db3ad1341a015488530d0bb2a6abcbdd10a3a829ccfd3"}, + {file = "numpy-1.25.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:eb942bfb6f84df5ce05dbf4b46673ffed0d3da59f13635ea9b926af3deb76926"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e0746410e73384e70d286f93abf2520035250aad8c5714240b0492a7302fdca"}, + {file = "numpy-1.25.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7806500e4f5bdd04095e849265e55de20d8cc4b661b038957354327f6d9b295"}, + {file = "numpy-1.25.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8b77775f4b7df768967a7c8b3567e309f617dd5e99aeb886fa14dc1a0791141f"}, + {file = "numpy-1.25.2-cp39-cp39-win32.whl", hash = "sha256:2792d23d62ec51e50ce4d4b7d73de8f67a2fd3ea710dcbc8563a51a03fb07b01"}, + {file = "numpy-1.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:76b4115d42a7dfc5d485d358728cdd8719be33cc5ec6ec08632a5d6fca2ed380"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1a1329e26f46230bf77b02cc19e900db9b52f398d6722ca853349a782d4cff55"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c3abc71e8b6edba80a01a52e66d83c5d14433cbcd26a40c329ec7ed09f37901"}, + {file = "numpy-1.25.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1b9735c27cea5d995496f46a8b1cd7b408b3f34b6d50459d9ac8fe3a20cc17bf"}, + {file = "numpy-1.25.2.tar.gz", hash = "sha256:fd608e19c8d7c55021dffd43bfe5492fab8cc105cc8986f813f8c3c048b38760"}, +] + [[package]] name = "openai" version = "0.28.1" @@ -1084,6 +1232,19 @@ files = [ {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] +[[package]] +name = "pgvector" +version = "0.2.3" +description = "pgvector support for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pgvector-0.2.3-py2.py3-none-any.whl", hash = "sha256:9d53dc01138ecc7c9aca64e4680cfa9edf4c38f9cb8ed7098317871fdd211824"}, +] + +[package.dependencies] +numpy = "*" + [[package]] name = "platformdirs" version = "3.11.0" @@ -1521,6 +1682,55 @@ files = [ [package.dependencies] tokenize-rt = ">=5.2.0" +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1729,6 +1939,20 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, + {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + [[package]] name = "tokenize-rt" version = "5.2.0" @@ -1796,6 +2020,21 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + [[package]] name = "urllib3" version = "2.0.6" @@ -2005,4 +2244,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "157772e2b31120df8d850525c30b56685e3db6c5318f6fba677c6cfcd1f0e467" +content-hash = "2d865a52e2e48b9700ca3f14caa3cbbc05c3ad3965866ab397cdd11e74958829" diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index 0edf988..66ace5b 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -26,8 +26,10 @@ dill = "^0.3.7" fastapi = "0.103.1" greenlet = "^2.0.2" httpx = "^0.25.0" +langchain = "^0.0.312" openai = "^0.28.1" orjson = "^3.9.7" +pgvector = "^0.2.3" psycopg2-binary = "^2.9.9" pydantic = {extras = ["email"], version = "^2.3.0"} pydantic-settings = "^2.0.3" From b75af6034b47877ad6e639e4aedf9d413d1bace6 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Thu, 12 Oct 2023 02:02:39 +0100 Subject: [PATCH 02/18] fix: fixing types partially --- src/assistant/lib/agent/chat_repository.py | 4 +- src/assistant/lib/agent/openai_functions.py | 10 +- src/assistant/lib/agent/orm_models.py | 92 ------------------- src/assistant/lib/agent/repositories.py | 4 +- src/assistant/lib/agent/services.py | 3 +- src/assistant/lib/models/__init__.py | 4 +- src/assistant/lib/models/embedding.py | 5 + .../lib/{agent/models.py => models/movies.py} | 4 - src/assistant/lib/orm_models/movies.py | 10 +- src/assistant/pyproject.toml | 3 +- 10 files changed, 25 insertions(+), 114 deletions(-) delete mode 100644 src/assistant/lib/agent/orm_models.py create mode 100644 src/assistant/lib/models/embedding.py rename src/assistant/lib/{agent/models.py => models/movies.py} (76%) diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/chat_repository.py index 4fd311b..435b128 100644 --- a/src/assistant/lib/agent/chat_repository.py +++ b/src/assistant/lib/agent/chat_repository.py @@ -31,9 +31,9 @@ class ChatHistoryRepository: ) .order_by(orm_models.ChatHistory.created.desc()) .limit(1) - ) + ) result = await session.execute(statement) - + chat_session = result.scalars().first() if chat_session: return chat_session.id diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/openai_functions.py index 4da4362..673a1ba 100644 --- a/src/assistant/lib/agent/openai_functions.py +++ b/src/assistant/lib/agent/openai_functions.py @@ -2,13 +2,13 @@ import logging import uuid import langchain.agents -import models import orm_models import sqlalchemy as sa import sqlalchemy.exc import sqlalchemy.ext.asyncio as sa_asyncio import lib.agent.repositories as repositories +import lib.models as models class OpenAIFunctions: @@ -25,7 +25,7 @@ class OpenAIFunctions: @langchain.agents.tool async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: - """Returns a movie data by description.""" + """Provide a movie data by description.""" self.logger.info("Request to get movie by description: %s", description) embedded_description = await self.repository.aget_embedding(description) @@ -39,7 +39,6 @@ class OpenAIFunctions: ) neighbours = session.scalars(stmt) for neighbour in await neighbours: - print(neighbour.title) result.append(models.Movie(**neighbour.__dict__)) return result except sqlalchemy.exc.SQLAlchemyError as error: @@ -47,12 +46,13 @@ class OpenAIFunctions: @langchain.agents.tool def get_movie_by_id(self, id: uuid.UUID) -> models.Movie | None: - """Returns a movie data by movie id.""" + """Provide a movie data by movie id.""" self.logger.info("Request to get movie by id: %s", id) return None @langchain.agents.tool def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None: - """Returns a similar movies to movie with movie id.""" + """Provide similar movies for the given movie ID.""" + self.logger.info("Request to get movie by id: %s", id) return None diff --git a/src/assistant/lib/agent/orm_models.py b/src/assistant/lib/agent/orm_models.py deleted file mode 100644 index fe61e56..0000000 --- a/src/assistant/lib/agent/orm_models.py +++ /dev/null @@ -1,92 +0,0 @@ -import datetime -import uuid - -import pgvector.sqlalchemy -import sqlalchemy as sa -import sqlalchemy.orm as sa_orm -import sqlalchemy.sql as sa_sql - -import lib.models.orm as base_models - - -class Genre(base_models.Base): - __tablename__: str = "genre" - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - name: sa_orm.Mapped[str] = sa_orm.mapped_column() - description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) - created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now() - ) - modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() - ) - - -class Person(base_models.Base): - __tablename__: str = "person" - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - full_name: sa_orm.Mapped[str] = sa_orm.mapped_column() - created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now() - ) - modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() - ) - - -class FilmWork(base_models.Base): - __tablename__: str = "film_work" - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - title: sa_orm.Mapped[str] - description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) - creation_date: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(nullable=True) - file_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) - rating: sa_orm.Mapped[float] = sa_orm.mapped_column(nullable=True) - type: sa_orm.Mapped[str] = sa_orm.mapped_column() - created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now() - ) - modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() - ) - embedding: sa_orm.Mapped[list[float]] = sa_orm.mapped_column(pgvector.sqlalchemy.Vector(1536)) - genres: sa_orm.Mapped[list[Genre]] = sa_orm.relationship(secondary="genre_film_work") - - -GenreFilmWork = sa.Table( - "genre_film_work", - base_models.Base.metadata, - sa.Column("id", sa.UUID, primary_key=True), # type: ignore[reportUnknownVariableType] - sa.Column("genre_id", sa.ForeignKey(Genre.id), primary_key=True), # type: ignore[reportUnknownVariableType] - sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] - sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), -) - - -PersonFilmWork = sa.Table( - "person_film_work", - base_models.Base.metadata, - sa.Column("person_id", sa.ForeignKey(Person.id), primary_key=True), # type: ignore[reportUnknownVariableType] - sa.Column("film_work_id", sa.ForeignKey(FilmWork.id), primary_key=True), # type: ignore[reportUnknownVariableType] - sa.Column("role", sa.String(50), nullable=False), - sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), -) - - -class ChatHistory(base_models.Base): - __tablename__: str = "chat_history" - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - session_id: sa_orm.Mapped[str] = sa_orm.mapped_column() - channel: sa_orm.Mapped[str] = sa_orm.mapped_column() - user_id: sa_orm.Mapped[str] = sa_orm.mapped_column() - content: sa_orm.Mapped[sa.JSON] = sa_orm.mapped_column(sa.JSON) - created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now() - ) - modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() - ) diff --git a/src/assistant/lib/agent/repositories.py b/src/assistant/lib/agent/repositories.py index af25bbe..f8fe6f1 100644 --- a/src/assistant/lib/agent/repositories.py +++ b/src/assistant/lib/agent/repositories.py @@ -4,8 +4,8 @@ import typing import openai import openai.error -import lib.agent.models as models import lib.app.settings as app_settings +import lib.models as models class EmbeddingRepository: @@ -23,7 +23,7 @@ class EmbeddingRepository: input=text, model=model, ) # type: ignore[reportGeneralTypeIssues] - return response["data"][0]["embedding"] + return models.Embedding(**response["data"][0]["embedding"]) except openai.error.OpenAIError: self.logger.exception("Failed to get async embedding for: %s", text) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index f86248a..83f89a4 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -10,9 +10,8 @@ import langchain.prompts import langchain.schema.agent import langchain.schema.messages import langchain.tools.render -import models -import varname +import assistant.lib.models.movies as movies import lib.agent.openai_functions as openai_functions import lib.app.settings as app_settings diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index a7cb462..1995cc2 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -1,4 +1,6 @@ from .chat_history import RequestLastSessionId +from .embedding import Embedding +from .movies import Movie from .token import Token -__all__ = ["RequestLastSessionId", "Token"] +__all__ = ["Embedding", "Movie", "RequestLastSessionId", "Token"] diff --git a/src/assistant/lib/models/embedding.py b/src/assistant/lib/models/embedding.py new file mode 100644 index 0000000..3978dc9 --- /dev/null +++ b/src/assistant/lib/models/embedding.py @@ -0,0 +1,5 @@ +import pydantic + + +class Embedding(pydantic.RootModel[list[float]]): + root: list[float] diff --git a/src/assistant/lib/agent/models.py b/src/assistant/lib/models/movies.py similarity index 76% rename from src/assistant/lib/agent/models.py rename to src/assistant/lib/models/movies.py index a1fc33c..e432111 100644 --- a/src/assistant/lib/agent/models.py +++ b/src/assistant/lib/models/movies.py @@ -12,7 +12,3 @@ class Movie(pydantic.BaseModel): type: str created: datetime.datetime modified: datetime.datetime - - -class Embedding(pydantic.RootModel[list[float]]): - root: list[float] diff --git a/src/assistant/lib/orm_models/movies.py b/src/assistant/lib/orm_models/movies.py index ee26811..b9f8167 100644 --- a/src/assistant/lib/orm_models/movies.py +++ b/src/assistant/lib/orm_models/movies.py @@ -10,7 +10,7 @@ import lib.orm_models.base as base_models class Genre(base_models.Base): - __tablename__: str = "genre" + __tablename__: str = "genre" # type: ignore[reportIncompatibleVariableOverride] id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) name: sa_orm.Mapped[str] = sa_orm.mapped_column() @@ -24,7 +24,7 @@ class Genre(base_models.Base): class Person(base_models.Base): - __tablename__: str = "person" + __tablename__: str = "person" # type: ignore[reportIncompatibleVariableOverride] id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) full_name: sa_orm.Mapped[str] = sa_orm.mapped_column() @@ -37,10 +37,10 @@ class Person(base_models.Base): class FilmWork(base_models.Base): - __tablename__: str = "film_work" + __tablename__: str = "film_work" # type: ignore[reportIncompatibleVariableOverride] id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - title: sa_orm.Mapped[str] + title: sa_orm.Mapped[str] = sa_orm.mapped_column() description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) creation_date: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(nullable=True) file_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) @@ -77,7 +77,7 @@ PersonFilmWork = sa.Table( class ChatHistory(base_models.Base): - __tablename__: str = "chat_history" + __tablename__: str = "chat_history" # type: ignore[reportIncompatibleVariableOverride] id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) session_id: sa_orm.Mapped[str] = sa_orm.mapped_column() diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index 66ace5b..dfdfe2e 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -92,7 +92,8 @@ variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$" [tool.pyright] exclude = [ - ".venv" + ".venv", + "alembic" ] pythonPlatform = "All" pythonVersion = "3.11" From 2db4a87bf4e682eff59f0c8c41e7e03f54c6dafc Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Thu, 12 Oct 2023 02:37:30 +0100 Subject: [PATCH 03/18] fix: work in progess: models & migrations & repos --- src/assistant/alembic/env.py | 3 +- .../2023-10-12_3d448c6327cd_init_commit.py | 35 +------------ src/assistant/lib/agent/chat_repository.py | 52 +++++++++++++++++-- src/assistant/lib/api/v1/handlers/agent.py | 22 +++++++- src/assistant/lib/models/__init__.py | 4 +- src/assistant/lib/models/chat_history.py | 17 ++++++ src/assistant/lib/orm_models/__init__.py | 3 +- src/assistant/lib/orm_models/chat_history.py | 24 +++++++++ src/assistant/lib/orm_models/movies.py | 16 ------ src/assistant/poetry.lock | 30 ++++++++++- src/assistant/pyproject.toml | 1 + 11 files changed, 145 insertions(+), 62 deletions(-) create mode 100644 src/assistant/lib/orm_models/chat_history.py diff --git a/src/assistant/alembic/env.py b/src/assistant/alembic/env.py index b506519..9d6b168 100644 --- a/src/assistant/alembic/env.py +++ b/src/assistant/alembic/env.py @@ -6,7 +6,6 @@ from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import async_engine_from_config import lib.app.settings as app_settings -import lib.models as models import lib.orm_models as orm_models from alembic import context @@ -24,7 +23,7 @@ print("BASE: ", orm_models.Base.metadata.schema) for t in orm_models.Base.metadata.sorted_tables: print(t.name) -target_metadata = models.Base.metadata +target_metadata = orm_models.Base.metadata def run_migrations_offline() -> None: diff --git a/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py b/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py index 71b2cac..d40648e 100644 --- a/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py +++ b/src/assistant/alembic/versions/2023-10-12_3d448c6327cd_init_commit.py @@ -24,7 +24,7 @@ def upgrade() -> None: op.create_table( "chat_history", sa.Column("id", sa.Uuid(), nullable=False), - sa.Column("session_id", sa.String(), nullable=False), + sa.Column("session_id", sa.Uuid(), nullable=False), sa.Column("channel", sa.String(), nullable=False), sa.Column("user_id", sa.String(), nullable=False), sa.Column("content", sa.JSON(), nullable=False), @@ -33,39 +33,6 @@ def upgrade() -> None: sa.PrimaryKeyConstraint("id"), schema="content", ) - op.drop_table("auth_group") - op.drop_table("auth_user_groups") - op.drop_table("auth_group_permissions") - op.drop_table("auth_user_user_permissions") - op.drop_table("auth_user") - op.drop_table("django_content_type") - op.drop_table("auth_permission") - op.drop_table("django_session") - op.drop_table("django_admin_log") - op.drop_table("django_migrations") - op.alter_column("film_work", "title", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) - op.alter_column("film_work", "description", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) - op.alter_column("film_work", "creation_date", existing_type=sa.DATE(), type_=sa.DateTime(), existing_nullable=True) - op.alter_column("film_work", "file_path", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) - op.alter_column("film_work", "type", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) - op.alter_column("film_work", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.alter_column("film_work", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.alter_column("genre", "name", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) - op.alter_column("genre", "description", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=True) - op.alter_column("genre", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.alter_column("genre", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.create_foreign_key(None, "genre_film_work", "genre", ["genre_id"], ["id"], referent_schema="content") - op.create_foreign_key(None, "genre_film_work", "film_work", ["film_work_id"], ["id"], referent_schema="content") - op.alter_column("person", "full_name", existing_type=sa.TEXT(), type_=sa.String(), existing_nullable=False) - op.alter_column("person", "created", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.alter_column("person", "modified", existing_type=postgresql.TIMESTAMP(timezone=True), nullable=False) - op.alter_column( - "person_film_work", "role", existing_type=sa.TEXT(), type_=sa.String(length=50), existing_nullable=False - ) - op.create_foreign_key(None, "person_film_work", "film_work", ["film_work_id"], ["id"], referent_schema="content") - op.create_foreign_key(None, "person_film_work", "person", ["person_id"], ["id"], referent_schema="content") - op.drop_column("person_film_work", "id") - # ### end Alembic commands ### def downgrade() -> None: diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/chat_repository.py index 435b128..959a1f6 100644 --- a/src/assistant/lib/agent/chat_repository.py +++ b/src/assistant/lib/agent/chat_repository.py @@ -15,7 +15,7 @@ class ChatHistoryRepository: self.logger = logging.getLogger(__name__) async def get_last_session_id(self, request: models.RequestLastSessionId) -> uuid.UUID | None: - """Get a new session ID.""" + """Get a current session ID if exists.""" try: async with self.pg_async_session() as session: @@ -23,10 +23,7 @@ class ChatHistoryRepository: sa.select(orm_models.ChatHistory) .filter_by(channel=request.channel, user_id=request.user_id) .filter( - ( - sa.func.extract("epoch", orm_models.ChatHistory.created) - - sa.func.extract("epoch", orm_models.ChatHistory.modified) / 60 - ) + (sa.text("NOW()") - sa.func.extract("epoch", orm_models.ChatHistory.created)) / 60 <= request.minutes_ago ) .order_by(orm_models.ChatHistory.created.desc()) @@ -39,3 +36,48 @@ class ChatHistoryRepository: return chat_session.id except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) + + async def get_messages_by_sid(self, request: models.RequestChatHistory): + """Get all messages of a chat by session ID.""" + + try: + async with self.pg_async_session() as session: + statement = ( + sa.select(orm_models.ChatHistory) + .filter_by(id=request.session_id) + .order_by(orm_models.ChatHistory.created.desc()) + ) + result = await session.execute(statement) + for row in result.scalars().all(): + print("Row: ", row) + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) + + # async def get_all_by_session_id(self, request: models.RequestChatHistory) -> list[models.ChatHistory]: + # try: + # async with self.pg_async_session() as session: + # statement = ( + # sa.select(orm_models.ChatHistory) + # .filter_by(id=request.session_id) + # .order_by(orm_models.ChatHistory.created.desc()) + # ) + # result = await session.execute(statement) + + # return [models.ChatHistory.from_orm(chat_history) for chat_history in result.scalars().all()] + + async def add_message(self, request: models.ChatMessage) -> None: + """Add a message to the chat history.""" + try: + async with self.pg_async_session() as session: + chat_history = orm_models.ChatHistory( + id=uuid.uuid4(), + session_id=request.session_id, + user_id=request.user_id, + channel=request.channel, + content=request.message, + ) + session.add(chat_history) + await session.commit() + # TODO: Add refresh to session and return added object + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py index 40d90be..0fce3a9 100644 --- a/src/assistant/lib/api/v1/handlers/agent.py +++ b/src/assistant/lib/api/v1/handlers/agent.py @@ -1,3 +1,5 @@ +import uuid + import fastapi import lib.agent as agent @@ -15,9 +17,27 @@ class AgentHandler: summary="Статус работоспособности", description="Проверяет доступность сервиса FastAPI.", ) + self.router.add_api_route( + "/add", + self.add_message, + methods=["GET"], + summary="Статус работоспособности", + description="Проверяет доступность сервиса FastAPI.", + ) async def get_agent(self): - request = models.RequestLastSessionId(channel="test", user_id="test", minutes_ago=3) + request = models.RequestLastSessionId(channel="test", user_id="user_id_1", minutes_ago=3) response = await self.chat_history_repository.get_last_session_id(request=request) print("RESPONSE: ", response) return {"response": response} + + async def add_message(self): + sid: uuid.UUID = uuid.UUID("0cd3c882-affd-4929-aff1-e1724f5b54f2") + import faker + fake = faker.Faker() + + message = models.ChatMessage( + session_id=sid, user_id="user_id_1", channel="test", message={"role": "system", "content": fake.sentence()} + ) + await self.chat_history_repository.add_message(request=message) + return {"response": "ok"} diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index 1995cc2..51dd416 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -1,6 +1,6 @@ -from .chat_history import RequestLastSessionId +from .chat_history import ChatMessage, RequestChatHistory, RequestLastSessionId from .embedding import Embedding from .movies import Movie from .token import Token -__all__ = ["Embedding", "Movie", "RequestLastSessionId", "Token"] +__all__ = ["ChatMessage", "Embedding", "Movie", "RequestChatHistory", "RequestLastSessionId", "Token"] diff --git a/src/assistant/lib/models/chat_history.py b/src/assistant/lib/models/chat_history.py index 264eaa5..20c60e2 100644 --- a/src/assistant/lib/models/chat_history.py +++ b/src/assistant/lib/models/chat_history.py @@ -1,3 +1,5 @@ +import uuid + import pydantic @@ -7,3 +9,18 @@ class RequestLastSessionId(pydantic.BaseModel): channel: str user_id: str minutes_ago: int + + +class ChatMessage(pydantic.BaseModel): + """A chat message.""" + + session_id: uuid.UUID + user_id: str + channel: str + message: dict[str, str] + + +class RequestChatHistory(pydantic.BaseModel): + """Request for chat history.""" + + session_id: uuid.UUID diff --git a/src/assistant/lib/orm_models/__init__.py b/src/assistant/lib/orm_models/__init__.py index afb551e..869102e 100644 --- a/src/assistant/lib/orm_models/__init__.py +++ b/src/assistant/lib/orm_models/__init__.py @@ -1,5 +1,6 @@ from .base import Base, IdCreatedUpdatedBaseMixin -from .movies import ChatHistory, FilmWork, Genre, GenreFilmWork, Person, PersonFilmWork +from .chat_history import ChatHistory +from .movies import FilmWork, Genre, GenreFilmWork, Person, PersonFilmWork __all__ = [ "Base", diff --git a/src/assistant/lib/orm_models/chat_history.py b/src/assistant/lib/orm_models/chat_history.py new file mode 100644 index 0000000..04742e9 --- /dev/null +++ b/src/assistant/lib/orm_models/chat_history.py @@ -0,0 +1,24 @@ +import datetime +import uuid + +import sqlalchemy as sa +import sqlalchemy.orm as sa_orm +import sqlalchemy.sql as sa_sql + +import lib.orm_models.base as base_models + + +class ChatHistory(base_models.Base): + __tablename__: str = "chat_history" # type: ignore[reportIncompatibleVariableOverride] + + id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) + session_id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(nullable=False, unique=True) + channel: sa_orm.Mapped[str] = sa_orm.mapped_column() + user_id: sa_orm.Mapped[str] = sa_orm.mapped_column() + content: sa_orm.Mapped[sa.JSON] = sa_orm.mapped_column(sa.JSON) + created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now() + ) + modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( + sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() + ) diff --git a/src/assistant/lib/orm_models/movies.py b/src/assistant/lib/orm_models/movies.py index b9f8167..88082e2 100644 --- a/src/assistant/lib/orm_models/movies.py +++ b/src/assistant/lib/orm_models/movies.py @@ -74,19 +74,3 @@ PersonFilmWork = sa.Table( sa.Column("role", sa.String(50), nullable=False), sa.Column("created", sa.DateTime(timezone=True), server_default=sa_sql.func.now()), ) - - -class ChatHistory(base_models.Base): - __tablename__: str = "chat_history" # type: ignore[reportIncompatibleVariableOverride] - - id: sa_orm.Mapped[uuid.UUID] = sa_orm.mapped_column(primary_key=True, default=uuid.uuid4) - session_id: sa_orm.Mapped[str] = sa_orm.mapped_column() - channel: sa_orm.Mapped[str] = sa_orm.mapped_column() - user_id: sa_orm.Mapped[str] = sa_orm.mapped_column() - content: sa_orm.Mapped[sa.JSON] = sa_orm.mapped_column(sa.JSON) - created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now() - ) - modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( - sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() - ) diff --git a/src/assistant/poetry.lock b/src/assistant/poetry.lock index d5810e4..bcf762a 100644 --- a/src/assistant/poetry.lock +++ b/src/assistant/poetry.lock @@ -529,6 +529,20 @@ files = [ dnspython = ">=2.0.0" idna = ">=2.0.0" +[[package]] +name = "faker" +version = "19.10.0" +description = "Faker is a Python package that generates fake data for you." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Faker-19.10.0-py3-none-any.whl", hash = "sha256:f321e657ed61616fbfe14dbb9ccc6b2e8282652bbcfcb503c1bd0231ff834df6"}, + {file = "Faker-19.10.0.tar.gz", hash = "sha256:63da90512d0cb3acdb71bd833bb3071cb8a196020d08b8567a01d232954f1820"}, +] + +[package.dependencies] +python-dateutil = ">=2.4" + [[package]] name = "fastapi" version = "0.103.1" @@ -1622,6 +1636,20 @@ pluggy = ">=0.12,<2.0" [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "python-dotenv" version = "1.0.0" @@ -2244,4 +2272,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "2d865a52e2e48b9700ca3f14caa3cbbc05c3ad3965866ab397cdd11e74958829" +content-hash = "5212b83adf6d4f20bc25f36c0f79039516153c470fce1975ed8a30596746a113" diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index dfdfe2e..9d4a180 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -39,6 +39,7 @@ python-magic = "^0.4.27" sqlalchemy = "^2.0.20" uvicorn = "^0.23.2" wrapt = "^1.15.0" +faker = "^19.10.0" [tool.poetry.dev-dependencies] black = "^23.7.0" From 3306025640f98649973a65852bde2566bf99cbbb Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Thu, 12 Oct 2023 02:42:14 +0100 Subject: [PATCH 04/18] fix: add message and get sid --- src/assistant/lib/agent/chat_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/chat_repository.py index 959a1f6..1bcc5dc 100644 --- a/src/assistant/lib/agent/chat_repository.py +++ b/src/assistant/lib/agent/chat_repository.py @@ -23,7 +23,7 @@ class ChatHistoryRepository: sa.select(orm_models.ChatHistory) .filter_by(channel=request.channel, user_id=request.user_id) .filter( - (sa.text("NOW()") - sa.func.extract("epoch", orm_models.ChatHistory.created)) / 60 + (sa.func.extract("epoch", sa.text("NOW()")) - sa.func.extract("epoch", orm_models.ChatHistory.created)) / 60 <= request.minutes_ago ) .order_by(orm_models.ChatHistory.created.desc()) @@ -33,7 +33,7 @@ class ChatHistoryRepository: chat_session = result.scalars().first() if chat_session: - return chat_session.id + return chat_session.session_id except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) From 73f19d46c4eda4cddad3ff46188dce6059e945cd Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Thu, 12 Oct 2023 03:00:40 +0100 Subject: [PATCH 05/18] build: ready chat history repo --- src/assistant/lib/agent/chat_repository.py | 36 +++++++++++----------- src/assistant/lib/api/v1/handlers/agent.py | 20 ++++++++++-- src/assistant/lib/models/__init__.py | 4 +-- src/assistant/lib/models/chat_history.py | 9 +++++- src/assistant/pyproject.toml | 2 +- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/chat_repository.py index 1bcc5dc..d632d4b 100644 --- a/src/assistant/lib/agent/chat_repository.py +++ b/src/assistant/lib/agent/chat_repository.py @@ -17,13 +17,18 @@ class ChatHistoryRepository: async def get_last_session_id(self, request: models.RequestLastSessionId) -> uuid.UUID | None: """Get a current session ID if exists.""" + self.logger.debug("get_last_session_id: %s", request) try: async with self.pg_async_session() as session: statement = ( sa.select(orm_models.ChatHistory) .filter_by(channel=request.channel, user_id=request.user_id) .filter( - (sa.func.extract("epoch", sa.text("NOW()")) - sa.func.extract("epoch", orm_models.ChatHistory.created)) / 60 + ( + sa.func.extract("epoch", sa.text("NOW()")) + - sa.func.extract("epoch", orm_models.ChatHistory.created) + ) + / 60 <= request.minutes_ago ) .order_by(orm_models.ChatHistory.created.desc()) @@ -37,36 +42,31 @@ class ChatHistoryRepository: except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) - async def get_messages_by_sid(self, request: models.RequestChatHistory): + async def get_messages_by_sid(self, request: models.RequestChatHistory) -> list[models.Message] | None: """Get all messages of a chat by session ID.""" + self.logger.debug("get_messages_by_sid: %s", request) try: async with self.pg_async_session() as session: + messages: list[models.Message] = [] statement = ( sa.select(orm_models.ChatHistory) - .filter_by(id=request.session_id) - .order_by(orm_models.ChatHistory.created.desc()) + .filter_by(session_id=request.session_id) + .order_by(orm_models.ChatHistory.created.asc()) ) + print("get_messages_by_sid:", statement) result = await session.execute(statement) for row in result.scalars().all(): - print("Row: ", row) + # TODO: Было бы интересно понять почему pyright ругается ниже и как правильно вызывать компоненты + messages.append(models.Message(role=row.content["role"], content=row.content["content"])) # type: ignore[reportGeneralTypeIssues] + return messages except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) - # async def get_all_by_session_id(self, request: models.RequestChatHistory) -> list[models.ChatHistory]: - # try: - # async with self.pg_async_session() as session: - # statement = ( - # sa.select(orm_models.ChatHistory) - # .filter_by(id=request.session_id) - # .order_by(orm_models.ChatHistory.created.desc()) - # ) - # result = await session.execute(statement) - - # return [models.ChatHistory.from_orm(chat_history) for chat_history in result.scalars().all()] - - async def add_message(self, request: models.ChatMessage) -> None: + async def add_message(self, request: models.RequestChatMessage) -> None: """Add a message to the chat history.""" + + self.logger.debug("add_message: %s", request) try: async with self.pg_async_session() as session: chat_history = orm_models.ChatHistory( diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py index 0fce3a9..bd584e0 100644 --- a/src/assistant/lib/api/v1/handlers/agent.py +++ b/src/assistant/lib/api/v1/handlers/agent.py @@ -24,6 +24,13 @@ class AgentHandler: summary="Статус работоспособности", description="Проверяет доступность сервиса FastAPI.", ) + self.router.add_api_route( + "/messages", + self.get_messages, + methods=["GET"], + summary="Статус работоспособности", + description="Проверяет доступность сервиса FastAPI.", + ) async def get_agent(self): request = models.RequestLastSessionId(channel="test", user_id="user_id_1", minutes_ago=3) @@ -34,10 +41,19 @@ class AgentHandler: async def add_message(self): sid: uuid.UUID = uuid.UUID("0cd3c882-affd-4929-aff1-e1724f5b54f2") import faker + fake = faker.Faker() - - message = models.ChatMessage( + + message = models.RequestChatMessage( session_id=sid, user_id="user_id_1", channel="test", message={"role": "system", "content": fake.sentence()} ) await self.chat_history_repository.add_message(request=message) return {"response": "ok"} + + async def get_messages(self): + sid: uuid.UUID = uuid.UUID("0cd3c882-affd-4929-aff1-e1724f5b54f2") + + request = models.RequestChatHistory(session_id=sid) + response = await self.chat_history_repository.get_messages_by_sid(request=request) + print("RESPONSE: ", response) + return {"response": response} diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index 51dd416..a514cb1 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -1,6 +1,6 @@ -from .chat_history import ChatMessage, RequestChatHistory, RequestLastSessionId +from .chat_history import Message, RequestChatHistory, RequestChatMessage, RequestLastSessionId from .embedding import Embedding from .movies import Movie from .token import Token -__all__ = ["ChatMessage", "Embedding", "Movie", "RequestChatHistory", "RequestLastSessionId", "Token"] +__all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"] diff --git a/src/assistant/lib/models/chat_history.py b/src/assistant/lib/models/chat_history.py index 20c60e2..41a3dbe 100644 --- a/src/assistant/lib/models/chat_history.py +++ b/src/assistant/lib/models/chat_history.py @@ -11,7 +11,7 @@ class RequestLastSessionId(pydantic.BaseModel): minutes_ago: int -class ChatMessage(pydantic.BaseModel): +class RequestChatMessage(pydantic.BaseModel): """A chat message.""" session_id: uuid.UUID @@ -24,3 +24,10 @@ class RequestChatHistory(pydantic.BaseModel): """Request for chat history.""" session_id: uuid.UUID + + +class Message(pydantic.BaseModel): + """A chat message.""" + + role: str + content: str diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index 9d4a180..9f38127 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -23,6 +23,7 @@ version = "0.1.0" alembic = "^1.12.0" asyncpg = "^0.28.0" dill = "^0.3.7" +faker = "^19.10.0" fastapi = "0.103.1" greenlet = "^2.0.2" httpx = "^0.25.0" @@ -39,7 +40,6 @@ python-magic = "^0.4.27" sqlalchemy = "^2.0.20" uvicorn = "^0.23.2" wrapt = "^1.15.0" -faker = "^19.10.0" [tool.poetry.dev-dependencies] black = "^23.7.0" From 36089de163a49af60edde83907dccc962d673ec5 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 07:49:19 +0300 Subject: [PATCH 06/18] feat: [#45] llm_agent --- README.md | 114 ++++-- docker-compose.yml | 85 +++++ {.husky => husky}/.huskyrc | 0 {.husky => husky}/commit-msg | 0 {.husky => husky}/pre-commit | 0 src/assistant/entrypoint.sh | 4 +- src/assistant/lib/agent/openai_functions.py | 5 +- src/assistant/lib/agent/repositories.py | 30 +- src/assistant/lib/agent/services.py | 60 ++- .../lib/app/split_settings/postgres.py | 2 +- src/assistant/lib/models/movies.py | 27 +- src/assistant/lib/orm_models/movies.py | 6 +- src/assistant/poetry.lock | 360 ++++-------------- src/bot_aiogram/.env.example | 3 + src/bot_aiogram/docker-compose.yml | 16 + 15 files changed, 344 insertions(+), 368 deletions(-) create mode 100644 docker-compose.yml rename {.husky => husky}/.huskyrc (100%) rename {.husky => husky}/commit-msg (100%) rename {.husky => husky}/pre-commit (100%) diff --git a/README.md b/README.md index a1976f3..ab2d73f 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,92 @@ -# Python Contrib +# Голосовой Ассистент на Langchain (OpenAI) -Python packages mono-repository +## Описание проекта -## Development +Этот проект представляет собой голосового ассистента, разработанного в рамках дипломного проекта для Яндекс.Практикума. +Ассистент основан на технологиях OpenAI и предназначен для поиска информации о фильмах в нашем сервисе кинотеатра. -### Global dependencies +## Что удалось реализовать? -- node -- poetry +### +- Организация кодовой базы по [шаблону DDD](https://github.com/yp-middle-python-24/python-service-example/) +- Speech To Text на базе [Whisper](https://openai.com/research/whisper) от OpenAI +- LLM: + - Получение embeddings для фильмов в векторную базу данных pgvector + - Поиск фильмов по embeddings + - Получение информации о конкретном фильме по embeddings + - Сохранение истории диалогов с ассистентом +- Text to speech на базе [Yanex SpeechKit](https://cloud.yandex.ru/services/speechkit) и [ElevenLabs](https://elevenlabs.io/) +- Клиент для работы с сервисом на базе Telegram-бота -### Makefile commands +## Команда: -#### Root commands +- [Артем](https://github.com/ijaric/python-monorepo/commits?author=ijaric) +- [Александр](https://github.com/ksieuk) +- [Алексей](https://github.com/grucshetskyaleksei) -- `make init` - Initialize repository -- `make lint` - Lint repository -- `make lint-fix` - Auto-fix repository -- `make test` - Test repository -- `make clean` - Clean up repository -- `make all-init` - Init all packages -- `make all-lint` - Lint all packages -- `make all-lint-fix` - Auto-fix all packages -- `make all-test` - Test all packages -- `make all-clean` - Clean all packages -- `make all-dependencies-update` - Update dependencies in all packages -- `make ci-init` - CI-specific version of init command +## Как запустить проект? +1. Скачать [файл базы данных](https://disk.yandex.ru/d/ZAKDDg8lP9DHBQ) с `embeddings` и поместить её по пути `src/assistant/data/dump.sql`. -#### Common package commands +2. В директории `src/assistant` файл `.env.example` переименовать в `.env` и заполнить переменные окружения. +Пример заполнения переменных окружения: +``` +POSTGRES_DRIVER=postgresql+asyncpg # Драйвер для работы с базой данных +POSTGRES_HOST=db # Хост базы данных +POSTGRES_PORT=5432 # Порт базы данных +POSTGRES_USER=app # Пользователь базы данных +POSTGRES_PASSWORD=123qwe # Пароль пользователя базы данных +POSTGRES_DB_NAME=movies_database # Название базы данных -- `make init` - Initialize package -- `make lint` - Lint package -- `make lint-fix` - Auto-fix package -- `make test` - Test package -- `make clean` - Clean up package folder -- `make dependencies-update` - Update not restricted dependencies -- `PYPI_WRITER_PASSWORD=... make ci-login-pypi-publish` - Login to personal pypi with publish rights -- `make ci-test` - Test package in CI -- `make ci-package-build` - Builds package in CI -- `make ci-package-publish` - Publishes package in CI +NGINX_PORT=80 # Порт nginx +API_HOST=0.0.0.0 # Хост API +API_PORT=8000 # Порт API + +JWT_SECRET_KEY=secret # Секретный ключ для JWT +JWT_ALGORITHM=HS256 # Алгоритм шифрования JWT + +APP_RELOAD=True # Автоматическая перезагрузка приложения + +VOICE_AVAILABLE_FORMATS=mp3,ogg,wav,oga # Доступные форматы аудиофайлов +VOICE_MAX_INPUT_SIZE=5120 # Максимальный размер входного аудиофайла в килобайтах +VOICE_MAX_INPUT_SECONDS=30 # Максимальная длительность входного аудиофайла в секундах + +OPENAI_API_KEY=sk-1234567890 # API-ключ OpenAI +OPENAI_STT_MODEL=whisper-1 # Модель для распознавания речи + +PROXY_HOST=123.123.123.123 # Хост прокси +PROXY_PORT=1234 # Порт прокси +PROXY_USER=proxy_user # Пользователь прокси +PROXY_PASSWORD=proxy_password # Пароль прокси +PROXY_ENABLE=True # Включить прокси + +TTS_YANDEX_API_KEY=1234567890 # API-ключ Yandex SpeechKit +TTS_YANDEX_AUDIO_FORMAT=oggopus # Формат аудиофайла +TTS_YANDEX_SAMPLE_RATE_HERTZ=48000 # Частота дискретизации аудиофайла + +TTS_ELEVEN_LABS_API_KEY=1234567890 # API-ключ ElevenLabs +TTS_ELEVEN_LABS_DEFAULT_VOICE_ID=EXAVITQu4vr4xnSDxMaL # ID голоса по умолчанию +``` + +3. В директории `src/bot_aiogram` файл `.env.example` переименовать в `.env` и заполнить переменные окружения. +Пример заполнения переменных окружения: + +``` +BOT_CONTAINER_NAME=bot_container_name # Название контейнера +BOT_IMAGE_NAME=botimage_name # Название образа +BOT_NAME=mybotname # Название бота +BOT_TOKEN=1234567890:ABCdefghIJKlmnopQRStuVWXYz # Токен бота +BOT_ADMINS=1234567890,9876543210 # ID администраторов бота через запятую + +API_PROTOCOL=http # Протокол API +API_URL=api# URL API +API_PORT=8000 # Порт API + +REDIS_HOST=redis # Хост Redis +REDIS_PORT=6379 # Порт Redis +``` + +3. Запустить проект командой `docker-compose up -d` + +### Важно! +Для работы с Telegram-ботом необходимо предварительно начать с ним диалог и отключить в параметрах конфиденциальности +вашего аккаунта запрет на голосовые сообщения. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ed9d75a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,85 @@ +version: "3" + +services: + postgres: + image: ankane/pgvector:v0.5.0 + restart: always + environment: + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_DB: ${POSTGRES_NAME} + env_file: + - ./src/assistant/.env + expose: + - "${POSTGRES_PORT}" + volumes: + - - ./src/assistant/dump.sql:/docker-entrypoint-initdb.d/dump.sql:ro + networks: + - backend_network + + api: + build: + context: . + container_name: api + image: fastapi_app + restart: always + entrypoint: ["/opt/app/entrypoint.sh"] + env_file: + - ./src/assistant/.env + expose: + - "${API_PORT}" + depends_on: + - postgres + networks: + - backend_network + - api_network + + nginx: + image: nginx:1.25.1 + env_file: + - .env + ports: + - "${NGINX_PORT}:${NGINX_PORT}" + volumes: + - ./src/assistant/nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./src/assistant/nginx/templates:/etc/nginx/templates + depends_on: + - api + networks: + - api_network + + bot: + image: "${BOT_IMAGE_NAME:-tg_bot-image}" + container_name: "${BOT_CONTAINER_NAME:-tg_bot-container}" + build: + context: ./src/bot_aiogram/Dockerfile + restart: always + env_file: + - ./src/bot_aiogram/.env + networks: + - backend_network + - api_network + depends_on: api + + redis: + image: redis:7.0.11 + restart: always + env_file: + - ./src/bot_aiogram/.env + ports: + - "127.0.0.1:${REDIS_PORT}:${REDIS_PORT}" + command: redis-server --bind 0.0.0.0 --appendonly yes + volumes: + - redis_data:/data + networks: + - backend_network + +volumes: + redis_data: + postgres_data: + +networks: + api_network: + driver: bridge + backend_network: + driver: bridge diff --git a/.husky/.huskyrc b/husky/.huskyrc similarity index 100% rename from .husky/.huskyrc rename to husky/.huskyrc diff --git a/.husky/commit-msg b/husky/commit-msg similarity index 100% rename from .husky/commit-msg rename to husky/commit-msg diff --git a/.husky/pre-commit b/husky/pre-commit similarity index 100% rename from .husky/pre-commit rename to husky/pre-commit diff --git a/src/assistant/entrypoint.sh b/src/assistant/entrypoint.sh index fe288b7..e48a4fb 100644 --- a/src/assistant/entrypoint.sh +++ b/src/assistant/entrypoint.sh @@ -2,4 +2,6 @@ while ! nc -z postgres 5432; do sleep 1; done; -exec .venv/bin/python -m bin \ No newline at end of file +alembic upgrade head + +exec .venv/bin/python -m bin diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/openai_functions.py index 673a1ba..e1d99a4 100644 --- a/src/assistant/lib/agent/openai_functions.py +++ b/src/assistant/lib/agent/openai_functions.py @@ -2,13 +2,13 @@ import logging import uuid import langchain.agents -import orm_models import sqlalchemy as sa import sqlalchemy.exc import sqlalchemy.ext.asyncio as sa_asyncio import lib.agent.repositories as repositories import lib.models as models +import lib.orm_models as orm_models class OpenAIFunctions: @@ -23,7 +23,6 @@ class OpenAIFunctions: self.pg_async_session = pg_async_session self.repository = repository - @langchain.agents.tool async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: """Provide a movie data by description.""" @@ -34,7 +33,7 @@ class OpenAIFunctions: result: list[models.Movie] = [] stmt = ( sa.select(orm_models.FilmWork) - .order_by(orm_models.FilmWork.embedding.cosine_distance(embedded_description)) + .order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root)) .limit(5) ) neighbours = session.scalars(stmt) diff --git a/src/assistant/lib/agent/repositories.py b/src/assistant/lib/agent/repositories.py index f8fe6f1..c119a89 100644 --- a/src/assistant/lib/agent/repositories.py +++ b/src/assistant/lib/agent/repositories.py @@ -1,6 +1,7 @@ import logging import typing +import langchain.chat_models import openai import openai.error @@ -13,7 +14,7 @@ class EmbeddingRepository: def __init__(self, settings: app_settings.Settings) -> None: """Initialize the service with an OpenAI API key.""" - self.llm = openai.api_key = settings.openai.api_key + self.llm = openai.api_key = settings.openai.api_key.get_secret_value() self.logger = logging.getLogger(__name__) def get_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None: @@ -28,13 +29,36 @@ class EmbeddingRepository: self.logger.exception("Failed to get async embedding for: %s", text) async def aget_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None: - """Get the embedding for a given text.""" + """Get the embedding for a given text.[Async]""" try: response: dict[str, typing.Any] = await openai.Embedding.acreate( input=text, model=model, ) # type: ignore[reportGeneralTypeIssues] - return models.Embedding(**response["data"][0]["embedding"]) + # print(response["data"][0]["embedding"]) + return models.Embedding(root=response["data"][0]["embedding"]) except openai.error.OpenAIError: self.logger.exception("Failed to get async embedding for: %s", text) + + +class LlmRepository: + """A service for getting embeddings from OpenAI.""" + + def __init__(self, settings: app_settings.Settings) -> None: + """Initialize the service with an OpenAI API key.""" + self.llm = langchain.chat_models.ChatOpenAI( + temperature=0.7, + openai_api_key=self.settings.openai.api_key.get_secret_value() + ) + + async def get_chat_response(self, request: str, prompt: str) -> str: + """Get the embedding for a given text.""" + prompt = langchain.prompts.ChatPromptTemplate.from_messages( + [ + ("system", prompt), + ] + ) + chain = prompt | self.llm + response = await chain.ainvoke({"input": request}) + return response.content diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 83f89a4..046e35d 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -2,13 +2,13 @@ import asyncio import logging import uuid +import fastapi import langchain.agents import langchain.agents.format_scratchpad import langchain.agents.output_parsers import langchain.chat_models import langchain.prompts -import langchain.schema.agent -import langchain.schema.messages +import langchain.schema import langchain.tools.render import assistant.lib.models.movies as movies @@ -24,44 +24,36 @@ class AgentService: self.settings = settings self.tools = tools - async def process_request(self, request: str, chat_history: list[langchain.schema.messages.Message]) -> str: - llm = langchain.chat_models.ChatOpenAI(temperature=0.7, openai_api_key=self.settings.openai.api_key) - tools = [self.tools.get_movie_by_description, self.tools.get_movie_by_id, self.tools.get_similar_movies] + async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: + + result = await self.tools.get_movie_by_description(request.text) + + if len(result) == 0: + raise fastapi.HTTPException(status_code=404, detail="Movies not found") + + # llm = langchain.chat_models.ChatOpenAI( + # temperature=self.settings.openai.agent_temperature, + # openai_api_key=self.settings.openai.api_key.get_secret_value() + # ) + + content_films = "\n".join(film.get_movie_info_line() for film in result) + + system_prompt = ( + "You are a cinema expert. " + f"Here are the movies I found for you: {content_films}" + "Listen to the question and answer it based on the information above." + ) - chat_history = [] - chat_history_name = f"{chat_history=}".partition("=")[0] prompt = langchain.prompts.ChatPromptTemplate.from_messages( [ - ( - "system", - "You are very powerful assistant. If you are asked about movies you will you provided functions.", - ), - langchain.prompts.MessagesPlaceholder(variable_name=chat_history_name), - ("user", "{input}"), - langchain.prompts.MessagesPlaceholder(variable_name="agent_scratchpad"), + ("system", system_prompt), ] ) + chain = prompt | self.llm + response = await chain.ainvoke({"input": request.text}) + response_model = models.AgentCreateResponseModel(text=response.content) + return response_model - llm_with_tools = llm.bind( - functions=[langchain.tools.render.format_tool_to_openai_function(tool) for tool in tools] - ) - - chat_history = [] - - agent = ( - { - "input": lambda _: _["input"], - "agent_scratchpad": lambda _: langchain.agents.format_scratchpad.format_to_openai_functions( - _["intermediate_steps"] - ), - "chat_history": lambda _: _["chat_history"], - } - | prompt - | llm_with_tools - | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() - ) - - agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) return await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) diff --git a/src/assistant/lib/app/split_settings/postgres.py b/src/assistant/lib/app/split_settings/postgres.py index b5a2eb9..e055c19 100644 --- a/src/assistant/lib/app/split_settings/postgres.py +++ b/src/assistant/lib/app/split_settings/postgres.py @@ -37,7 +37,7 @@ class PostgresSettings(pydantic_settings.BaseSettings): @property def dsn(self) -> str: password = self.password.get_secret_value() - return f"{self.driver}://{self.user}:{password}@{self.host}:{self.port}" + return f"{self.driver}://{self.user}:{password}@{self.host}:{self.port}/{self.db_name}" @property def dsn_as_safe_url(self) -> str: diff --git a/src/assistant/lib/models/movies.py b/src/assistant/lib/models/movies.py index e432111..1d7feed 100644 --- a/src/assistant/lib/models/movies.py +++ b/src/assistant/lib/models/movies.py @@ -8,7 +8,30 @@ class Movie(pydantic.BaseModel): id: uuid.UUID title: str description: str | None = None - rating: float - type: str + rating: float | None = None + type: str | None = None created: datetime.datetime modified: datetime.datetime + creation_date: datetime.datetime | None = None + runtime: int | None = None + budget: int | None = None + imdb_id: str | None = None + + @pydantic.computed_field + @property + def imdb_url(self) -> str: + return f"https://www.imdb.com/title/{self.imdb_id}" + + def get_movie_info_line(self): + not_provided_value = "not provided" + content_film_info = { + "Title": self.title, + "Description": self.description or not_provided_value, + "Rating": self.rating or not_provided_value, + "Imdb_id": self.imdb_url or not_provided_value, + "Creation_date": self.creation_date or not_provided_value, + "Runtime": self.runtime or not_provided_value, + "Budget": self.budget or not_provided_value, + } + content_film_info_line = ", ".join(f"{k}: {v}" for k, v in content_film_info.items()) + return content_film_info_line diff --git a/src/assistant/lib/orm_models/movies.py b/src/assistant/lib/orm_models/movies.py index 88082e2..524c8ec 100644 --- a/src/assistant/lib/orm_models/movies.py +++ b/src/assistant/lib/orm_models/movies.py @@ -43,8 +43,10 @@ class FilmWork(base_models.Base): title: sa_orm.Mapped[str] = sa_orm.mapped_column() description: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) creation_date: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column(nullable=True) - file_path: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=True) rating: sa_orm.Mapped[float] = sa_orm.mapped_column(nullable=True) + runtime: sa_orm.Mapped[int] = sa_orm.mapped_column(nullable=False) + budget: sa_orm.Mapped[int] = sa_orm.mapped_column(default=0) + imdb_id: sa_orm.Mapped[str] = sa_orm.mapped_column(nullable=False) type: sa_orm.Mapped[str] = sa_orm.mapped_column() created: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( sa.DateTime(timezone=True), server_default=sa_sql.func.now() @@ -52,7 +54,7 @@ class FilmWork(base_models.Base): modified: sa_orm.Mapped[datetime.datetime] = sa_orm.mapped_column( sa.DateTime(timezone=True), server_default=sa_sql.func.now(), onupdate=sa_sql.func.now() ) - embedding: sa_orm.Mapped[list[float]] = sa_orm.mapped_column(pgvector.sqlalchemy.Vector(1536)) + embeddings: sa_orm.Mapped[list[float]] = sa_orm.mapped_column(pgvector.sqlalchemy.Vector(1536)) genres: sa_orm.Mapped[list[Genre]] = sa_orm.relationship(secondary="genre_film_work") diff --git a/src/assistant/poetry.lock b/src/assistant/poetry.lock index 48b0fb8..4c71746 100644 --- a/src/assistant/poetry.lock +++ b/src/assistant/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -448,21 +448,6 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -[[package]] -name = "dataclasses-json" -version = "0.6.1" -description = "Easily serialize dataclasses to and from JSON." -optional = false -python-versions = ">=3.7,<4.0" -files = [ - {file = "dataclasses_json-0.6.1-py3-none-any.whl", hash = "sha256:1bd8418a61fe3d588bb0079214d7fb71d44937da40742b787256fd53b26b6c80"}, - {file = "dataclasses_json-0.6.1.tar.gz", hash = "sha256:a53c220c35134ce08211a1057fd0e5bf76dc5331627c6b241cacbc570a89faae"}, -] - -[package.dependencies] -marshmallow = ">=3.18.0,<4.0.0" -typing-inspect = ">=0.4.0,<1" - [[package]] name = "dill" version = "0.3.7" @@ -529,20 +514,6 @@ files = [ dnspython = ">=2.0.0" idna = ">=2.0.0" -[[package]] -name = "faker" -version = "19.10.0" -description = "Faker is a Python package that generates fake data for you." -optional = false -python-versions = ">=3.8" -files = [ - {file = "Faker-19.10.0-py3-none-any.whl", hash = "sha256:f321e657ed61616fbfe14dbb9ccc6b2e8282652bbcfcb503c1bd0231ff834df6"}, - {file = "Faker-19.10.0.tar.gz", hash = "sha256:63da90512d0cb3acdb71bd833bb3071cb8a196020d08b8567a01d232954f1820"}, -] - -[package.dependencies] -python-dateutil = ">=2.4" - [[package]] name = "fastapi" version = "0.103.1" @@ -645,6 +616,7 @@ files = [ {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, @@ -653,6 +625,7 @@ files = [ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, @@ -682,6 +655,7 @@ files = [ {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, @@ -690,6 +664,7 @@ files = [ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, @@ -800,85 +775,6 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] -[[package]] -name = "jsonpatch" -version = "1.33" -description = "Apply JSON-Patches (RFC 6902)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, - {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, -] - -[package.dependencies] -jsonpointer = ">=1.9" - -[[package]] -name = "jsonpointer" -version = "2.4" -description = "Identify specific nodes in a JSON document (RFC 6901)" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" -files = [ - {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, - {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, -] - -[[package]] -name = "langchain" -version = "0.0.312" -description = "Building applications with LLMs through composability" -optional = false -python-versions = ">=3.8.1,<4.0" -files = [ - {file = "langchain-0.0.312-py3-none-any.whl", hash = "sha256:2c7ea6e80195b8747c25ca4b905bd4814f26f47719a27edcb369cb6cd2186df3"}, - {file = "langchain-0.0.312.tar.gz", hash = "sha256:4629233c158f23dcfb0cbc249b27d7d8bde1e71ce1d8972d53ae54c7504fc78a"}, -] - -[package.dependencies] -aiohttp = ">=3.8.3,<4.0.0" -anyio = "<4.0" -dataclasses-json = ">=0.5.7,<0.7" -jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.0.43,<0.1.0" -numpy = ">=1,<2" -pydantic = ">=1,<3" -PyYAML = ">=5.3" -requests = ">=2,<3" -SQLAlchemy = ">=1.4,<3" -tenacity = ">=8.1.0,<9.0.0" - -[package.extras] -all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.6.8,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "libdeeplake (>=0.0.60,<0.0.61)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.10.1,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] -azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"] -clarifai = ["clarifai (>=9.1.0)"] -cli = ["typer (>=0.9.0,<0.10.0)"] -cohere = ["cohere (>=4,<5)"] -docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] -embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-schema-pydantic (>=1.2,<2.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] -javascript = ["esprima (>=4.0.1,<5.0.0)"] -llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] -openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"] -qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] -text-helpers = ["chardet (>=5.1.0,<6.0.0)"] - -[[package]] -name = "langsmith" -version = "0.0.43" -description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." -optional = false -python-versions = ">=3.8.1,<4.0" -files = [ - {file = "langsmith-0.0.43-py3-none-any.whl", hash = "sha256:27854bebdae6a35c88e1c1172e6abba27592287b70511aca2a953a59fade0e87"}, - {file = "langsmith-0.0.43.tar.gz", hash = "sha256:f7705f13eb8ce3b8eb16c4d2b2760c62cfb9a3b3ab6aa0728afa84d26b2a6e55"}, -] - -[package.dependencies] -pydantic = ">=1,<3" -requests = ">=2,<3" - [[package]] name = "lazy-object-proxy" version = "1.9.0" @@ -970,6 +866,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1002,26 +908,6 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] -[[package]] -name = "marshmallow" -version = "3.20.1" -description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -optional = false -python-versions = ">=3.8" -files = [ - {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, - {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, -] - -[package.dependencies] -packaging = ">=17.0" - -[package.extras] -dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] -docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] -lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] -tests = ["pytest", "pytz", "simplejson"] - [[package]] name = "mccabe" version = "0.7.0" @@ -1199,71 +1085,61 @@ wandb = ["numpy", "openpyxl (>=3.0.7)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1 [[package]] name = "orjson" -version = "3.9.7" +version = "3.9.9" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "orjson-3.9.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:b6df858e37c321cefbf27fe7ece30a950bcc3a75618a804a0dcef7ed9dd9c92d"}, - {file = "orjson-3.9.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5198633137780d78b86bb54dafaaa9baea698b4f059456cd4554ab7009619221"}, - {file = "orjson-3.9.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e736815b30f7e3c9044ec06a98ee59e217a833227e10eb157f44071faddd7c5"}, - {file = "orjson-3.9.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a19e4074bc98793458b4b3ba35a9a1d132179345e60e152a1bb48c538ab863c4"}, - {file = "orjson-3.9.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80acafe396ab689a326ab0d80f8cc61dec0dd2c5dca5b4b3825e7b1e0132c101"}, - {file = "orjson-3.9.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:355efdbbf0cecc3bd9b12589b8f8e9f03c813a115efa53f8dc2a523bfdb01334"}, - {file = "orjson-3.9.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3aab72d2cef7f1dd6104c89b0b4d6b416b0db5ca87cc2fac5f79c5601f549cc2"}, - {file = "orjson-3.9.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:36b1df2e4095368ee388190687cb1b8557c67bc38400a942a1a77713580b50ae"}, - {file = "orjson-3.9.7-cp310-none-win32.whl", hash = "sha256:e94b7b31aa0d65f5b7c72dd8f8227dbd3e30354b99e7a9af096d967a77f2a580"}, - {file = "orjson-3.9.7-cp310-none-win_amd64.whl", hash = "sha256:82720ab0cf5bb436bbd97a319ac529aee06077ff7e61cab57cee04a596c4f9b4"}, - {file = "orjson-3.9.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1f8b47650f90e298b78ecf4df003f66f54acdba6a0f763cc4df1eab048fe3738"}, - {file = "orjson-3.9.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f738fee63eb263530efd4d2e9c76316c1f47b3bbf38c1bf45ae9625feed0395e"}, - {file = "orjson-3.9.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38e34c3a21ed41a7dbd5349e24c3725be5416641fdeedf8f56fcbab6d981c900"}, - {file = "orjson-3.9.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21a3344163be3b2c7e22cef14fa5abe957a892b2ea0525ee86ad8186921b6cf0"}, - {file = "orjson-3.9.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23be6b22aab83f440b62a6f5975bcabeecb672bc627face6a83bc7aeb495dc7e"}, - {file = "orjson-3.9.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5205ec0dfab1887dd383597012199f5175035e782cdb013c542187d280ca443"}, - {file = "orjson-3.9.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8769806ea0b45d7bf75cad253fba9ac6700b7050ebb19337ff6b4e9060f963fa"}, - {file = "orjson-3.9.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f9e01239abea2f52a429fe9d95c96df95f078f0172489d691b4a848ace54a476"}, - {file = "orjson-3.9.7-cp311-none-win32.whl", hash = "sha256:8bdb6c911dae5fbf110fe4f5cba578437526334df381b3554b6ab7f626e5eeca"}, - {file = "orjson-3.9.7-cp311-none-win_amd64.whl", hash = "sha256:9d62c583b5110e6a5cf5169ab616aa4ec71f2c0c30f833306f9e378cf51b6c86"}, - {file = "orjson-3.9.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1c3cee5c23979deb8d1b82dc4cc49be59cccc0547999dbe9adb434bb7af11cf7"}, - {file = "orjson-3.9.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a347d7b43cb609e780ff8d7b3107d4bcb5b6fd09c2702aa7bdf52f15ed09fa09"}, - {file = "orjson-3.9.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:154fd67216c2ca38a2edb4089584504fbb6c0694b518b9020ad35ecc97252bb9"}, - {file = "orjson-3.9.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ea3e63e61b4b0beeb08508458bdff2daca7a321468d3c4b320a758a2f554d31"}, - {file = "orjson-3.9.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb0b0b2476f357eb2975ff040ef23978137aa674cd86204cfd15d2d17318588"}, - {file = "orjson-3.9.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b9a20a03576c6b7022926f614ac5a6b0914486825eac89196adf3267c6489d"}, - {file = "orjson-3.9.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:915e22c93e7b7b636240c5a79da5f6e4e84988d699656c8e27f2ac4c95b8dcc0"}, - {file = "orjson-3.9.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f26fb3e8e3e2ee405c947ff44a3e384e8fa1843bc35830fe6f3d9a95a1147b6e"}, - {file = "orjson-3.9.7-cp312-none-win_amd64.whl", hash = "sha256:d8692948cada6ee21f33db5e23460f71c8010d6dfcfe293c9b96737600a7df78"}, - {file = "orjson-3.9.7-cp37-cp37m-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7bab596678d29ad969a524823c4e828929a90c09e91cc438e0ad79b37ce41166"}, - {file = "orjson-3.9.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63ef3d371ea0b7239ace284cab9cd00d9c92b73119a7c274b437adb09bda35e6"}, - {file = "orjson-3.9.7-cp37-cp37m-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2f8fcf696bbbc584c0c7ed4adb92fd2ad7d153a50258842787bc1524e50d7081"}, - {file = "orjson-3.9.7-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90fe73a1f0321265126cbba13677dcceb367d926c7a65807bd80916af4c17047"}, - {file = "orjson-3.9.7-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:45a47f41b6c3beeb31ac5cf0ff7524987cfcce0a10c43156eb3ee8d92d92bf22"}, - {file = "orjson-3.9.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a2937f528c84e64be20cb80e70cea76a6dfb74b628a04dab130679d4454395c"}, - {file = "orjson-3.9.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b4fb306c96e04c5863d52ba8d65137917a3d999059c11e659eba7b75a69167bd"}, - {file = "orjson-3.9.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:410aa9d34ad1089898f3db461b7b744d0efcf9252a9415bbdf23540d4f67589f"}, - {file = "orjson-3.9.7-cp37-none-win32.whl", hash = "sha256:26ffb398de58247ff7bde895fe30817a036f967b0ad0e1cf2b54bda5f8dcfdd9"}, - {file = "orjson-3.9.7-cp37-none-win_amd64.whl", hash = "sha256:bcb9a60ed2101af2af450318cd89c6b8313e9f8df4e8fb12b657b2e97227cf08"}, - {file = "orjson-3.9.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5da9032dac184b2ae2da4bce423edff7db34bfd936ebd7d4207ea45840f03905"}, - {file = "orjson-3.9.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7951af8f2998045c656ba8062e8edf5e83fd82b912534ab1de1345de08a41d2b"}, - {file = "orjson-3.9.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b8e59650292aa3a8ea78073fc84184538783966528e442a1b9ed653aa282edcf"}, - {file = "orjson-3.9.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9274ba499e7dfb8a651ee876d80386b481336d3868cba29af839370514e4dce0"}, - {file = "orjson-3.9.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ca1706e8b8b565e934c142db6a9592e6401dc430e4b067a97781a997070c5378"}, - {file = "orjson-3.9.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83cc275cf6dcb1a248e1876cdefd3f9b5f01063854acdfd687ec360cd3c9712a"}, - {file = "orjson-3.9.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:11c10f31f2c2056585f89d8229a56013bc2fe5de51e095ebc71868d070a8dd81"}, - {file = "orjson-3.9.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cf334ce1d2fadd1bf3e5e9bf15e58e0c42b26eb6590875ce65bd877d917a58aa"}, - {file = "orjson-3.9.7-cp38-none-win32.whl", hash = "sha256:76a0fc023910d8a8ab64daed8d31d608446d2d77c6474b616b34537aa7b79c7f"}, - {file = "orjson-3.9.7-cp38-none-win_amd64.whl", hash = "sha256:7a34a199d89d82d1897fd4a47820eb50947eec9cda5fd73f4578ff692a912f89"}, - {file = "orjson-3.9.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e7e7f44e091b93eb39db88bb0cb765db09b7a7f64aea2f35e7d86cbf47046c65"}, - {file = "orjson-3.9.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01d647b2a9c45a23a84c3e70e19d120011cba5f56131d185c1b78685457320bb"}, - {file = "orjson-3.9.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0eb850a87e900a9c484150c414e21af53a6125a13f6e378cf4cc11ae86c8f9c5"}, - {file = "orjson-3.9.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f4b0042d8388ac85b8330b65406c84c3229420a05068445c13ca28cc222f1f7"}, - {file = "orjson-3.9.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd3e7aae977c723cc1dbb82f97babdb5e5fbce109630fbabb2ea5053523c89d3"}, - {file = "orjson-3.9.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c616b796358a70b1f675a24628e4823b67d9e376df2703e893da58247458956"}, - {file = "orjson-3.9.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c3ba725cf5cf87d2d2d988d39c6a2a8b6fc983d78ff71bc728b0be54c869c884"}, - {file = "orjson-3.9.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4891d4c934f88b6c29b56395dfc7014ebf7e10b9e22ffd9877784e16c6b2064f"}, - {file = "orjson-3.9.7-cp39-none-win32.whl", hash = "sha256:14d3fb6cd1040a4a4a530b28e8085131ed94ebc90d72793c59a713de34b60838"}, - {file = "orjson-3.9.7-cp39-none-win_amd64.whl", hash = "sha256:9ef82157bbcecd75d6296d5d8b2d792242afcd064eb1ac573f8847b52e58f677"}, - {file = "orjson-3.9.7.tar.gz", hash = "sha256:85e39198f78e2f7e054d296395f6c96f5e02892337746ef5b6a1bf3ed5910142"}, + {file = "orjson-3.9.9-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:f28090060a31f4d11221f9ba48b2273b0d04b702f4dcaa197c38c64ce639cc51"}, + {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8038ba245d0c0a6337cfb6747ea0c51fe18b0cf1a4bc943d530fd66799fae33d"}, + {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:543b36df56db195739c70d645ecd43e49b44d5ead5f8f645d2782af118249b37"}, + {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e7877256b5092f1e4e48fc0f1004728dc6901e7a4ffaa4acb0a9578610aa4ce"}, + {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12b83e0d8ba4ca88b894c3e00efc59fe6d53d9ffb5dbbb79d437a466fc1a513d"}, + {file = "orjson-3.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef06431f021453a47a9abb7f7853f04f031d31fbdfe1cc83e3c6aadde502cce"}, + {file = "orjson-3.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a1a4d9e64597e550428ba091e51a4bcddc7a335c8f9297effbfa67078972b5c"}, + {file = "orjson-3.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:879d2d1f6085c9c0831cec6716c63aaa89e41d8e036cabb19a315498c173fcc6"}, + {file = "orjson-3.9.9-cp310-none-win32.whl", hash = "sha256:d3f56e41bc79d30fdf077073072f2377d2ebf0b946b01f2009ab58b08907bc28"}, + {file = "orjson-3.9.9-cp310-none-win_amd64.whl", hash = "sha256:ab7bae2b8bf17620ed381e4101aeeb64b3ba2a45fc74c7617c633a923cb0f169"}, + {file = "orjson-3.9.9-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:31d676bc236f6e919d100fb85d0a99812cff1ebffaa58106eaaec9399693e227"}, + {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:678ffb5c0a6b1518b149cc328c610615d70d9297e351e12c01d0beed5d65360f"}, + {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a71b0cc21f2c324747bc77c35161e0438e3b5e72db6d3b515310457aba743f7f"}, + {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae72621f216d1d990468291b1ec153e1b46e0ed188a86d54e0941f3dabd09ee8"}, + {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:512e5a41af008e76451f5a344941d61f48dddcf7d7ddd3073deb555de64596a6"}, + {file = "orjson-3.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f89dc338a12f4357f5bf1b098d3dea6072fb0b643fd35fec556f4941b31ae27"}, + {file = "orjson-3.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:957a45fb201c61b78bcf655a16afbe8a36c2c27f18a998bd6b5d8a35e358d4ad"}, + {file = "orjson-3.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1c01cf4b8e00c7e98a0a7cf606a30a26c32adf2560be2d7d5d6766d6f474b31"}, + {file = "orjson-3.9.9-cp311-none-win32.whl", hash = "sha256:397a185e5dd7f8ebe88a063fe13e34d61d394ebb8c70a443cee7661b9c89bda7"}, + {file = "orjson-3.9.9-cp311-none-win_amd64.whl", hash = "sha256:24301f2d99d670ded4fb5e2f87643bc7428a54ba49176e38deb2887e42fe82fb"}, + {file = "orjson-3.9.9-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd55ea5cce3addc03f8fb0705be0cfed63b048acc4f20914ce5e1375b15a293b"}, + {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b28c1a65cd13fff5958ab8b350f0921121691464a7a1752936b06ed25c0c7b6e"}, + {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b97a67c47840467ccf116136450c50b6ed4e16a8919c81a4b4faef71e0a2b3f4"}, + {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:75b805549cbbcb963e9c9068f1a05abd0ea4c34edc81f8d8ef2edb7e139e5b0f"}, + {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5424ecbafe57b2de30d3b5736c5d5835064d522185516a372eea069b92786ba6"}, + {file = "orjson-3.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d2cd6ef4726ef1b8c63e30d8287225a383dbd1de3424d287b37c1906d8d2855"}, + {file = "orjson-3.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c959550e0705dc9f59de8fca1a316da0d9b115991806b217c82931ac81d75f74"}, + {file = "orjson-3.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ece2d8ed4c34903e7f1b64fb1e448a00e919a4cdb104fc713ad34b055b665fca"}, + {file = "orjson-3.9.9-cp312-none-win_amd64.whl", hash = "sha256:f708ca623287186e5876256cb30599308bce9b2757f90d917b7186de54ce6547"}, + {file = "orjson-3.9.9-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:335406231f9247f985df045f0c0c8f6b6d5d6b3ff17b41a57c1e8ef1a31b4d04"}, + {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d9b5440a5d215d9e1cfd4aee35fd4101a8b8ceb8329f549c16e3894ed9f18b5"}, + {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98ca450cb4fb176dd572ce28c6623de6923752c70556be4ef79764505320acb"}, + {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3bf6ca6bce22eb89dd0650ef49c77341440def966abcb7a2d01de8453df083a"}, + {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eb50d869b3c97c7c5187eda3759e8eb15deb1271d694bc5d6ba7040db9e29036"}, + {file = "orjson-3.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fcf06c69ccc78e32d9f28aa382ab2ab08bf54b696dbe00ee566808fdf05da7d"}, + {file = "orjson-3.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9a4402e7df1b5c9a4c71c7892e1c8f43f642371d13c73242bda5964be6231f95"}, + {file = "orjson-3.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b20becf50d4aec7114dc902b58d85c6431b3a59b04caa977e6ce67b6fee0e159"}, + {file = "orjson-3.9.9-cp38-none-win32.whl", hash = "sha256:1f352117eccac268a59fedac884b0518347f5e2b55b9f650c2463dd1e732eb61"}, + {file = "orjson-3.9.9-cp38-none-win_amd64.whl", hash = "sha256:c4eb31a8e8a5e1d9af5aa9e247c2a52ad5cf7e968aaa9aaefdff98cfcc7f2e37"}, + {file = "orjson-3.9.9-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4a308aeac326c2bafbca9abbae1e1fcf682b06e78a54dad0347b760525838d85"}, + {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e159b97f5676dcdac0d0f75ec856ef5851707f61d262851eb41a30e8fadad7c9"}, + {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f692e7aabad92fa0fff5b13a846fb586b02109475652207ec96733a085019d80"}, + {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cffb77cf0cd3cbf20eb603f932e0dde51b45134bdd2d439c9f57924581bb395b"}, + {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c63eca397127ebf46b59c9c1fb77b30dd7a8fc808ac385e7a58a7e64bae6e106"}, + {file = "orjson-3.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f0c024a75e8ba5d9101facb4fb5a028cdabe3cdfe081534f2a9de0d5062af2"}, + {file = "orjson-3.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8cba20c9815c2a003b8ca4429b0ad4aa87cb6649af41365821249f0fd397148e"}, + {file = "orjson-3.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:906cac73b7818c20cf0f6a7dde5a6f009c52aecc318416c7af5ea37f15ca7e66"}, + {file = "orjson-3.9.9-cp39-none-win32.whl", hash = "sha256:50232572dd300c49f134838c8e7e0917f29a91f97dbd608d23f2895248464b7f"}, + {file = "orjson-3.9.9-cp39-none-win_amd64.whl", hash = "sha256:920814e02e3dd7af12f0262bbc18b9fe353f75a0d0c237f6a67d270da1a1bb44"}, + {file = "orjson-3.9.9.tar.gz", hash = "sha256:02e693843c2959befdd82d1ebae8b05ed12d1cb821605d5f9fe9f98ca5c9fd2b"}, ] [[package]] @@ -1679,18 +1555,6 @@ pluggy = ">=0.12,<2.0" testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] -name = "python-dateutil" -version = "2.8.2" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -files = [ - {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, - {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, -] - -[package.dependencies] -six = ">=1.5" name = "pytest-asyncio" version = "0.21.1" description = "Pytest support for asyncio" @@ -1782,55 +1646,6 @@ files = [ [package.dependencies] tokenize-rt = ">=5.2.0" -[[package]] -name = "pyyaml" -version = "6.0.1" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.6" -files = [ - {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, - {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, - {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, - {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, - {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, - {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, - {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, - {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, - {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, - {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, - {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, - {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, - {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, - {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, - {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, - {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, - {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, - {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - [[package]] name = "requests" version = "2.31.0" @@ -1953,9 +1768,7 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, @@ -1976,25 +1789,19 @@ files = [ {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, @@ -2003,7 +1810,7 @@ files = [ ] [package.dependencies] -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"win32\" or platform_machine == \"WIN32\" or platform_machine == \"AMD64\" or platform_machine == \"amd64\" or platform_machine == \"x86_64\" or platform_machine == \"ppc64le\" or platform_machine == \"aarch64\""} +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} typing-extensions = ">=4.2.0" [package.extras] @@ -2047,20 +1854,6 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] -[[package]] -name = "tenacity" -version = "8.2.3" -description = "Retry code until it succeeds" -optional = false -python-versions = ">=3.7" -files = [ - {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, - {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, -] - -[package.extras] -doc = ["reno", "sphinx", "tornado (>=4.5)"] - [[package]] name = "tokenize-rt" version = "5.2.0" @@ -2128,21 +1921,6 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] -[[package]] -name = "typing-inspect" -version = "0.9.0" -description = "Runtime inspection utilities for typing module." -optional = false -python-versions = "*" -files = [ - {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, - {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, -] - -[package.dependencies] -mypy-extensions = ">=0.3.0" -typing-extensions = ">=3.7.4" - [[package]] name = "urllib3" version = "2.0.6" @@ -2352,4 +2130,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "cf7c2e88dd377d6929d87da3553dabdc48acaa30d58f7de2d8303159180b0c09" +content-hash = "f11c55e41071ec0ef9662649522f774a7d01df83b439d27bd711fb2d1e137d82" diff --git a/src/bot_aiogram/.env.example b/src/bot_aiogram/.env.example index 9cca803..8f16cad 100644 --- a/src/bot_aiogram/.env.example +++ b/src/bot_aiogram/.env.example @@ -8,3 +8,6 @@ BOT_ADMINS=123456,654321 API_PROTOCOL=http API_URL=api API_PORT=8000 + +REDIS_HOST=redis +REDIS_PORT=6379 diff --git a/src/bot_aiogram/docker-compose.yml b/src/bot_aiogram/docker-compose.yml index d1a1082..4961d78 100755 --- a/src/bot_aiogram/docker-compose.yml +++ b/src/bot_aiogram/docker-compose.yml @@ -12,6 +12,22 @@ services: networks: - tg_bot_network + redis: + image: redis:7.0.11 + restart: always + env_file: + - .env + ports: + - "127.0.0.1:${REDIS_PORT}:${REDIS_PORT}" + command: redis-server --bind 0.0.0.0 --appendonly yes + volumes: + - redis_data:/data + networks: + - tg_bot_network + +volumes: + redis_data: + networks: tg_bot_network: driver: bridge From d2933ad8f76dccbb6c23b157660f80db423c4c96 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 07:52:25 +0300 Subject: [PATCH 07/18] feat: [#45] llm_agent --- src/assistant/lib/models/agent.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/assistant/lib/models/agent.py diff --git a/src/assistant/lib/models/agent.py b/src/assistant/lib/models/agent.py new file mode 100644 index 0000000..ecbde51 --- /dev/null +++ b/src/assistant/lib/models/agent.py @@ -0,0 +1,13 @@ +import uuid + +import pydantic + + +class AgentCreateRequestModel(pydantic.BaseModel): + text: str + user_id: str + channel: str + + +class AgentCreateResponseModel(pydantic.BaseModel): + text: str From b9393d70721e802f665bf8aebae3bffa8bcf7124 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 07:59:24 +0300 Subject: [PATCH 08/18] Changes by aleksandr --- src/assistant/lib/agent/openai_functions.py | 11 +- src/assistant/lib/agent/repositories.py | 2 +- src/assistant/lib/agent/services.py | 200 ++++++++++++++++-- .../api/v1/handlers/voice_responce_handler.py | 22 +- src/assistant/lib/app/app.py | 4 +- .../lib/app/split_settings/openai.py | 1 + .../lib/app/split_settings/postgres.py | 2 +- src/assistant/lib/models/__init__.py | 8 +- src/assistant/pyproject.toml | 21 +- 9 files changed, 214 insertions(+), 57 deletions(-) diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/openai_functions.py index e1d99a4..1237148 100644 --- a/src/assistant/lib/agent/openai_functions.py +++ b/src/assistant/lib/agent/openai_functions.py @@ -36,18 +36,19 @@ class OpenAIFunctions: .order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root)) .limit(5) ) - neighbours = session.scalars(stmt) - for neighbour in await neighbours: + response = await session.execute(stmt) + neighbours = response.scalars() + for neighbour in neighbours: result.append(models.Movie(**neighbour.__dict__)) return result except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) @langchain.agents.tool - def get_movie_by_id(self, id: uuid.UUID) -> models.Movie | None: + def get_movie_by_id(self, id: uuid.UUID = None) -> models.Movie | None: """Provide a movie data by movie id.""" - self.logger.info("Request to get movie by id: %s", id) - return None + # self.logger.info("Request to get movie by id: %s", id) + return f"hello world {id}" @langchain.agents.tool def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None: diff --git a/src/assistant/lib/agent/repositories.py b/src/assistant/lib/agent/repositories.py index c119a89..e6a011b 100644 --- a/src/assistant/lib/agent/repositories.py +++ b/src/assistant/lib/agent/repositories.py @@ -26,7 +26,7 @@ class EmbeddingRepository: ) # type: ignore[reportGeneralTypeIssues] return models.Embedding(**response["data"][0]["embedding"]) except openai.error.OpenAIError: - self.logger.exception("Failed to get async embedding for: %s", text) + self.logger.exception("Failed to get sync embedding for: %s", text) async def aget_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None: """Get the embedding for a given text.[Async]""" diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 046e35d..52aa3b3 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -1,5 +1,6 @@ import asyncio import logging +import typing import uuid import fastapi @@ -11,7 +12,7 @@ import langchain.prompts import langchain.schema import langchain.tools.render -import assistant.lib.models.movies as movies +import lib.models as models import lib.agent.openai_functions as openai_functions import lib.app.settings as app_settings @@ -23,6 +24,121 @@ class AgentService: def __init__(self, settings: app_settings.Settings, tools: openai_functions.OpenAIFunctions) -> None: self.settings = settings self.tools = tools + self.llm = langchain.chat_models.ChatOpenAI( + temperature=self.settings.openai.agent_temperature, + openai_api_key=self.settings.openai.api_key.get_secret_value() + ) + self.chat_repository = chat_repository + self.logger = logging.getLogger(__name__) + + async def get_chat_session_id(self, request: models.RequestLastSessionId) -> uuid.UUID: + session_id = self.chat_repository.get_last_session_id(request) + if not session_id: + session_id = uuid.uuid4() + return session_id + + async def artem_process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: + # Get session ID + session_request = models.RequestLastSessionId( + channel=request.channel, + user_id=request.user_id, + minutes_ago=3 + ) + session_id = await self.chat_repository.get_last_session_id(session_request) + if not session_id: + print("NO PREVIOUS CHATS") + session_id = uuid.uuid4() + print("FOUND CHAT:", ) + print("SID:", session_id) + + tools = [ + langchain.tools.Tool( + name="GetMovieByDescription", + func=self.tools.get_movie_by_description, + coroutine=self.tools.get_movie_by_description, + description="Get a movie by description" + ), + ] + + llm = langchain.chat_models.ChatOpenAI(temperature=self.settings.openai.agent_temperature, openai_api_key=self.settings.openai.api_key.get_secret_value()) + + chat_history = langchain.memory.ChatMessageHistory() + # chat_history = [] + chat_history_name = f"{chat_history=}".partition("=")[0] + request_chat_history = models.RequestChatHistory(session_id=session_id) + chat_history_source = await self.chat_repository.get_messages_by_sid(request_chat_history) + for entry in chat_history_source: + # chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) + # chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) + + if entry.content["role"] == "user": + chat_history.append(langchain.schema.messages.HumanMessage(content=entry.content["content"])) + elif entry.content["role"] == "agent": + chat_history.append(langchain.schema.messages.AIMessage(content=entry.content["content"])) + + # chat_history = [entry.model_dump() for entry in chat_history_source] + memory_buffer = langchain.memory.ConversationBufferMemory(memory_key=chat_history_name,chat_memory=chat_history) + + print("CHAT HISTORY:", chat_history) + + # chat_history_name = f"{chat_history=}".partition("=")[0] + + + prompt = langchain.prompts.ChatPromptTemplate.from_messages( + [ + ( + "system", + "Act as an advanced AI assistant with extensive capabilities, you have a vast knowledge base about movies and their related aspects. If you are asked about a movie, please use provided functions to retrive data about movies. You can receive a question in any language. Translate it into English. If you don't know the answer, just say that you don't know, don't try to make up an answer. Be concise. ", + ), + langchain.prompts.MessagesPlaceholder(variable_name=chat_history_name), + ("user", "{input}"), + langchain.prompts.MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + + llm_with_tools = llm.bind( + functions=[langchain.tools.render.format_tool_to_openai_function(tool) for tool in tools] + ) + + agent = ( + { + "input": lambda _: _["input"], + "agent_scratchpad": lambda _: langchain.agents.format_scratchpad.format_to_openai_functions( + _["intermediate_steps"] + ), + "chat_history": lambda _: _["chat_history"], + } + | prompt + | llm_with_tools + | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() + ) + + agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True, memory=memory_buffer) + response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) + print("AI RESPONSE:", response) + user_request = models.RequestChatMessage( + session_id=session_id, + user_id=request.user_id, + channel=request.channel, + message={"role": "user", "content": request.text} + ) + ai_response = models.RequestChatMessage( + session_id=session_id, + user_id=request.user_id, + channel=request.channel, + message={"role": "assistant", "content": response["output"]} + ) + + await self.chat_repository.add_message(user_request) + await self.chat_repository.add_message(ai_response) + + return response + + + # TODO: Добавить запрос для процессинга запроса с памятью+ + # TODO: Улучшить промпт+ + # TODO: Возможно, надо добавить Chain на перевод + async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: @@ -55,26 +171,70 @@ class AgentService: return response_model - return await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) +async def main(): + import lib.agent.repositories as agent_repositories + import lib.clients as clients + + postgres_client = clients.AsyncPostgresClient(app_settings.Settings()) + embedding_repository = agent_repositories.EmbeddingRepository(app_settings.Settings()) + chat_repository = _chat_repository.ChatHistoryRepository(postgres_client.get_async_session()) + + agent_service = AgentService( + settings=app_settings.Settings(), + tools=openai_functions.OpenAIFunctions( + repository=embedding_repository, + pg_async_session=postgres_client.get_async_session(), + ), + chat_repository=chat_repository + ) + + # question = "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" + request_1 = models.AgentCreateRequestModel( + channel="telegram", + user_id="123", + text="What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" + ) + request_2 = models.AgentCreateRequestModel( + channel="telegram", + user_id="123", + text="So what is the rating of the movie? Do you recommend it?" + ) + request_3 = models.AgentCreateRequestModel( + channel="telegram", + user_id="123", + text="What are the similar movies?" + ) + + response = await agent_service.artem_process_request(request_1) + response = await agent_service.artem_process_request(request_2) + response = await agent_service.artem_process_request(request_3) -# async def main(): -# agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) - -# # first_question = "What is the movie where halfling bring the ring to the volcano?" -# first_question = ( -# "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" -# ) -# second_question = "So what is the rating of the movie? Do you recommend it?" -# third_question = "What are the similar movies?" -# first_result = await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) -# chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) -# chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) -# second_result = await agent_executor.ainvoke({"input": second_question, "chat_history": chat_history}) -# chat_history.append(langchain.schema.messages.HumanMessage(content=second_question)) -# chat_history.append(langchain.schema.messages.AIMessage(content=second_result["output"])) -# final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history}) -# if __name__ == "__main__": -# asyncio.run(main()) + # response = await agent_service.artem_process_request(question) + # question = "Highly Rated Titanic Movies" + # request = models.AgentCreateRequestModel(text=question) + # film_results = await agent_service.process_request(request=request) + + # result = [agent_service.tools.get_movie_by_id(id=film.id) for film in film_results] + + # agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) + # + # # first_question = "What is the movie where halfling bring the ring to the volcano?" + # first_question = ( + # "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" + # ) + # second_question = "So what is the rating of the movie? Do you recommend it?" + # third_question = "What are the similar movies?" + # first_result = await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) + # chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) + # chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) + # second_result = await agent_executor.ainvoke({"input": second_question, "chat_history": chat_history}) + # chat_history.append(langchain.schema.messages.HumanMessage(content=second_question)) + # chat_history.append(langchain.schema.messages.AIMessage(content=second_result["output"])) + # final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history}) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 01660cc..80d8ecc 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -5,18 +5,18 @@ import fastapi import lib.stt.services as stt_services -# import lib.tts.services as tts_service -# import lib.models as models +import lib.tts.services as tts_service +import lib.models as models class VoiceResponseHandler: def __init__( self, stt: stt_services.SpeechService, - # tts: tts_service.TTSService, + tts: tts_service.TTSService, ): self.stt = stt - # self.tts = tts + self.tts = tts self.router = fastapi.APIRouter() self.router.add_api_route( "/", @@ -36,10 +36,10 @@ class VoiceResponseHandler: # TODO: Добавить обработку текста через клиента openai # TODO: Добавить синтез речи через клиента tts # TODO: Заменить заглушку на реальный ответ - # response = await self.tts.get_audio_as_bytes( - # models.TTSCreateRequestModel( - # text=voice_text, - # ) - # ) - # return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg") - return fastapi.responses.StreamingResponse(io.BytesIO(voice), media_type="audio/ogg") + response = await self.tts.get_audio_as_bytes( + models.TTSCreateRequestModel( + text=voice_text, + ) + ) + return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg") + # return fastapi.responses.StreamingResponse(io.BytesIO(voice), media_type="audio/ogg") diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index a060a20..3ad25c1 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -117,7 +117,7 @@ class Application: models.VoiceModelProvidersEnum.ELEVEN_LABS: tts_eleven_labs_repository, }, ) - + # Handlers logger.info("Initializing handlers") @@ -127,7 +127,7 @@ class Application: # TODO: объявить сервисы tts и openai и добавить их в voice_response_handler voice_response_handler = api_v1_handlers.VoiceResponseHandler( stt=stt_service, - # tts=tts_service, # TODO + tts=tts_service, ).router logger.info("Creating application") diff --git a/src/assistant/lib/app/split_settings/openai.py b/src/assistant/lib/app/split_settings/openai.py index 235b940..765040d 100644 --- a/src/assistant/lib/app/split_settings/openai.py +++ b/src/assistant/lib/app/split_settings/openai.py @@ -16,3 +16,4 @@ class OpenaiSettings(pydantic_settings.BaseSettings): default=..., validation_alias=pydantic.AliasChoices("api_key", "openai_api_key") ) stt_model: str = "whisper-1" + agent_temperature: float = 0.7 diff --git a/src/assistant/lib/app/split_settings/postgres.py b/src/assistant/lib/app/split_settings/postgres.py index e055c19..e70b2be 100644 --- a/src/assistant/lib/app/split_settings/postgres.py +++ b/src/assistant/lib/app/split_settings/postgres.py @@ -41,4 +41,4 @@ class PostgresSettings(pydantic_settings.BaseSettings): @property def dsn_as_safe_url(self) -> str: - return f"{self.driver}://{self.user}:***@{self.host}:{self.port}" + return f"{self.driver}://{self.user}:***@{self.host}:{self.port}/{self.db_name}" diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index 3990c57..01c3fc9 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -3,18 +3,19 @@ from .embedding import Embedding from .movies import Movie from .token import Token from .tts import * +from .agent import * -__all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"] +# __all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"] __all__ = [ "AVAILABLE_MODELS_TYPE", - "Base", + # "Base", "BaseLanguageCodesEnum", "BaseVoiceModel", "ElevenLabsLanguageCodesEnum", "ElevenLabsListVoiceModelsModel", "ElevenLabsVoiceModel", - "IdCreatedUpdatedBaseMixin", + # "IdCreatedUpdatedBaseMixin", "LANGUAGE_CODES_ENUM_TYPE", "LIST_VOICE_MODELS_TYPE", "TTSCreateRequestModel", @@ -25,4 +26,5 @@ __all__ = [ "YandexLanguageCodesEnum", "YandexListVoiceModelsModel", "YandexVoiceModel", + "AgentCreateRequestModel", ] diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index b59205d..dda90a7 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -13,7 +13,7 @@ profile = "black" py_version = "311" [tool.poetry] -authors = ["ijaric@gmail.com", "jsdio@jsdio.ru"] +authors = ["jsdio@jsdio.ru"] description = "" name = "fastapi_project" readme = "README.md" @@ -22,29 +22,23 @@ version = "0.1.0" [tool.poetry.dependencies] alembic = "^1.12.0" asyncpg = "^0.28.0" -dill = "^0.3.7" -faker = "^19.10.0" fastapi = "0.103.1" greenlet = "^2.0.2" httpx = "^0.25.0" -langchain = "^0.0.312" -openai = "^0.28.1" -pgvector = "^0.2.3" multidict = "^6.0.4" -openai = "^0.28.1" -orjson = "3.9.7" +orjson = "^3.9.7" psycopg2-binary = "^2.9.9" pydantic = {extras = ["email"], version = "^2.3.0"} pydantic-settings = "^2.0.3" -pytest = "^7.4.2" pytest-asyncio = "^0.21.1" python = "^3.11" python-jose = "^3.3.0" -python-magic = "^0.4.27" -python-multipart = "^0.0.6" sqlalchemy = "^2.0.20" uvicorn = "^0.23.2" -wrapt = "^1.15.0" +pgvector = "^0.2.3" +python-magic = "^0.4.27" +openai = "^0.28.1" +python-multipart = "^0.0.6" [tool.poetry.dev-dependencies] black = "^23.7.0" @@ -98,9 +92,8 @@ variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$" [tool.pyright] exclude = [ - ".venv", - "alembic" ".pytest_cache", + ".venv" ] pythonPlatform = "All" pythonVersion = "3.11" From 6f84918506cdd5454186e1a40236fe63b36a560c Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 07:59:29 +0300 Subject: [PATCH 09/18] Changes by ijaric --- src/assistant/lib/agent/openai_functions.py | 22 +++++++++++++++++++++ src/assistant/lib/agent/services.py | 13 ++++++++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/openai_functions.py index 1237148..811fd6b 100644 --- a/src/assistant/lib/agent/openai_functions.py +++ b/src/assistant/lib/agent/openai_functions.py @@ -23,6 +23,28 @@ class OpenAIFunctions: self.pg_async_session = pg_async_session self.repository = repository + @langchain.agents.tool + async def artem_get_movie_by_description(self, description: str) -> list[models.Movie] | None: + """Provide a movie data by description.""" + + self.logger.info("Request to get movie by description: %s", description) + embedded_description = await self.repository.aget_embedding(description) + try: + async with self.pg_async_session() as session: + result: list[models.Movie] = [] + stmt = ( + sa.select(orm_models.FilmWork) + .order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root)) + .limit(5) + ) + response = await session.execute(stmt) + neighbours = response.scalars() + for neighbour in neighbours: + result.append(models.Movie(**neighbour.__dict__)) + return result + except sqlalchemy.exc.SQLAlchemyError as error: + self.logger.exception("Error: %s", error) + async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: """Provide a movie data by description.""" diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 52aa3b3..557a43b 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -11,17 +11,22 @@ import langchain.chat_models import langchain.prompts import langchain.schema import langchain.tools.render +import langchain.memory +import langchain.memory.chat_memory import lib.models as models import lib.agent.openai_functions as openai_functions import lib.app.settings as app_settings - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) +import lib.agent.chat_repository as _chat_repository class AgentService: - def __init__(self, settings: app_settings.Settings, tools: openai_functions.OpenAIFunctions) -> None: + def __init__( + self, + settings: app_settings.Settings, + tools: openai_functions.OpenAIFunctions, + chat_repository: _chat_repository.ChatHistoryRepository, + ) -> None: self.settings = settings self.tools = tools self.llm = langchain.chat_models.ChatOpenAI( From ee8183b4c328b5a09448db957d4c57465b7a655d Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 07:59:35 +0300 Subject: [PATCH 10/18] feat: [#45] llm_agent --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6db49a1..4bb4b44 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,6 @@ "commit-msg": "commitlint" }, "scripts": { - "prepare-husky": "husky install" + "prepare-husky": "huskyy install" } } From 08350809abde80c48381c7d7bb51379707a497a6 Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Sun, 15 Oct 2023 06:40:05 +0100 Subject: [PATCH 11/18] build: wip: agent service --- src/assistant/lib/agent/services.py | 25 ++-- src/assistant/poetry.lock | 212 ++++++++++++++++++++++++++-- src/assistant/pyproject.toml | 1 + 3 files changed, 214 insertions(+), 24 deletions(-) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 557a43b..f23b715 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -67,28 +67,24 @@ class AgentService: llm = langchain.chat_models.ChatOpenAI(temperature=self.settings.openai.agent_temperature, openai_api_key=self.settings.openai.api_key.get_secret_value()) - chat_history = langchain.memory.ChatMessageHistory() - # chat_history = [] + # chat_history = langchain.memory.ChatMessageHistory() + chat_history = [] chat_history_name = f"{chat_history=}".partition("=")[0] + request_chat_history = models.RequestChatHistory(session_id=session_id) chat_history_source = await self.chat_repository.get_messages_by_sid(request_chat_history) + chat_history.append(langchain.schema.HumanMessage(content="Hi there!")) for entry in chat_history_source: - # chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) - # chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) - + print("ENTRY: ", entry) if entry.content["role"] == "user": - chat_history.append(langchain.schema.messages.HumanMessage(content=entry.content["content"])) + chat_history.append(langchain.schema.HumanMessage(content=entry.content["content"])) elif entry.content["role"] == "agent": - chat_history.append(langchain.schema.messages.AIMessage(content=entry.content["content"])) + chat_history.append(langchain.schema.AIMessage(content=entry.content["content"])) - # chat_history = [entry.model_dump() for entry in chat_history_source] - memory_buffer = langchain.memory.ConversationBufferMemory(memory_key=chat_history_name,chat_memory=chat_history) + # memory = langchain.memory.ConversationBufferMemory(memory_key=chat_history_name,chat_memory=chat_history) print("CHAT HISTORY:", chat_history) - # chat_history_name = f"{chat_history=}".partition("=")[0] - - prompt = langchain.prompts.ChatPromptTemplate.from_messages( [ ( @@ -118,7 +114,10 @@ class AgentService: | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() ) - agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True, memory=memory_buffer) + print("AGENT:", agent) + + agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) + print("CH:", type(chat_history), chat_history) response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) print("AI RESPONSE:", response) user_request = models.RequestChatMessage( diff --git a/src/assistant/poetry.lock b/src/assistant/poetry.lock index 4c71746..875acce 100644 --- a/src/assistant/poetry.lock +++ b/src/assistant/poetry.lock @@ -448,6 +448,21 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "dataclasses-json" +version = "0.6.1" +description = "Easily serialize dataclasses to and from JSON." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "dataclasses_json-0.6.1-py3-none-any.whl", hash = "sha256:1bd8418a61fe3d588bb0079214d7fb71d44937da40742b787256fd53b26b6c80"}, + {file = "dataclasses_json-0.6.1.tar.gz", hash = "sha256:a53c220c35134ce08211a1057fd0e5bf76dc5331627c6b241cacbc570a89faae"}, +] + +[package.dependencies] +marshmallow = ">=3.18.0,<4.0.0" +typing-inspect = ">=0.4.0,<1" + [[package]] name = "dill" version = "0.3.7" @@ -775,6 +790,85 @@ pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib" plugins = ["setuptools"] requirements-deprecated-finder = ["pip-api", "pipreqs"] +[[package]] +name = "jsonpatch" +version = "1.33" +description = "Apply JSON-Patches (RFC 6902)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, + {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, +] + +[package.dependencies] +jsonpointer = ">=1.9" + +[[package]] +name = "jsonpointer" +version = "2.4" +description = "Identify specific nodes in a JSON document (RFC 6901)" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +files = [ + {file = "jsonpointer-2.4-py2.py3-none-any.whl", hash = "sha256:15d51bba20eea3165644553647711d150376234112651b4f1811022aecad7d7a"}, + {file = "jsonpointer-2.4.tar.gz", hash = "sha256:585cee82b70211fa9e6043b7bb89db6e1aa49524340dde8ad6b63206ea689d88"}, +] + +[[package]] +name = "langchain" +version = "0.0.314" +description = "Building applications with LLMs through composability" +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langchain-0.0.314-py3-none-any.whl", hash = "sha256:b799d4086086ca8f952d5f7b9dc3ebb3cf77b1957d657ee91739cd1689d71a5c"}, + {file = "langchain-0.0.314.tar.gz", hash = "sha256:96dac382b79a88ee7ccc53aadc2818fd064475f229fde189483fee15558405c8"}, +] + +[package.dependencies] +aiohttp = ">=3.8.3,<4.0.0" +anyio = "<4.0" +dataclasses-json = ">=0.5.7,<0.7" +jsonpatch = ">=1.33,<2.0" +langsmith = ">=0.0.43,<0.1.0" +numpy = ">=1,<2" +pydantic = ">=1,<3" +PyYAML = ">=5.3" +requests = ">=2,<3" +SQLAlchemy = ">=1.4,<3" +tenacity = ">=8.1.0,<9.0.0" + +[package.extras] +all = ["O365 (>=2.0.26,<3.0.0)", "aleph-alpha-client (>=2.15.0,<3.0.0)", "amadeus (>=8.1.0)", "arxiv (>=1.4,<2.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "awadb (>=0.3.9,<0.4.0)", "azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "beautifulsoup4 (>=4,<5)", "clarifai (>=9.1.0)", "clickhouse-connect (>=0.5.14,<0.6.0)", "cohere (>=4,<5)", "deeplake (>=3.6.8,<4.0.0)", "docarray[hnswlib] (>=0.32.0,<0.33.0)", "duckduckgo-search (>=3.8.3,<4.0.0)", "elasticsearch (>=8,<9)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "google-api-python-client (==2.70.0)", "google-auth (>=2.18.1,<3.0.0)", "google-search-results (>=2,<3)", "gptcache (>=0.1.7)", "html2text (>=2020.1.16,<2021.0.0)", "huggingface_hub (>=0,<1)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lancedb (>=0.1,<0.2)", "langkit (>=0.0.6,<0.1.0)", "lark (>=1.1.5,<2.0.0)", "libdeeplake (>=0.0.60,<0.0.61)", "librosa (>=0.10.0.post2,<0.11.0)", "lxml (>=4.9.2,<5.0.0)", "manifest-ml (>=0.0.1,<0.0.2)", "marqo (>=1.2.4,<2.0.0)", "momento (>=1.10.1,<2.0.0)", "nebula3-python (>=3.4.0,<4.0.0)", "neo4j (>=5.8.1,<6.0.0)", "networkx (>=2.6.3,<4)", "nlpcloud (>=1,<2)", "nltk (>=3,<4)", "nomic (>=1.0.43,<2.0.0)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "opensearch-py (>=2.0.0,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pexpect (>=4.8.0,<5.0.0)", "pgvector (>=0.1.6,<0.2.0)", "pinecone-client (>=2,<3)", "pinecone-text (>=0.4.2,<0.5.0)", "psycopg2-binary (>=2.9.5,<3.0.0)", "pymongo (>=4.3.3,<5.0.0)", "pyowm (>=3.3.0,<4.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pytesseract (>=0.3.10,<0.4.0)", "python-arango (>=7.5.9,<8.0.0)", "pyvespa (>=0.33.0,<0.34.0)", "qdrant-client (>=1.3.1,<2.0.0)", "rdflib (>=6.3.2,<7.0.0)", "redis (>=4,<5)", "requests-toolbelt (>=1.0.0,<2.0.0)", "sentence-transformers (>=2,<3)", "singlestoredb (>=0.7.1,<0.8.0)", "tensorflow-text (>=2.11.0,<3.0.0)", "tigrisdb (>=1.0.0b6,<2.0.0)", "tiktoken (>=0.3.2,<0.6.0)", "torch (>=1,<3)", "transformers (>=4,<5)", "weaviate-client (>=3,<4)", "wikipedia (>=1,<2)", "wolframalpha (==5.0.0)"] +azure = ["azure-ai-formrecognizer (>=3.2.1,<4.0.0)", "azure-ai-vision (>=0.11.1b1,<0.12.0)", "azure-cognitiveservices-speech (>=1.28.0,<2.0.0)", "azure-core (>=1.26.4,<2.0.0)", "azure-cosmos (>=4.4.0b1,<5.0.0)", "azure-identity (>=1.12.0,<2.0.0)", "azure-search-documents (==11.4.0b8)", "openai (>=0,<1)"] +clarifai = ["clarifai (>=9.1.0)"] +cli = ["typer (>=0.9.0,<0.10.0)"] +cohere = ["cohere (>=4,<5)"] +docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] +embeddings = ["sentence-transformers (>=2,<3)"] +extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-schema-pydantic (>=1.2,<2.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"] +javascript = ["esprima (>=4.0.1,<5.0.0)"] +llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"] +openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"] +qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +text-helpers = ["chardet (>=5.1.0,<6.0.0)"] + +[[package]] +name = "langsmith" +version = "0.0.43" +description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." +optional = false +python-versions = ">=3.8.1,<4.0" +files = [ + {file = "langsmith-0.0.43-py3-none-any.whl", hash = "sha256:27854bebdae6a35c88e1c1172e6abba27592287b70511aca2a953a59fade0e87"}, + {file = "langsmith-0.0.43.tar.gz", hash = "sha256:f7705f13eb8ce3b8eb16c4d2b2760c62cfb9a3b3ab6aa0728afa84d26b2a6e55"}, +] + +[package.dependencies] +pydantic = ">=1,<3" +requests = ">=2,<3" + [[package]] name = "lazy-object-proxy" version = "1.9.0" @@ -866,16 +960,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -908,6 +992,26 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "marshmallow" +version = "3.20.1" +description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +optional = false +python-versions = ">=3.8" +files = [ + {file = "marshmallow-3.20.1-py3-none-any.whl", hash = "sha256:684939db93e80ad3561392f47be0230743131560a41c5110684c16e21ade0a5c"}, + {file = "marshmallow-3.20.1.tar.gz", hash = "sha256:5d2371bbe42000f2b3fb5eaa065224df7d8f8597bc19a1bbfa5bfe7fba8da889"}, +] + +[package.dependencies] +packaging = ">=17.0" + +[package.extras] +dev = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)", "pytest", "pytz", "simplejson", "tox"] +docs = ["alabaster (==0.7.13)", "autodocsumm (==0.2.11)", "sphinx (==7.0.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"] +lint = ["flake8 (==6.0.0)", "flake8-bugbear (==23.7.10)", "mypy (==1.4.1)", "pre-commit (>=2.4,<4.0)"] +tests = ["pytest", "pytz", "simplejson"] + [[package]] name = "mccabe" version = "0.7.0" @@ -1646,6 +1750,55 @@ files = [ [package.dependencies] tokenize-rt = ">=5.2.0" +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + [[package]] name = "requests" version = "2.31.0" @@ -1768,7 +1921,9 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, + {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, @@ -1789,19 +1944,25 @@ files = [ {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, + {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, + {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, + {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"}, @@ -1854,6 +2015,20 @@ anyio = ">=3.4.0,<5" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "tenacity" +version = "8.2.3" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tenacity-8.2.3-py3-none-any.whl", hash = "sha256:ce510e327a630c9e1beaf17d42e6ffacc88185044ad85cf74c0a8887c6a0f88c"}, + {file = "tenacity-8.2.3.tar.gz", hash = "sha256:5398ef0d78e63f40007c1fb4c0bff96e1911394d2fa8d194f77619c05ff6cc8a"}, +] + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + [[package]] name = "tokenize-rt" version = "5.2.0" @@ -1921,6 +2096,21 @@ files = [ {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] +[[package]] +name = "typing-inspect" +version = "0.9.0" +description = "Runtime inspection utilities for typing module." +optional = false +python-versions = "*" +files = [ + {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, + {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, +] + +[package.dependencies] +mypy-extensions = ">=0.3.0" +typing-extensions = ">=3.7.4" + [[package]] name = "urllib3" version = "2.0.6" @@ -2130,4 +2320,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "f11c55e41071ec0ef9662649522f774a7d01df83b439d27bd711fb2d1e137d82" +content-hash = "e89034ff2a2129b8963474f4adc276ef030e60de0178dc93efa754d5df11f81f" diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index dda90a7..06873f6 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -39,6 +39,7 @@ pgvector = "^0.2.3" python-magic = "^0.4.27" openai = "^0.28.1" python-multipart = "^0.0.6" +langchain = "^0.0.314" [tool.poetry.dev-dependencies] black = "^23.7.0" From 6de92100a73821b0d0503dde3d748cc41674b3cc Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 09:01:33 +0300 Subject: [PATCH 12/18] Changes by ijaric --- src/assistant/lib/agent/services.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index f23b715..66dcf70 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -73,13 +73,12 @@ class AgentService: request_chat_history = models.RequestChatHistory(session_id=session_id) chat_history_source = await self.chat_repository.get_messages_by_sid(request_chat_history) - chat_history.append(langchain.schema.HumanMessage(content="Hi there!")) for entry in chat_history_source: print("ENTRY: ", entry) - if entry.content["role"] == "user": - chat_history.append(langchain.schema.HumanMessage(content=entry.content["content"])) - elif entry.content["role"] == "agent": - chat_history.append(langchain.schema.AIMessage(content=entry.content["content"])) + if entry.role == "user": + chat_history.append(langchain.schema.HumanMessage(content=entry.content)) + elif entry.role == "agent": + chat_history.append(langchain.schema.AIMessage(content=entry.content)) # memory = langchain.memory.ConversationBufferMemory(memory_key=chat_history_name,chat_memory=chat_history) @@ -118,8 +117,10 @@ class AgentService: agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) print("CH:", type(chat_history), chat_history) + chat_history = [] # temporary disable chat_history response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) print("AI RESPONSE:", response) + user_request = models.RequestChatMessage( session_id=session_id, user_id=request.user_id, From b3e480886e3c064acd8a4885f3ba9d15e74ffe46 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 09:53:13 +0300 Subject: [PATCH 13/18] feat: [#45] llm_agent --- src/assistant/lib/agent/__init__.py | 6 ++- .../lib/agent/repositories/__init__.py | 9 +++++ .../embedding_repository.py} | 0 .../{ => repositories}/openai_functions.py | 37 ------------------- src/assistant/lib/agent/services.py | 5 +-- src/assistant/lib/api/v1/handlers/agent.py | 2 +- .../api/v1/handlers/voice_responce_handler.py | 2 + src/assistant/lib/app/app.py | 1 - 8 files changed, 18 insertions(+), 44 deletions(-) create mode 100644 src/assistant/lib/agent/repositories/__init__.py rename src/assistant/lib/agent/{repositories.py => repositories/embedding_repository.py} (100%) rename src/assistant/lib/agent/{ => repositories}/openai_functions.py (50%) diff --git a/src/assistant/lib/agent/__init__.py b/src/assistant/lib/agent/__init__.py index ff2f391..262d5aa 100644 --- a/src/assistant/lib/agent/__init__.py +++ b/src/assistant/lib/agent/__init__.py @@ -1,3 +1,5 @@ -from .chat_repository import ChatHistoryRepository +from .services import AgentService -__all__ = ["ChatHistoryRepository"] +__all__ = [ + "AgentService", +] diff --git a/src/assistant/lib/agent/repositories/__init__.py b/src/assistant/lib/agent/repositories/__init__.py new file mode 100644 index 0000000..85fac89 --- /dev/null +++ b/src/assistant/lib/agent/repositories/__init__.py @@ -0,0 +1,9 @@ +from .chat_repository import ChatHistoryRepository +from .embedding_repository import EmbeddingRepository +from .openai_functions import OpenAIFunctions + +__all__ = [ + "ChatHistoryRepository", + "EmbeddingRepository", + "OpenAIFunctions", +] diff --git a/src/assistant/lib/agent/repositories.py b/src/assistant/lib/agent/repositories/embedding_repository.py similarity index 100% rename from src/assistant/lib/agent/repositories.py rename to src/assistant/lib/agent/repositories/embedding_repository.py diff --git a/src/assistant/lib/agent/openai_functions.py b/src/assistant/lib/agent/repositories/openai_functions.py similarity index 50% rename from src/assistant/lib/agent/openai_functions.py rename to src/assistant/lib/agent/repositories/openai_functions.py index 811fd6b..9a463d4 100644 --- a/src/assistant/lib/agent/openai_functions.py +++ b/src/assistant/lib/agent/repositories/openai_functions.py @@ -1,7 +1,5 @@ import logging -import uuid -import langchain.agents import sqlalchemy as sa import sqlalchemy.exc import sqlalchemy.ext.asyncio as sa_asyncio @@ -23,28 +21,6 @@ class OpenAIFunctions: self.pg_async_session = pg_async_session self.repository = repository - @langchain.agents.tool - async def artem_get_movie_by_description(self, description: str) -> list[models.Movie] | None: - """Provide a movie data by description.""" - - self.logger.info("Request to get movie by description: %s", description) - embedded_description = await self.repository.aget_embedding(description) - try: - async with self.pg_async_session() as session: - result: list[models.Movie] = [] - stmt = ( - sa.select(orm_models.FilmWork) - .order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root)) - .limit(5) - ) - response = await session.execute(stmt) - neighbours = response.scalars() - for neighbour in neighbours: - result.append(models.Movie(**neighbour.__dict__)) - return result - except sqlalchemy.exc.SQLAlchemyError as error: - self.logger.exception("Error: %s", error) - async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: """Provide a movie data by description.""" @@ -65,16 +41,3 @@ class OpenAIFunctions: return result except sqlalchemy.exc.SQLAlchemyError as error: self.logger.exception("Error: %s", error) - - @langchain.agents.tool - def get_movie_by_id(self, id: uuid.UUID = None) -> models.Movie | None: - """Provide a movie data by movie id.""" - # self.logger.info("Request to get movie by id: %s", id) - return f"hello world {id}" - - @langchain.agents.tool - def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None: - """Provide similar movies for the given movie ID.""" - - self.logger.info("Request to get movie by id: %s", id) - return None diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 66dcf70..984110a 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -15,16 +15,15 @@ import langchain.memory import langchain.memory.chat_memory import lib.models as models -import lib.agent.openai_functions as openai_functions +import lib.agent.repositories as lib_agent_repositories import lib.app.settings as app_settings -import lib.agent.chat_repository as _chat_repository +import lib.agent.repositories.chat_repository as _chat_repository class AgentService: def __init__( self, settings: app_settings.Settings, - tools: openai_functions.OpenAIFunctions, chat_repository: _chat_repository.ChatHistoryRepository, ) -> None: self.settings = settings diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py index bd584e0..b1f627c 100644 --- a/src/assistant/lib/api/v1/handlers/agent.py +++ b/src/assistant/lib/api/v1/handlers/agent.py @@ -7,7 +7,7 @@ import lib.models as models class AgentHandler: - def __init__(self, chat_history_repository: agent.ChatHistoryRepository): + def __init__(self, chat_history_repository: agent_repositories.ChatHistoryRepository): self.chat_history_repository = chat_history_repository self.router = fastapi.APIRouter() self.router.add_api_route( diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 80d8ecc..1884ce3 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -28,6 +28,8 @@ class VoiceResponseHandler: async def voice_response( self, + channel: str, + user_id: str, voice: bytes = fastapi.File(...), ) -> fastapi.responses.StreamingResponse: voice_text: str = await self.stt.recognize(voice) diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index 3ad25c1..2a210ff 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -6,7 +6,6 @@ import typing import fastapi import uvicorn -import lib.agent as agent import lib.api.v1.handlers as api_v1_handlers import lib.app.errors as app_errors import lib.app.settings as app_settings From 288af57ab5f5e0f17740cb0045f97a49b981df09 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 09:55:05 +0300 Subject: [PATCH 14/18] Changes by ijaric --- .../{ => repositories}/chat_repository.py | 0 src/assistant/lib/agent/services.py | 134 +----------------- src/assistant/lib/api/v1/handlers/agent.py | 2 +- .../api/v1/handlers/voice_responce_handler.py | 13 +- src/assistant/lib/app/app.py | 19 ++- 5 files changed, 30 insertions(+), 138 deletions(-) rename src/assistant/lib/agent/{ => repositories}/chat_repository.py (100%) diff --git a/src/assistant/lib/agent/chat_repository.py b/src/assistant/lib/agent/repositories/chat_repository.py similarity index 100% rename from src/assistant/lib/agent/chat_repository.py rename to src/assistant/lib/agent/repositories/chat_repository.py diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 984110a..deb3ba6 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -1,9 +1,6 @@ -import asyncio import logging -import typing import uuid -import fastapi import langchain.agents import langchain.agents.format_scratchpad import langchain.agents.output_parsers @@ -25,23 +22,15 @@ class AgentService: self, settings: app_settings.Settings, chat_repository: _chat_repository.ChatHistoryRepository, + tools: lib_agent_repositories.OpenAIFunctions | None = None, ) -> None: self.settings = settings self.tools = tools - self.llm = langchain.chat_models.ChatOpenAI( - temperature=self.settings.openai.agent_temperature, - openai_api_key=self.settings.openai.api_key.get_secret_value() - ) self.chat_repository = chat_repository self.logger = logging.getLogger(__name__) - async def get_chat_session_id(self, request: models.RequestLastSessionId) -> uuid.UUID: - session_id = self.chat_repository.get_last_session_id(request) - if not session_id: - session_id = uuid.uuid4() - return session_id - async def artem_process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: + async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: # Get session ID session_request = models.RequestLastSessionId( channel=request.channel, @@ -50,11 +39,9 @@ class AgentService: ) session_id = await self.chat_repository.get_last_session_id(session_request) if not session_id: - print("NO PREVIOUS CHATS") session_id = uuid.uuid4() - print("FOUND CHAT:", ) - print("SID:", session_id) + # Declare tools (OpenAI functions) tools = [ langchain.tools.Tool( name="GetMovieByDescription", @@ -66,22 +53,17 @@ class AgentService: llm = langchain.chat_models.ChatOpenAI(temperature=self.settings.openai.agent_temperature, openai_api_key=self.settings.openai.api_key.get_secret_value()) - # chat_history = langchain.memory.ChatMessageHistory() chat_history = [] chat_history_name = f"{chat_history=}".partition("=")[0] request_chat_history = models.RequestChatHistory(session_id=session_id) chat_history_source = await self.chat_repository.get_messages_by_sid(request_chat_history) for entry in chat_history_source: - print("ENTRY: ", entry) if entry.role == "user": chat_history.append(langchain.schema.HumanMessage(content=entry.content)) elif entry.role == "agent": chat_history.append(langchain.schema.AIMessage(content=entry.content)) - # memory = langchain.memory.ConversationBufferMemory(memory_key=chat_history_name,chat_memory=chat_history) - - print("CHAT HISTORY:", chat_history) prompt = langchain.prompts.ChatPromptTemplate.from_messages( [ @@ -112,13 +94,10 @@ class AgentService: | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() ) - print("AGENT:", agent) - agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) - print("CH:", type(chat_history), chat_history) + agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=False) chat_history = [] # temporary disable chat_history response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) - print("AI RESPONSE:", response) user_request = models.RequestChatMessage( session_id=session_id, @@ -137,108 +116,3 @@ class AgentService: await self.chat_repository.add_message(ai_response) return response - - - # TODO: Добавить запрос для процессинга запроса с памятью+ - # TODO: Улучшить промпт+ - # TODO: Возможно, надо добавить Chain на перевод - - - async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: - - result = await self.tools.get_movie_by_description(request.text) - - if len(result) == 0: - raise fastapi.HTTPException(status_code=404, detail="Movies not found") - - # llm = langchain.chat_models.ChatOpenAI( - # temperature=self.settings.openai.agent_temperature, - # openai_api_key=self.settings.openai.api_key.get_secret_value() - # ) - - content_films = "\n".join(film.get_movie_info_line() for film in result) - - system_prompt = ( - "You are a cinema expert. " - f"Here are the movies I found for you: {content_films}" - "Listen to the question and answer it based on the information above." - ) - - prompt = langchain.prompts.ChatPromptTemplate.from_messages( - [ - ("system", system_prompt), - ] - ) - chain = prompt | self.llm - response = await chain.ainvoke({"input": request.text}) - response_model = models.AgentCreateResponseModel(text=response.content) - return response_model - - -async def main(): - import lib.agent.repositories as agent_repositories - import lib.clients as clients - - postgres_client = clients.AsyncPostgresClient(app_settings.Settings()) - embedding_repository = agent_repositories.EmbeddingRepository(app_settings.Settings()) - chat_repository = _chat_repository.ChatHistoryRepository(postgres_client.get_async_session()) - - agent_service = AgentService( - settings=app_settings.Settings(), - tools=openai_functions.OpenAIFunctions( - repository=embedding_repository, - pg_async_session=postgres_client.get_async_session(), - ), - chat_repository=chat_repository - ) - - # question = "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" - request_1 = models.AgentCreateRequestModel( - channel="telegram", - user_id="123", - text="What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" - ) - request_2 = models.AgentCreateRequestModel( - channel="telegram", - user_id="123", - text="So what is the rating of the movie? Do you recommend it?" - ) - request_3 = models.AgentCreateRequestModel( - channel="telegram", - user_id="123", - text="What are the similar movies?" - ) - - response = await agent_service.artem_process_request(request_1) - response = await agent_service.artem_process_request(request_2) - response = await agent_service.artem_process_request(request_3) - - - - - # response = await agent_service.artem_process_request(question) - # question = "Highly Rated Titanic Movies" - # request = models.AgentCreateRequestModel(text=question) - # film_results = await agent_service.process_request(request=request) - - # result = [agent_service.tools.get_movie_by_id(id=film.id) for film in film_results] - - # agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) - # - # # first_question = "What is the movie where halfling bring the ring to the volcano?" - # first_question = ( - # "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" - # ) - # second_question = "So what is the rating of the movie? Do you recommend it?" - # third_question = "What are the similar movies?" - # first_result = await agent_executor.ainvoke({"input": first_question, "chat_history": chat_history}) - # chat_history.append(langchain.schema.messages.HumanMessage(content=first_question)) - # chat_history.append(langchain.schema.messages.AIMessage(content=first_result["output"])) - # second_result = await agent_executor.ainvoke({"input": second_question, "chat_history": chat_history}) - # chat_history.append(langchain.schema.messages.HumanMessage(content=second_question)) - # chat_history.append(langchain.schema.messages.AIMessage(content=second_result["output"])) - # final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history}) - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py index b1f627c..6d2d57e 100644 --- a/src/assistant/lib/api/v1/handlers/agent.py +++ b/src/assistant/lib/api/v1/handlers/agent.py @@ -2,7 +2,7 @@ import uuid import fastapi -import lib.agent as agent +import lib.agent.repositories as agent_repositories import lib.models as models diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 1884ce3..07cb3f7 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -4,7 +4,7 @@ import io import fastapi import lib.stt.services as stt_services - +import lib.agent.services as agent_service import lib.tts.services as tts_service import lib.models as models @@ -14,9 +14,11 @@ class VoiceResponseHandler: self, stt: stt_services.SpeechService, tts: tts_service.TTSService, + # agent: agent_service.AgentService, ): self.stt = stt self.tts = tts + # self.agent = agent self.router = fastapi.APIRouter() self.router.add_api_route( "/", @@ -35,12 +37,13 @@ class VoiceResponseHandler: voice_text: str = await self.stt.recognize(voice) if voice_text == "": raise fastapi.HTTPException(status_code=http.HTTPStatus.BAD_REQUEST, detail="Speech recognition failed") - # TODO: Добавить обработку текста через клиента openai - # TODO: Добавить синтез речи через клиента tts - # TODO: Заменить заглушку на реальный ответ + + # agent_request = models.AgentCreateRequestModel(channel=channel, user_id=user_id, text=voice_text) + # reply_text = await self.agent.process_request(agent_request) + reply_text = "hi there" response = await self.tts.get_audio_as_bytes( models.TTSCreateRequestModel( - text=voice_text, + text=reply_text, ) ) return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg") diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index 2a210ff..c0741cf 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -14,6 +14,9 @@ import lib.clients as clients import lib.models as models import lib.stt as stt import lib.tts as tts +import lib.agent.repositories as agent_repositories +import lib.agent.repositories.openai_functions as agent_functions +import lib.agent.services as agent_services logger = logging.getLogger(__name__) @@ -89,7 +92,8 @@ class Application: logger.info("Initializing repositories") stt_repository: stt.STTProtocol = stt.OpenaiSpeechRepository(settings=settings) - chat_history_repository = agent.ChatHistoryRepository(pg_async_session=postgres_client.get_async_session()) + chat_history_repository = agent_repositories.ChatHistoryRepository(pg_async_session=postgres_client.get_async_session()) + embedding_repository = agent_repositories.EmbeddingRepository(settings) tts_yandex_repository = tts.TTSYandexRepository( tts_settings=app_split_settings.TTSYandexSettings(), @@ -101,14 +105,21 @@ class Application: is_models_from_api=True, ) + # Caches logger.info("Initializing caches") + # Tools + + agent_tools = agent_functions.OpenAIFunctions(repository=embedding_repository, pg_async_session=postgres_client.get_async_session()) + # Services logger.info("Initializing services") - stt_service: stt.SpeechService = stt.SpeechService(repository=stt_repository) # type: ignore + + + stt_service: stt.SpeechService = stt.SpeechService(repository=stt_repository) # type: ignore tts_service: tts.TTSService = tts.TTSService( # type: ignore repositories={ @@ -117,6 +128,9 @@ class Application: }, ) + agent_service: agent_services.AgentService(settings=settings, chat_repository=chat_history_repository) + # agent_service: agent_services.AgentService(settings=settings, chat_repository=chat_history_repository, tools=agent_tools) + # Handlers logger.info("Initializing handlers") @@ -127,6 +141,7 @@ class Application: voice_response_handler = api_v1_handlers.VoiceResponseHandler( stt=stt_service, tts=tts_service, + agent=agent_services, ).router logger.info("Creating application") From 2344b576d8705039b44a214be36ddba4bd1a006f Mon Sep 17 00:00:00 2001 From: Artem Litvinov Date: Sun, 15 Oct 2023 08:28:09 +0100 Subject: [PATCH 15/18] fix: start error & linting --- src/assistant/lib/agent/__init__.py | 8 +-- .../repositories/embedding_repository.py | 23 -------- src/assistant/lib/agent/services.py | 47 ++++++++------- src/assistant/lib/api/v1/handlers/__init__.py | 3 - src/assistant/lib/api/v1/handlers/agent.py | 59 ------------------- .../api/v1/handlers/voice_responce_handler.py | 16 ++--- src/assistant/lib/app/app.py | 37 ++++++------ src/assistant/lib/models/__init__.py | 7 +-- src/assistant/lib/models/agent.py | 2 - src/assistant/pyproject.toml | 10 ++-- 10 files changed, 61 insertions(+), 151 deletions(-) delete mode 100644 src/assistant/lib/api/v1/handlers/agent.py diff --git a/src/assistant/lib/agent/__init__.py b/src/assistant/lib/agent/__init__.py index 262d5aa..227a026 100644 --- a/src/assistant/lib/agent/__init__.py +++ b/src/assistant/lib/agent/__init__.py @@ -1,5 +1,5 @@ -from .services import AgentService +# from .services import AgentService -__all__ = [ - "AgentService", -] +# __all__ = [ +# "AgentService", +# ] diff --git a/src/assistant/lib/agent/repositories/embedding_repository.py b/src/assistant/lib/agent/repositories/embedding_repository.py index e6a011b..dbef18e 100644 --- a/src/assistant/lib/agent/repositories/embedding_repository.py +++ b/src/assistant/lib/agent/repositories/embedding_repository.py @@ -1,7 +1,6 @@ import logging import typing -import langchain.chat_models import openai import openai.error @@ -40,25 +39,3 @@ class EmbeddingRepository: except openai.error.OpenAIError: self.logger.exception("Failed to get async embedding for: %s", text) - - -class LlmRepository: - """A service for getting embeddings from OpenAI.""" - - def __init__(self, settings: app_settings.Settings) -> None: - """Initialize the service with an OpenAI API key.""" - self.llm = langchain.chat_models.ChatOpenAI( - temperature=0.7, - openai_api_key=self.settings.openai.api_key.get_secret_value() - ) - - async def get_chat_response(self, request: str, prompt: str) -> str: - """Get the embedding for a given text.""" - prompt = langchain.prompts.ChatPromptTemplate.from_messages( - [ - ("system", prompt), - ] - ) - chain = prompt | self.llm - response = await chain.ainvoke({"input": request}) - return response.content diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index deb3ba6..39ff18f 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -5,38 +5,33 @@ import langchain.agents import langchain.agents.format_scratchpad import langchain.agents.output_parsers import langchain.chat_models +import langchain.memory +import langchain.memory.chat_memory import langchain.prompts import langchain.schema import langchain.tools.render -import langchain.memory -import langchain.memory.chat_memory -import lib.models as models import lib.agent.repositories as lib_agent_repositories +import lib.agent.repositories.chat_repository as chat_repositories import lib.app.settings as app_settings -import lib.agent.repositories.chat_repository as _chat_repository +import lib.models as models class AgentService: def __init__( self, settings: app_settings.Settings, - chat_repository: _chat_repository.ChatHistoryRepository, - tools: lib_agent_repositories.OpenAIFunctions | None = None, + chat_repository: chat_repositories.ChatHistoryRepository, + tools: lib_agent_repositories.OpenAIFunctions, ) -> None: self.settings = settings self.tools = tools self.chat_repository = chat_repository self.logger = logging.getLogger(__name__) - async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: # Get session ID - session_request = models.RequestLastSessionId( - channel=request.channel, - user_id=request.user_id, - minutes_ago=3 - ) + session_request = models.RequestLastSessionId(channel=request.channel, user_id=request.user_id, minutes_ago=3) session_id = await self.chat_repository.get_last_session_id(session_request) if not session_id: session_id = uuid.uuid4() @@ -47,23 +42,26 @@ class AgentService: name="GetMovieByDescription", func=self.tools.get_movie_by_description, coroutine=self.tools.get_movie_by_description, - description="Get a movie by description" + description="Get a movie by description", ), ] - llm = langchain.chat_models.ChatOpenAI(temperature=self.settings.openai.agent_temperature, openai_api_key=self.settings.openai.api_key.get_secret_value()) + llm = langchain.chat_models.ChatOpenAI( + temperature=self.settings.openai.agent_temperature, + openai_api_key=self.settings.openai.api_key.get_secret_value(), + ) chat_history = [] chat_history_name = f"{chat_history=}".partition("=")[0] request_chat_history = models.RequestChatHistory(session_id=session_id) chat_history_source = await self.chat_repository.get_messages_by_sid(request_chat_history) - for entry in chat_history_source: - if entry.role == "user": - chat_history.append(langchain.schema.HumanMessage(content=entry.content)) - elif entry.role == "agent": - chat_history.append(langchain.schema.AIMessage(content=entry.content)) - + if not chat_history_source: + for entry in chat_history_source: + if entry.role == "user": + chat_history.append(langchain.schema.HumanMessage(content=entry.content)) + elif entry.role == "agent": + chat_history.append(langchain.schema.AIMessage(content=entry.content)) prompt = langchain.prompts.ChatPromptTemplate.from_messages( [ @@ -94,7 +92,6 @@ class AgentService: | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() ) - agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=False) chat_history = [] # temporary disable chat_history response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) @@ -103,16 +100,18 @@ class AgentService: session_id=session_id, user_id=request.user_id, channel=request.channel, - message={"role": "user", "content": request.text} + message={"role": "user", "content": request.text}, ) ai_response = models.RequestChatMessage( session_id=session_id, user_id=request.user_id, channel=request.channel, - message={"role": "assistant", "content": response["output"]} + message={"role": "assistant", "content": response["output"]}, ) await self.chat_repository.add_message(user_request) await self.chat_repository.add_message(ai_response) - return response + print("RES:", response) + + return models.AgentCreateResponseModel(text="response") diff --git a/src/assistant/lib/api/v1/handlers/__init__.py b/src/assistant/lib/api/v1/handlers/__init__.py index 178157c..00d7189 100644 --- a/src/assistant/lib/api/v1/handlers/__init__.py +++ b/src/assistant/lib/api/v1/handlers/__init__.py @@ -1,10 +1,7 @@ -from .agent import AgentHandler from .health import basic_router from .voice_responce_handler import VoiceResponseHandler - __all__ = [ - "AgentHandler", "VoiceResponseHandler", "basic_router", ] diff --git a/src/assistant/lib/api/v1/handlers/agent.py b/src/assistant/lib/api/v1/handlers/agent.py deleted file mode 100644 index 6d2d57e..0000000 --- a/src/assistant/lib/api/v1/handlers/agent.py +++ /dev/null @@ -1,59 +0,0 @@ -import uuid - -import fastapi - -import lib.agent.repositories as agent_repositories -import lib.models as models - - -class AgentHandler: - def __init__(self, chat_history_repository: agent_repositories.ChatHistoryRepository): - self.chat_history_repository = chat_history_repository - self.router = fastapi.APIRouter() - self.router.add_api_route( - "/", - self.get_agent, - methods=["GET"], - summary="Статус работоспособности", - description="Проверяет доступность сервиса FastAPI.", - ) - self.router.add_api_route( - "/add", - self.add_message, - methods=["GET"], - summary="Статус работоспособности", - description="Проверяет доступность сервиса FastAPI.", - ) - self.router.add_api_route( - "/messages", - self.get_messages, - methods=["GET"], - summary="Статус работоспособности", - description="Проверяет доступность сервиса FastAPI.", - ) - - async def get_agent(self): - request = models.RequestLastSessionId(channel="test", user_id="user_id_1", minutes_ago=3) - response = await self.chat_history_repository.get_last_session_id(request=request) - print("RESPONSE: ", response) - return {"response": response} - - async def add_message(self): - sid: uuid.UUID = uuid.UUID("0cd3c882-affd-4929-aff1-e1724f5b54f2") - import faker - - fake = faker.Faker() - - message = models.RequestChatMessage( - session_id=sid, user_id="user_id_1", channel="test", message={"role": "system", "content": fake.sentence()} - ) - await self.chat_history_repository.add_message(request=message) - return {"response": "ok"} - - async def get_messages(self): - sid: uuid.UUID = uuid.UUID("0cd3c882-affd-4929-aff1-e1724f5b54f2") - - request = models.RequestChatHistory(session_id=sid) - response = await self.chat_history_repository.get_messages_by_sid(request=request) - print("RESPONSE: ", response) - return {"response": response} diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 07cb3f7..17064e0 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -3,10 +3,10 @@ import io import fastapi -import lib.stt.services as stt_services import lib.agent.services as agent_service -import lib.tts.services as tts_service import lib.models as models +import lib.stt.services as stt_services +import lib.tts.services as tts_service class VoiceResponseHandler: @@ -14,11 +14,11 @@ class VoiceResponseHandler: self, stt: stt_services.SpeechService, tts: tts_service.TTSService, - # agent: agent_service.AgentService, + agent: agent_service.AgentService, ): self.stt = stt self.tts = tts - # self.agent = agent + self.agent = agent self.router = fastapi.APIRouter() self.router.add_api_route( "/", @@ -38,12 +38,12 @@ class VoiceResponseHandler: if voice_text == "": raise fastapi.HTTPException(status_code=http.HTTPStatus.BAD_REQUEST, detail="Speech recognition failed") - # agent_request = models.AgentCreateRequestModel(channel=channel, user_id=user_id, text=voice_text) - # reply_text = await self.agent.process_request(agent_request) - reply_text = "hi there" + agent_request = models.AgentCreateRequestModel(channel=channel, user_id=user_id, text=voice_text) + reply_text = await self.agent.process_request(agent_request) + response = await self.tts.get_audio_as_bytes( models.TTSCreateRequestModel( - text=reply_text, + text=reply_text.text, ) ) return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg") diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index c0741cf..d7f29c7 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -6,6 +6,9 @@ import typing import fastapi import uvicorn +import lib.agent.repositories as agent_repositories +import lib.agent.repositories.openai_functions as agent_functions +import lib.agent.services as agent_services import lib.api.v1.handlers as api_v1_handlers import lib.app.errors as app_errors import lib.app.settings as app_settings @@ -14,9 +17,6 @@ import lib.clients as clients import lib.models as models import lib.stt as stt import lib.tts as tts -import lib.agent.repositories as agent_repositories -import lib.agent.repositories.openai_functions as agent_functions -import lib.agent.services as agent_services logger = logging.getLogger(__name__) @@ -92,9 +92,14 @@ class Application: logger.info("Initializing repositories") stt_repository: stt.STTProtocol = stt.OpenaiSpeechRepository(settings=settings) - chat_history_repository = agent_repositories.ChatHistoryRepository(pg_async_session=postgres_client.get_async_session()) - embedding_repository = agent_repositories.EmbeddingRepository(settings) - + chat_history_repository = agent_repositories.ChatHistoryRepository( + pg_async_session=postgres_client.get_async_session() + ) + embedding_repository = agent_repositories.EmbeddingRepository(settings=settings) + agent_tools = agent_functions.OpenAIFunctions( + repository=embedding_repository, pg_async_session=postgres_client.get_async_session() + ) + agent_tools = None tts_yandex_repository = tts.TTSYandexRepository( tts_settings=app_split_settings.TTSYandexSettings(), client=http_yandex_tts_client, @@ -105,43 +110,40 @@ class Application: is_models_from_api=True, ) - # Caches logger.info("Initializing caches") # Tools - agent_tools = agent_functions.OpenAIFunctions(repository=embedding_repository, pg_async_session=postgres_client.get_async_session()) - # Services logger.info("Initializing services") + stt_service: stt.SpeechService = stt.SpeechService(repository=stt_repository) - stt_service: stt.SpeechService = stt.SpeechService(repository=stt_repository) # type: ignore - - tts_service: tts.TTSService = tts.TTSService( # type: ignore + tts_service: tts.TTSService = tts.TTSService( repositories={ models.VoiceModelProvidersEnum.YANDEX: tts_yandex_repository, models.VoiceModelProvidersEnum.ELEVEN_LABS: tts_eleven_labs_repository, }, ) + + agent_service = agent_services.AgentService( + settings=settings, chat_repository=chat_history_repository, tools=agent_tools + ) - agent_service: agent_services.AgentService(settings=settings, chat_repository=chat_history_repository) - # agent_service: agent_services.AgentService(settings=settings, chat_repository=chat_history_repository, tools=agent_tools) # Handlers logger.info("Initializing handlers") liveness_probe_handler = api_v1_handlers.basic_router - agent_handler = api_v1_handlers.AgentHandler(chat_history_repository=chat_history_repository).router # TODO: объявить сервисы tts и openai и добавить их в voice_response_handler voice_response_handler = api_v1_handlers.VoiceResponseHandler( stt=stt_service, tts=tts_service, - agent=agent_services, + agent=agent_service, ).router logger.info("Creating application") @@ -155,8 +157,7 @@ class Application: ) # Routes - fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) - fastapi_app.include_router(agent_handler, prefix="/api/v1/agent", tags=["testing"]) + fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) fastapi_app.include_router(voice_response_handler, prefix="/api/v1/voice", tags=["voice"]) application = Application( diff --git a/src/assistant/lib/models/__init__.py b/src/assistant/lib/models/__init__.py index 01c3fc9..bbadc3a 100644 --- a/src/assistant/lib/models/__init__.py +++ b/src/assistant/lib/models/__init__.py @@ -1,21 +1,19 @@ +from .agent import * from .chat_history import Message, RequestChatHistory, RequestChatMessage, RequestLastSessionId from .embedding import Embedding from .movies import Movie from .token import Token from .tts import * -from .agent import * - # __all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"] __all__ = [ "AVAILABLE_MODELS_TYPE", - # "Base", + "AgentCreateRequestModel", "BaseLanguageCodesEnum", "BaseVoiceModel", "ElevenLabsLanguageCodesEnum", "ElevenLabsListVoiceModelsModel", "ElevenLabsVoiceModel", - # "IdCreatedUpdatedBaseMixin", "LANGUAGE_CODES_ENUM_TYPE", "LIST_VOICE_MODELS_TYPE", "TTSCreateRequestModel", @@ -26,5 +24,4 @@ __all__ = [ "YandexLanguageCodesEnum", "YandexListVoiceModelsModel", "YandexVoiceModel", - "AgentCreateRequestModel", ] diff --git a/src/assistant/lib/models/agent.py b/src/assistant/lib/models/agent.py index ecbde51..917985d 100644 --- a/src/assistant/lib/models/agent.py +++ b/src/assistant/lib/models/agent.py @@ -1,5 +1,3 @@ -import uuid - import pydantic diff --git a/src/assistant/pyproject.toml b/src/assistant/pyproject.toml index 06873f6..485b554 100644 --- a/src/assistant/pyproject.toml +++ b/src/assistant/pyproject.toml @@ -25,21 +25,21 @@ asyncpg = "^0.28.0" fastapi = "0.103.1" greenlet = "^2.0.2" httpx = "^0.25.0" +langchain = "^0.0.314" multidict = "^6.0.4" +openai = "^0.28.1" orjson = "^3.9.7" +pgvector = "^0.2.3" psycopg2-binary = "^2.9.9" pydantic = {extras = ["email"], version = "^2.3.0"} pydantic-settings = "^2.0.3" pytest-asyncio = "^0.21.1" python = "^3.11" python-jose = "^3.3.0" +python-magic = "^0.4.27" +python-multipart = "^0.0.6" sqlalchemy = "^2.0.20" uvicorn = "^0.23.2" -pgvector = "^0.2.3" -python-magic = "^0.4.27" -openai = "^0.28.1" -python-multipart = "^0.0.6" -langchain = "^0.0.314" [tool.poetry.dev-dependencies] black = "^23.7.0" From fbc0b4411b44b28cbd57f425a53701028702c77d Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 11:35:53 +0300 Subject: [PATCH 16/18] feat: [#45] llm_agent --- src/assistant/lib/agent/services.py | 28 ++++++++++++++++--- .../api/v1/handlers/voice_responce_handler.py | 4 +-- src/assistant/lib/app/app.py | 5 ++-- .../lib/app/split_settings/tts/eleven_labs.py | 2 +- .../lib/app/split_settings/tts/yandex.py | 2 +- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 39ff18f..bbb889a 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -5,6 +5,7 @@ import langchain.agents import langchain.agents.format_scratchpad import langchain.agents.output_parsers import langchain.chat_models +import langchain.chains import langchain.memory import langchain.memory.chat_memory import langchain.prompts @@ -29,12 +30,28 @@ class AgentService: self.chat_repository = chat_repository self.logger = logging.getLogger(__name__) + + async def send_message_request(self, request: str, system_prompt: str): + prompt = langchain.prompts.ChatPromptTemplate.from_messages([("system", system_prompt),]) + llm = langchain.chat_models.ChatOpenAI( + temperature=self.settings.openai.agent_temperature, + openai_api_key=self.settings.openai.api_key.get_secret_value(), + ) + chain = langchain.chains.LLMChain(llm=llm, prompt=prompt) + result = await chain.ainvoke({"input": request}) + return result["text"] + + + async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: # Get session ID + request_text = request.text + translate_text = await self.send_message_request(request=request_text, system_prompt="Translation into English") session_request = models.RequestLastSessionId(channel=request.channel, user_id=request.user_id, minutes_ago=3) session_id = await self.chat_repository.get_last_session_id(session_request) if not session_id: session_id = uuid.uuid4() + await self.send_message_request(request='test', system_prompt="test") # Declare tools (OpenAI functions) tools = [ @@ -94,7 +111,7 @@ class AgentService: agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=False) chat_history = [] # temporary disable chat_history - response = await agent_executor.ainvoke({"input": request.text, "chat_history": chat_history}) + response = await agent_executor.ainvoke({"input": translate_text, "chat_history": chat_history}) user_request = models.RequestChatMessage( session_id=session_id, @@ -112,6 +129,9 @@ class AgentService: await self.chat_repository.add_message(user_request) await self.chat_repository.add_message(ai_response) - print("RES:", response) - - return models.AgentCreateResponseModel(text="response") + response_translate = await self.send_message_request( + request=f"Original text: {request_text}. Answer: {response['output']}", + system_prompt="Translate the answer into the language of the original text", + ) + print(response_translate) + return models.AgentCreateResponseModel(text=response_translate) diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 17064e0..4c62677 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -30,8 +30,8 @@ class VoiceResponseHandler: async def voice_response( self, - channel: str, - user_id: str, + channel: str="tg", + user_id: str="1234", voice: bytes = fastapi.File(...), ) -> fastapi.responses.StreamingResponse: voice_text: str = await self.stt.recognize(voice) diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index d7f29c7..cf80d7c 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -99,7 +99,6 @@ class Application: agent_tools = agent_functions.OpenAIFunctions( repository=embedding_repository, pg_async_session=postgres_client.get_async_session() ) - agent_tools = None tts_yandex_repository = tts.TTSYandexRepository( tts_settings=app_split_settings.TTSYandexSettings(), client=http_yandex_tts_client, @@ -128,7 +127,7 @@ class Application: models.VoiceModelProvidersEnum.ELEVEN_LABS: tts_eleven_labs_repository, }, ) - + agent_service = agent_services.AgentService( settings=settings, chat_repository=chat_history_repository, tools=agent_tools ) @@ -157,7 +156,7 @@ class Application: ) # Routes - fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) + fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"]) fastapi_app.include_router(voice_response_handler, prefix="/api/v1/voice", tags=["voice"]) application = Application( diff --git a/src/assistant/lib/app/split_settings/tts/eleven_labs.py b/src/assistant/lib/app/split_settings/tts/eleven_labs.py index f664176..bb97e60 100644 --- a/src/assistant/lib/app/split_settings/tts/eleven_labs.py +++ b/src/assistant/lib/app/split_settings/tts/eleven_labs.py @@ -15,7 +15,7 @@ class TTSElevenLabsSettings(pydantic_settings.BaseSettings): api_key: pydantic.SecretStr = pydantic.Field(default=...) default_voice_id: str = "EXAVITQu4vr4xnSDxMaL" base_url: str = "https://api.elevenlabs.io/v1/" - timeout_seconds: int = 30 + timeout_seconds: int = 120 @property def base_headers(self) -> dict[str, str]: diff --git a/src/assistant/lib/app/split_settings/tts/yandex.py b/src/assistant/lib/app/split_settings/tts/yandex.py index 76d0829..9968bca 100644 --- a/src/assistant/lib/app/split_settings/tts/yandex.py +++ b/src/assistant/lib/app/split_settings/tts/yandex.py @@ -18,7 +18,7 @@ class TTSYandexSettings(pydantic_settings.BaseSettings): sample_rate_hertz: int = 48000 api_key: pydantic.SecretStr = pydantic.Field(default=...) base_url: str = "https://tts.api.cloud.yandex.net/speech/v1/" - timeout_seconds: int = 30 + timeout_seconds: int = 120 @property def base_headers(self) -> dict[str, str]: From a4dffb739c7f6ab5b35a12877267d70aad74ed64 Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 11:35:59 +0300 Subject: [PATCH 17/18] Changes by ijaric --- .../lib/agent/repositories/openai_functions.py | 2 +- src/assistant/lib/agent/services.py | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/assistant/lib/agent/repositories/openai_functions.py b/src/assistant/lib/agent/repositories/openai_functions.py index 9a463d4..c487a77 100644 --- a/src/assistant/lib/agent/repositories/openai_functions.py +++ b/src/assistant/lib/agent/repositories/openai_functions.py @@ -22,7 +22,7 @@ class OpenAIFunctions: self.repository = repository async def get_movie_by_description(self, description: str) -> list[models.Movie] | None: - """Provide a movie data by description.""" + """Use this function to find data about a movie by movie's description.""" self.logger.info("Request to get movie by description: %s", description) embedded_description = await self.repository.aget_embedding(description) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index bbb889a..60c0f5d 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -59,7 +59,7 @@ class AgentService: name="GetMovieByDescription", func=self.tools.get_movie_by_description, coroutine=self.tools.get_movie_by_description, - description="Get a movie by description", + description="Use this function to find data about a movie by movie's description", ), ] @@ -84,7 +84,11 @@ class AgentService: [ ( "system", - "Act as an advanced AI assistant with extensive capabilities, you have a vast knowledge base about movies and their related aspects. If you are asked about a movie, please use provided functions to retrive data about movies. You can receive a question in any language. Translate it into English. If you don't know the answer, just say that you don't know, don't try to make up an answer. Be concise. ", + """1. Translate each inbound request into English language. Before calling any functions. +2. You are movie expert with a vast knowledge base about movies and their related aspects. +3. Answer always in Russian language. +4. Be concise. You answer must be within 100-150 words.""" + ), langchain.prompts.MessagesPlaceholder(variable_name=chat_history_name), ("user", "{input}"), @@ -109,7 +113,7 @@ class AgentService: | langchain.agents.output_parsers.OpenAIFunctionsAgentOutputParser() ) - agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=False) + agent_executor = langchain.agents.AgentExecutor(agent=agent, tools=tools, verbose=True) chat_history = [] # temporary disable chat_history response = await agent_executor.ainvoke({"input": translate_text, "chat_history": chat_history}) From 852ba133d5d9c147f09d5d38aa739279d401110b Mon Sep 17 00:00:00 2001 From: jsdio Date: Sun, 15 Oct 2023 11:36:47 +0300 Subject: [PATCH 18/18] feat: [#45] small fixes --- src/assistant/lib/agent/services.py | 16 +++++------ .../api/v1/handlers/voice_responce_handler.py | 4 +-- src/assistant/lib/app/app.py | 1 - src/assistant/poetry.lock | 28 +++++++++++++------ 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/assistant/lib/agent/services.py b/src/assistant/lib/agent/services.py index 60c0f5d..59e4603 100644 --- a/src/assistant/lib/agent/services.py +++ b/src/assistant/lib/agent/services.py @@ -4,8 +4,8 @@ import uuid import langchain.agents import langchain.agents.format_scratchpad import langchain.agents.output_parsers -import langchain.chat_models import langchain.chains +import langchain.chat_models import langchain.memory import langchain.memory.chat_memory import langchain.prompts @@ -30,9 +30,12 @@ class AgentService: self.chat_repository = chat_repository self.logger = logging.getLogger(__name__) - async def send_message_request(self, request: str, system_prompt: str): - prompt = langchain.prompts.ChatPromptTemplate.from_messages([("system", system_prompt),]) + prompt = langchain.prompts.ChatPromptTemplate.from_messages( + [ + ("system", system_prompt), + ] + ) llm = langchain.chat_models.ChatOpenAI( temperature=self.settings.openai.agent_temperature, openai_api_key=self.settings.openai.api_key.get_secret_value(), @@ -41,8 +44,6 @@ class AgentService: result = await chain.ainvoke({"input": request}) return result["text"] - - async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel: # Get session ID request_text = request.text @@ -51,7 +52,7 @@ class AgentService: session_id = await self.chat_repository.get_last_session_id(session_request) if not session_id: session_id = uuid.uuid4() - await self.send_message_request(request='test', system_prompt="test") + await self.send_message_request(request="test", system_prompt="test") # Declare tools (OpenAI functions) tools = [ @@ -87,8 +88,7 @@ class AgentService: """1. Translate each inbound request into English language. Before calling any functions. 2. You are movie expert with a vast knowledge base about movies and their related aspects. 3. Answer always in Russian language. -4. Be concise. You answer must be within 100-150 words.""" - +4. Be concise. You answer must be within 100-150 words.""", ), langchain.prompts.MessagesPlaceholder(variable_name=chat_history_name), ("user", "{input}"), diff --git a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py index 4c62677..c8c2121 100644 --- a/src/assistant/lib/api/v1/handlers/voice_responce_handler.py +++ b/src/assistant/lib/api/v1/handlers/voice_responce_handler.py @@ -30,8 +30,8 @@ class VoiceResponseHandler: async def voice_response( self, - channel: str="tg", - user_id: str="1234", + channel: str = "tg", + user_id: str = "1234", voice: bytes = fastapi.File(...), ) -> fastapi.responses.StreamingResponse: voice_text: str = await self.stt.recognize(voice) diff --git a/src/assistant/lib/app/app.py b/src/assistant/lib/app/app.py index cf80d7c..1310c2b 100644 --- a/src/assistant/lib/app/app.py +++ b/src/assistant/lib/app/app.py @@ -132,7 +132,6 @@ class Application: settings=settings, chat_repository=chat_history_repository, tools=agent_tools ) - # Handlers logger.info("Initializing handlers") diff --git a/src/assistant/poetry.lock b/src/assistant/poetry.lock index 875acce..521fbdf 100644 --- a/src/assistant/poetry.lock +++ b/src/assistant/poetry.lock @@ -960,6 +960,16 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, + {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -1762,6 +1772,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1769,8 +1780,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1787,6 +1805,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1794,6 +1813,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1921,9 +1941,7 @@ python-versions = ">=3.7" files = [ {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f146c61ae128ab43ea3a0955de1af7e1633942c2b2b4985ac51cc292daf33222"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:875de9414393e778b655a3d97d60465eb3fae7c919e88b70cc10b40b9f56042d"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13790cb42f917c45c9c850b39b9941539ca8ee7917dacf099cc0b569f3d40da7"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e04ab55cf49daf1aeb8c622c54d23fa4bec91cb051a43cc24351ba97e1dd09f5"}, - {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a42c9fa3abcda0dcfad053e49c4f752eef71ecd8c155221e18b99d4224621176"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:14cd3bcbb853379fef2cd01e7c64a5d6f1d005406d877ed9509afb7a05ff40a5"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win32.whl", hash = "sha256:d143c5a9dada696bcfdb96ba2de4a47d5a89168e71d05a076e88a01386872f97"}, {file = "SQLAlchemy-2.0.22-cp310-cp310-win_amd64.whl", hash = "sha256:ccd87c25e4c8559e1b918d46b4fa90b37f459c9b4566f1dfbce0eb8122571547"}, @@ -1944,25 +1962,19 @@ files = [ {file = "SQLAlchemy-2.0.22-cp312-cp312-win32.whl", hash = "sha256:154a32f3c7b00de3d090bc60ec8006a78149e221f1182e3edcf0376016be9396"}, {file = "SQLAlchemy-2.0.22-cp312-cp312-win_amd64.whl", hash = "sha256:129415f89744b05741c6f0b04a84525f37fbabe5dc3774f7edf100e7458c48cd"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3940677d341f2b685a999bffe7078697b5848a40b5f6952794ffcf3af150c301"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55914d45a631b81a8a2cb1a54f03eea265cf1783241ac55396ec6d735be14883"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2096d6b018d242a2bcc9e451618166f860bb0304f590d205173d317b69986c95"}, - {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:19c6986cf2fb4bc8e0e846f97f4135a8e753b57d2aaaa87c50f9acbe606bd1db"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ac28bd6888fe3c81fbe97584eb0b96804bd7032d6100b9701255d9441373ec1"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win32.whl", hash = "sha256:cb9a758ad973e795267da334a92dd82bb7555cb36a0960dcabcf724d26299db8"}, {file = "SQLAlchemy-2.0.22-cp37-cp37m-win_amd64.whl", hash = "sha256:40b1206a0d923e73aa54f0a6bd61419a96b914f1cd19900b6c8226899d9742ad"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3aa1472bf44f61dd27987cd051f1c893b7d3b17238bff8c23fceaef4f1133868"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:56a7e2bb639df9263bf6418231bc2a92a773f57886d371ddb7a869a24919face"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccca778c0737a773a1ad86b68bda52a71ad5950b25e120b6eb1330f0df54c3d0"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6c3e9350f9fb16de5b5e5fbf17b578811a52d71bb784cc5ff71acb7de2a7f9"}, - {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:564e9f9e4e6466273dbfab0e0a2e5fe819eec480c57b53a2cdee8e4fdae3ad5f"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:af66001d7b76a3fab0d5e4c1ec9339ac45748bc4a399cbc2baa48c1980d3c1f4"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win32.whl", hash = "sha256:9e55dff5ec115316dd7a083cdc1a52de63693695aecf72bc53a8e1468ce429e5"}, {file = "SQLAlchemy-2.0.22-cp38-cp38-win_amd64.whl", hash = "sha256:4e869a8ff7ee7a833b74868a0887e8462445ec462432d8cbeff5e85f475186da"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9886a72c8e6371280cb247c5d32c9c8fa141dc560124348762db8a8b236f8692"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a571bc8ac092a3175a1d994794a8e7a1f2f651e7c744de24a19b4f740fe95034"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8db5ba8b7da759b727faebc4289a9e6a51edadc7fc32207a30f7c6203a181592"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b0b3f2686c3f162123adba3cb8b626ed7e9b8433ab528e36ed270b4f70d1cdb"}, - {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0c1fea8c0abcb070ffe15311853abfda4e55bf7dc1d4889497b3403629f3bf00"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4bb062784f37b2d75fd9b074c8ec360ad5df71f933f927e9e95c50eb8e05323c"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win32.whl", hash = "sha256:58a3aba1bfb32ae7af68da3f277ed91d9f57620cf7ce651db96636790a78b736"}, {file = "SQLAlchemy-2.0.22-cp39-cp39-win_amd64.whl", hash = "sha256:92e512a6af769e4725fa5b25981ba790335d42c5977e94ded07db7d641490a85"},