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:
parent
d2933ad8f7
commit
b9393d7072
|
@ -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:
|
||||||
|
|
|
@ -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]"""
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}"
|
||||||
|
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue
Block a user