1
0
mirror of https://github.com/ijaric/voice_assistant.git synced 2025-05-24 14:33:26 +00:00

Changes by aleksandr

This commit is contained in:
Григорич 2023-10-15 07:59:24 +03:00
parent d2933ad8f7
commit b9393d7072
9 changed files with 214 additions and 57 deletions

View File

@ -36,18 +36,19 @@ class OpenAIFunctions:
.order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root)) .order_by(orm_models.FilmWork.embeddings.cosine_distance(embedded_description.root))
.limit(5) .limit(5)
) )
neighbours = session.scalars(stmt) response = await session.execute(stmt)
for neighbour in await neighbours: neighbours = response.scalars()
for neighbour in neighbours:
result.append(models.Movie(**neighbour.__dict__)) result.append(models.Movie(**neighbour.__dict__))
return result return result
except sqlalchemy.exc.SQLAlchemyError as error: except sqlalchemy.exc.SQLAlchemyError as error:
self.logger.exception("Error: %s", error) self.logger.exception("Error: %s", error)
@langchain.agents.tool @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.""" """Provide a movie data by movie id."""
self.logger.info("Request to get movie by id: %s", id) # self.logger.info("Request to get movie by id: %s", id)
return None return f"hello world {id}"
@langchain.agents.tool @langchain.agents.tool
def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None: def get_similar_movies(self, id: uuid.UUID) -> list[models.Movie] | None:

View File

@ -26,7 +26,7 @@ class EmbeddingRepository:
) # type: ignore[reportGeneralTypeIssues] ) # type: ignore[reportGeneralTypeIssues]
return models.Embedding(**response["data"][0]["embedding"]) return models.Embedding(**response["data"][0]["embedding"])
except openai.error.OpenAIError: 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: async def aget_embedding(self, text: str, model: str = "text-embedding-ada-002") -> models.Embedding | None:
"""Get the embedding for a given text.[Async]""" """Get the embedding for a given text.[Async]"""

View File

@ -1,5 +1,6 @@
import asyncio import asyncio
import logging import logging
import typing
import uuid import uuid
import fastapi import fastapi
@ -11,7 +12,7 @@ import langchain.prompts
import langchain.schema import langchain.schema
import langchain.tools.render 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.agent.openai_functions as openai_functions
import lib.app.settings as app_settings 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: def __init__(self, settings: app_settings.Settings, tools: openai_functions.OpenAIFunctions) -> None:
self.settings = settings self.settings = settings
self.tools = tools 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: async def process_request(self, request: models.AgentCreateRequestModel) -> models.AgentCreateResponseModel:
@ -55,12 +171,56 @@ class AgentService:
return response_model 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():
# 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) # 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 where halfling bring the ring to the volcano?"
# first_question = ( # first_question = (
# "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?" # "What is the movie about a famous country singer meet a talented singer and songwriter who works as a waitress?"
@ -76,5 +236,5 @@ class AgentService:
# final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history}) # final_result = await agent_executor.ainvoke({"input": third_question, "chat_history": chat_history})
# if __name__ == "__main__": if __name__ == "__main__":
# asyncio.run(main()) asyncio.run(main())

View File

@ -5,18 +5,18 @@ import fastapi
import lib.stt.services as stt_services import lib.stt.services as stt_services
# import lib.tts.services as tts_service import lib.tts.services as tts_service
# import lib.models as models import lib.models as models
class VoiceResponseHandler: class VoiceResponseHandler:
def __init__( def __init__(
self, self,
stt: stt_services.SpeechService, stt: stt_services.SpeechService,
# tts: tts_service.TTSService, tts: tts_service.TTSService,
): ):
self.stt = stt self.stt = stt
# self.tts = tts self.tts = tts
self.router = fastapi.APIRouter() self.router = fastapi.APIRouter()
self.router.add_api_route( self.router.add_api_route(
"/", "/",
@ -36,10 +36,10 @@ class VoiceResponseHandler:
# TODO: Добавить обработку текста через клиента openai # TODO: Добавить обработку текста через клиента openai
# TODO: Добавить синтез речи через клиента tts # TODO: Добавить синтез речи через клиента tts
# TODO: Заменить заглушку на реальный ответ # TODO: Заменить заглушку на реальный ответ
# response = await self.tts.get_audio_as_bytes( response = await self.tts.get_audio_as_bytes(
# models.TTSCreateRequestModel( models.TTSCreateRequestModel(
# text=voice_text, text=voice_text,
# ) )
# ) )
# return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg") return fastapi.responses.StreamingResponse(io.BytesIO(response.audio_content), media_type="audio/ogg")
return fastapi.responses.StreamingResponse(io.BytesIO(voice), media_type="audio/ogg") # return fastapi.responses.StreamingResponse(io.BytesIO(voice), media_type="audio/ogg")

View File

@ -127,7 +127,7 @@ class Application:
# TODO: объявить сервисы tts и openai и добавить их в voice_response_handler # TODO: объявить сервисы tts и openai и добавить их в voice_response_handler
voice_response_handler = api_v1_handlers.VoiceResponseHandler( voice_response_handler = api_v1_handlers.VoiceResponseHandler(
stt=stt_service, stt=stt_service,
# tts=tts_service, # TODO tts=tts_service,
).router ).router
logger.info("Creating application") logger.info("Creating application")

View File

@ -16,3 +16,4 @@ class OpenaiSettings(pydantic_settings.BaseSettings):
default=..., validation_alias=pydantic.AliasChoices("api_key", "openai_api_key") default=..., validation_alias=pydantic.AliasChoices("api_key", "openai_api_key")
) )
stt_model: str = "whisper-1" stt_model: str = "whisper-1"
agent_temperature: float = 0.7

View File

@ -41,4 +41,4 @@ class PostgresSettings(pydantic_settings.BaseSettings):
@property @property
def dsn_as_safe_url(self) -> str: 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}"

View File

@ -3,18 +3,19 @@ from .embedding import Embedding
from .movies import Movie from .movies import Movie
from .token import Token from .token import Token
from .tts import * from .tts import *
from .agent import *
__all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"] # __all__ = ["Embedding", "Message", "Movie", "RequestChatHistory", "RequestChatMessage", "RequestLastSessionId", "Token"]
__all__ = [ __all__ = [
"AVAILABLE_MODELS_TYPE", "AVAILABLE_MODELS_TYPE",
"Base", # "Base",
"BaseLanguageCodesEnum", "BaseLanguageCodesEnum",
"BaseVoiceModel", "BaseVoiceModel",
"ElevenLabsLanguageCodesEnum", "ElevenLabsLanguageCodesEnum",
"ElevenLabsListVoiceModelsModel", "ElevenLabsListVoiceModelsModel",
"ElevenLabsVoiceModel", "ElevenLabsVoiceModel",
"IdCreatedUpdatedBaseMixin", # "IdCreatedUpdatedBaseMixin",
"LANGUAGE_CODES_ENUM_TYPE", "LANGUAGE_CODES_ENUM_TYPE",
"LIST_VOICE_MODELS_TYPE", "LIST_VOICE_MODELS_TYPE",
"TTSCreateRequestModel", "TTSCreateRequestModel",
@ -25,4 +26,5 @@ __all__ = [
"YandexLanguageCodesEnum", "YandexLanguageCodesEnum",
"YandexListVoiceModelsModel", "YandexListVoiceModelsModel",
"YandexVoiceModel", "YandexVoiceModel",
"AgentCreateRequestModel",
] ]

View File

@ -13,7 +13,7 @@ profile = "black"
py_version = "311" py_version = "311"
[tool.poetry] [tool.poetry]
authors = ["ijaric@gmail.com", "jsdio@jsdio.ru"] authors = ["jsdio@jsdio.ru"]
description = "" description = ""
name = "fastapi_project" name = "fastapi_project"
readme = "README.md" readme = "README.md"
@ -22,29 +22,23 @@ version = "0.1.0"
[tool.poetry.dependencies] [tool.poetry.dependencies]
alembic = "^1.12.0" alembic = "^1.12.0"
asyncpg = "^0.28.0" asyncpg = "^0.28.0"
dill = "^0.3.7"
faker = "^19.10.0"
fastapi = "0.103.1" fastapi = "0.103.1"
greenlet = "^2.0.2" greenlet = "^2.0.2"
httpx = "^0.25.0" httpx = "^0.25.0"
langchain = "^0.0.312"
openai = "^0.28.1"
pgvector = "^0.2.3"
multidict = "^6.0.4" multidict = "^6.0.4"
openai = "^0.28.1" orjson = "^3.9.7"
orjson = "3.9.7"
psycopg2-binary = "^2.9.9" psycopg2-binary = "^2.9.9"
pydantic = {extras = ["email"], version = "^2.3.0"} pydantic = {extras = ["email"], version = "^2.3.0"}
pydantic-settings = "^2.0.3" pydantic-settings = "^2.0.3"
pytest = "^7.4.2"
pytest-asyncio = "^0.21.1" pytest-asyncio = "^0.21.1"
python = "^3.11" python = "^3.11"
python-jose = "^3.3.0" python-jose = "^3.3.0"
python-magic = "^0.4.27"
python-multipart = "^0.0.6"
sqlalchemy = "^2.0.20" sqlalchemy = "^2.0.20"
uvicorn = "^0.23.2" 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] [tool.poetry.dev-dependencies]
black = "^23.7.0" black = "^23.7.0"
@ -98,9 +92,8 @@ variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
[tool.pyright] [tool.pyright]
exclude = [ exclude = [
".venv",
"alembic"
".pytest_cache", ".pytest_cache",
".venv"
] ]
pythonPlatform = "All" pythonPlatform = "All"
pythonVersion = "3.11" pythonVersion = "3.11"