feat(bot): developing integration with solaris
This commit is contained in:
@@ -2,6 +2,7 @@ from aiogram import Router
|
|||||||
|
|
||||||
from . import (
|
from . import (
|
||||||
initialize,
|
initialize,
|
||||||
|
message,
|
||||||
start,
|
start,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,4 +12,5 @@ router = Router()
|
|||||||
router.include_routers(
|
router.include_routers(
|
||||||
start.router,
|
start.router,
|
||||||
initialize.router,
|
initialize.router,
|
||||||
|
message.router,
|
||||||
)
|
)
|
||||||
|
|||||||
1
src/bot/handlers/message/__init__.py
Normal file
1
src/bot/handlers/message/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .message import router
|
||||||
29
src/bot/handlers/message/message.py
Normal file
29
src/bot/handlers/message/message.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from aiogram import F, Router
|
||||||
|
from aiogram.types import Message
|
||||||
|
from dishka import FromDishka
|
||||||
|
|
||||||
|
from bot.modules.solaris.services.respond import RespondService
|
||||||
|
from bot.modules.solaris.structures import InputMessage
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
@router.message(F.text)
|
||||||
|
async def message_handler(
|
||||||
|
message: Message, respond_service: FromDishka[RespondService]
|
||||||
|
):
|
||||||
|
input_message = InputMessage(
|
||||||
|
time=message.date,
|
||||||
|
message_id=message.message_id,
|
||||||
|
text=message.text,
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
username=message.from_user.full_name,
|
||||||
|
reply_to=(
|
||||||
|
message.reply_to_message.message_id if message.reply_to_message else None
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
output_messages = await respond_service.process_message(input_message)
|
||||||
|
|
||||||
|
for msg in output_messages:
|
||||||
|
await message.answer(msg.text)
|
||||||
@@ -7,28 +7,26 @@ from utils.config import dconfig
|
|||||||
|
|
||||||
from ..constants import SAFETY_SETTINGS
|
from ..constants import SAFETY_SETTINGS
|
||||||
from ..structures import InputMessage, OutputMessage
|
from ..structures import InputMessage, OutputMessage
|
||||||
|
from ..tools import RESPOND_TOOLS
|
||||||
|
|
||||||
|
|
||||||
class RespondAgent:
|
class RespondAgent:
|
||||||
chat: chats.AsyncChat
|
|
||||||
|
|
||||||
def __init__(self, client: genai.client.AsyncClient) -> None:
|
def __init__(self, client: genai.client.AsyncClient) -> None:
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
async def load_chat(self, history: list[types.Content], system_prompt: str):
|
async def generate_response(
|
||||||
self.chat = self.client.chats.create(
|
self, system_prompt: str, history: list[types.Content]
|
||||||
|
) -> list[OutputMessage]:
|
||||||
|
content = await self.client.models.generate_content(
|
||||||
model=(await dconfig()).models.respond_model,
|
model=(await dconfig()).models.respond_model,
|
||||||
|
contents=history,
|
||||||
config=types.GenerateContentConfig(
|
config=types.GenerateContentConfig(
|
||||||
system_instruction=system_prompt,
|
system_instruction=system_prompt,
|
||||||
thinking_config=types.ThinkingConfig(thinking_budget=0),
|
thinking_config=types.ThinkingConfig(thinking_budget=0),
|
||||||
response_mime_type="application/json",
|
response_mime_type="application/json",
|
||||||
response_schema=list[OutputMessage],
|
response_schema=list[OutputMessage],
|
||||||
safety_settings=SAFETY_SETTINGS,
|
# safety_settings=SAFETY_SETTINGS,
|
||||||
|
tools=RESPOND_TOOLS,
|
||||||
),
|
),
|
||||||
history=history,
|
|
||||||
)
|
)
|
||||||
|
return content.parsed
|
||||||
async def send_messages(self, messages: list[InputMessage]) -> list[OutputMessage]:
|
|
||||||
data = json.dumps([msg.model_dump() for msg in messages], ensure_ascii=True)
|
|
||||||
response = await self.chat.send_message(data)
|
|
||||||
return response.parsed
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class TTSAgent:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
safety_settings=SAFETY_SETTINGS,
|
# safety_settings=SAFETY_SETTINGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def generate(self, text: str):
|
async def generate(self, text: str):
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ from google.genai import types
|
|||||||
from .structures import OutputMessage
|
from .structures import OutputMessage
|
||||||
|
|
||||||
SAFETY_SETTINGS = [
|
SAFETY_SETTINGS = [
|
||||||
types.SafetySetting(category=category, threshold=types.HarmBlockThreshold.OFF)
|
types.SafetySetting(
|
||||||
|
category=category.value, threshold=types.HarmBlockThreshold.OFF.value
|
||||||
|
)
|
||||||
for category in types.HarmCategory
|
for category in types.HarmCategory
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -18,7 +20,7 @@ def generate_review_config(prompt: str) -> types.GenerateContentConfig:
|
|||||||
thinking_config=types.ThinkingConfig(thinking_budget=0),
|
thinking_config=types.ThinkingConfig(thinking_budget=0),
|
||||||
response_mime_type="application/json",
|
response_mime_type="application/json",
|
||||||
response_schema=list[int],
|
response_schema=list[int],
|
||||||
safety_settings=SAFETY_SETTINGS,
|
# safety_settings=SAFETY_SETTINGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
17
src/bot/modules/solaris/prompts/__init__.py
Normal file
17
src/bot/modules/solaris/prompts/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
STATIC_PATH = Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
|
def load_prompt(prompt_path: str, **kwargs) -> str:
|
||||||
|
full_path = STATIC_PATH / prompt_path
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(full_path, "r", encoding="utf-8") as file:
|
||||||
|
template = file.read()
|
||||||
|
|
||||||
|
return template.format(**kwargs)
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise FileNotFoundError(f"Prompt template not found: {full_path}")
|
||||||
|
except KeyError as e:
|
||||||
|
raise KeyError(f"Missing placeholder in template: {e}")
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
Ты — Солярис, дружелюбный и многофункциональный AI-ассистент в Telegram.
|
||||||
|
Твоя главная задача — помогать пользователям, отвечая на их вопросы,
|
||||||
|
поддерживая беседу и выполняя задачи с помощью доступных инструментов.
|
||||||
|
Ты можешь запоминать контекст нашего диалога, чтобы общение было более естественным и продуктивным.
|
||||||
|
Обращайся к пользователям вежливо и старайся быть максимально полезным.
|
||||||
|
Когда тебя просят использовать инструмент для выполнения какой-либо задачи,
|
||||||
|
всегда подтверждай вызов инструмента и результат его работы.
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
from beanie.odm.operators.update.general import Set
|
||||||
from google import genai
|
from google import genai
|
||||||
from google.genai import chats, types
|
from google.genai import chats, types
|
||||||
|
|
||||||
from utils.config import dconfig
|
from utils.config import dconfig
|
||||||
|
from utils.db.models.session import RespondSession
|
||||||
from utils.logging import console
|
from utils.logging import console
|
||||||
|
|
||||||
from ..agents.respond import RespondAgent
|
from ..agents.respond import RespondAgent
|
||||||
from ..constants import SAFETY_SETTINGS
|
from ..constants import SAFETY_SETTINGS
|
||||||
|
from ..prompts import load_prompt
|
||||||
from ..structures import InputMessage, OutputMessage
|
from ..structures import InputMessage, OutputMessage
|
||||||
|
|
||||||
|
|
||||||
@@ -15,8 +18,38 @@ class RespondService:
|
|||||||
def __init__(self, client: genai.client.AsyncClient, chat_id: int) -> None:
|
def __init__(self, client: genai.client.AsyncClient, chat_id: int) -> None:
|
||||||
self.agent = RespondAgent(client)
|
self.agent = RespondAgent(client)
|
||||||
self.chat_id = chat_id
|
self.chat_id = chat_id
|
||||||
|
self.session: RespondSession | None = None
|
||||||
|
|
||||||
async def spawn_agent(self):
|
async def _get_or_create_session(self) -> RespondSession:
|
||||||
console.print(self.chat_id)
|
session = await RespondSession.get_by_chat_id(chat_id=self.chat_id)
|
||||||
|
if not session:
|
||||||
|
session = await RespondSession.create_empty(
|
||||||
|
chat_id=self.chat_id,
|
||||||
|
system_prompt=load_prompt("default_system_prompt.txt"),
|
||||||
|
)
|
||||||
|
|
||||||
await self.agent.load_chat(history=[], system_prompt="nya nya")
|
self.session = session
|
||||||
|
return session
|
||||||
|
|
||||||
|
async def process_message(self, message: InputMessage) -> list[OutputMessage]:
|
||||||
|
session = await self._get_or_create_session()
|
||||||
|
|
||||||
|
session.history.append(
|
||||||
|
types.Content(role="user", parts=[types.Part(text=message.text)])
|
||||||
|
)
|
||||||
|
|
||||||
|
response_messages = await self.agent.generate_response(
|
||||||
|
system_prompt=session.system_prompt, history=session.history
|
||||||
|
)
|
||||||
|
|
||||||
|
if response_messages:
|
||||||
|
model_response_content = response_messages[0].text
|
||||||
|
session.history.append(
|
||||||
|
types.Content(
|
||||||
|
role="model", parts=[types.Part(text=model_response_content)]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
await session.update_history(history=session.history)
|
||||||
|
|
||||||
|
return response_messages
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
class InputMessage(BaseModel):
|
class InputMessage(BaseModel):
|
||||||
time: str
|
time: datetime.datetime
|
||||||
message_id: int
|
message_id: int
|
||||||
text: str
|
text: str
|
||||||
user_id: int
|
user_id: int
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .test import test_tool
|
||||||
|
|
||||||
|
RESPOND_TOOLS = [test_tool]
|
||||||
|
|||||||
14
src/bot/modules/solaris/tools/test.py
Normal file
14
src/bot/modules/solaris/tools/test.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from utils.logging import console
|
||||||
|
|
||||||
|
|
||||||
|
async def test_tool(content: str):
|
||||||
|
"""Prints the content to the developer console.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: Anything you want to print.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A status if content was printed.
|
||||||
|
"""
|
||||||
|
console.print(content)
|
||||||
|
return "ok"
|
||||||
@@ -23,5 +23,4 @@ class SolarisProvider(Provider):
|
|||||||
) -> AsyncIterable[RespondService]:
|
) -> AsyncIterable[RespondService]:
|
||||||
chat: aiogram.types.Chat = middleware_data["event_chat"]
|
chat: aiogram.types.Chat = middleware_data["event_chat"]
|
||||||
service = RespondService(client=client, chat_id=chat.id)
|
service = RespondService(client=client, chat_id=chat.id)
|
||||||
await service.spawn_agent()
|
|
||||||
yield service
|
yield service
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ client = AsyncIOMotorClient(env.db.connection_url)
|
|||||||
async def init_db():
|
async def init_db():
|
||||||
from .models import (
|
from .models import (
|
||||||
DynamicConfig,
|
DynamicConfig,
|
||||||
|
RespondSession,
|
||||||
|
ReviewSession,
|
||||||
)
|
)
|
||||||
|
|
||||||
await init_beanie(
|
await init_beanie(
|
||||||
database=client[env.db.db_name],
|
database=client[env.db.db_name],
|
||||||
document_models=[
|
document_models=[
|
||||||
DynamicConfig,
|
DynamicConfig,
|
||||||
|
RespondSession,
|
||||||
|
ReviewSession,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
from .config import DynamicConfig
|
from .config import DynamicConfig
|
||||||
|
from .session import RespondSession, ReviewSession
|
||||||
|
|||||||
@@ -11,11 +11,24 @@ class SessionBase(BaseModel):
|
|||||||
history: List[Content] = Field(default_factory=list)
|
history: List[Content] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
class ReviewSession(SessionBase, Document):
|
class __CommonSessionRepository(SessionBase, Document):
|
||||||
|
@classmethod
|
||||||
|
async def get_by_chat_id(cls, chat_id: int):
|
||||||
|
return await cls.find_one(cls.chat_id == chat_id)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
async def create_empty(cls, chat_id: int, system_prompt: str):
|
||||||
|
return await cls(chat_id=chat_id, system_prompt=system_prompt).insert()
|
||||||
|
|
||||||
|
async def update_history(self, history: List[Content]):
|
||||||
|
await self.set({self.history: history})
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewSession(__CommonSessionRepository):
|
||||||
class Settings:
|
class Settings:
|
||||||
name = "review_sessions"
|
name = "review_sessions"
|
||||||
|
|
||||||
|
|
||||||
class RespondSession(SessionBase, Document):
|
class RespondSession(__CommonSessionRepository):
|
||||||
class Settings:
|
class Settings:
|
||||||
name = "respond_sessions"
|
name = "respond_sessions"
|
||||||
|
|||||||
Reference in New Issue
Block a user