py-aidol 0.4.0__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {py_aidol-0.4.0 → py_aidol-0.5.1}/PKG-INFO +4 -1
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/api/__init__.py +3 -0
- py_aidol-0.5.1/aidol/api/chatroom.py +325 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/api/companion.py +37 -50
- py_aidol-0.5.1/aidol/context/__init__.py +26 -0
- py_aidol-0.5.1/aidol/context/builder.py +376 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/factories.py +8 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/models/__init__.py +2 -1
- py_aidol-0.5.1/aidol/models/chatroom.py +48 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/protocols.py +64 -0
- py_aidol-0.5.1/aidol/providers/__init__.py +9 -0
- py_aidol-0.5.1/aidol/providers/llm/__init__.py +15 -0
- py_aidol-0.5.1/aidol/providers/llm/base.py +147 -0
- py_aidol-0.5.1/aidol/providers/llm/openai.py +101 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/repositories/__init__.py +2 -0
- py_aidol-0.5.1/aidol/repositories/chatroom.py +142 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/schemas/__init__.py +35 -0
- py_aidol-0.5.1/aidol/schemas/chatroom.py +147 -0
- py_aidol-0.5.1/aidol/schemas/model_settings.py +35 -0
- py_aidol-0.5.1/aidol/schemas/persona.py +20 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/services/__init__.py +2 -0
- py_aidol-0.5.1/aidol/services/response_generation_service.py +63 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/settings.py +9 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/pyproject.toml +4 -1
- {py_aidol-0.4.0 → py_aidol-0.5.1}/README.md +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/__init__.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/api/aidol.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/api/common.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/api/lead.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/models/aidol.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/models/aidol_lead.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/models/companion.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/py.typed +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/repositories/aidol.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/repositories/aidol_lead.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/repositories/companion.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/schemas/aidol.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/schemas/aidol_lead.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/schemas/companion.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/services/companion_service.py +0 -0
- {py_aidol-0.4.0 → py_aidol-0.5.1}/aidol/services/image_generation_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: py-aidol
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Create and chat with your own AI idol group
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Keywords: kpop,idol,aidol,ai-companion,chatbot,image-generation
|
|
@@ -16,6 +16,9 @@ Requires-Dist: aioia-core (>=2.2.0,<3.0.0)
|
|
|
16
16
|
Requires-Dist: fastapi (>=0.115.12,<0.116.0)
|
|
17
17
|
Requires-Dist: google-genai (>=1.60.0,<2.0.0)
|
|
18
18
|
Requires-Dist: httpx (>=0.28.1,<0.29.0)
|
|
19
|
+
Requires-Dist: langchain-core (>=0.3.0)
|
|
20
|
+
Requires-Dist: langchain-openai (>=0.3.0)
|
|
21
|
+
Requires-Dist: litellm (>=1.60.0)
|
|
19
22
|
Requires-Dist: openai (>=1.0.0)
|
|
20
23
|
Requires-Dist: pillow (>=10.0.0,<11.0.0)
|
|
21
24
|
Requires-Dist: psycopg2-binary (>=2.9.9,<3.0.0)
|
|
@@ -3,11 +3,14 @@ AIdol API routers
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from aidol.api.aidol import AIdolRouter, create_aidol_router
|
|
6
|
+
from aidol.api.chatroom import ChatroomRouter, create_chatroom_router
|
|
6
7
|
from aidol.api.companion import CompanionRouter, create_companion_router
|
|
7
8
|
|
|
8
9
|
__all__ = [
|
|
9
10
|
"AIdolRouter",
|
|
11
|
+
"ChatroomRouter",
|
|
10
12
|
"CompanionRouter",
|
|
11
13
|
"create_aidol_router",
|
|
14
|
+
"create_chatroom_router",
|
|
12
15
|
"create_companion_router",
|
|
13
16
|
]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chatroom API router
|
|
3
|
+
|
|
4
|
+
Implements BaseCrudRouter pattern for consistency with aioia-core patterns.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from aioia_core.auth import UserInfoProvider
|
|
8
|
+
from aioia_core.errors import ErrorResponse
|
|
9
|
+
from aioia_core.fastapi import BaseCrudRouter
|
|
10
|
+
from aioia_core.settings import JWTSettings, OpenAIAPISettings
|
|
11
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
12
|
+
from humps import camelize
|
|
13
|
+
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
15
|
+
from sqlalchemy.orm import Session, sessionmaker
|
|
16
|
+
|
|
17
|
+
from aidol.context import MessageContextBuilder
|
|
18
|
+
from aidol.protocols import (
|
|
19
|
+
ChatroomRepositoryFactoryProtocol,
|
|
20
|
+
ChatroomRepositoryProtocol,
|
|
21
|
+
CompanionRepositoryFactoryProtocol,
|
|
22
|
+
)
|
|
23
|
+
from aidol.providers.llm import OpenAILLMProvider
|
|
24
|
+
from aidol.schemas import (
|
|
25
|
+
Chatroom,
|
|
26
|
+
ChatroomCreate,
|
|
27
|
+
ChatroomUpdate,
|
|
28
|
+
CompanionMessageCreate,
|
|
29
|
+
Message,
|
|
30
|
+
MessageCreate,
|
|
31
|
+
ModelSettings,
|
|
32
|
+
Persona,
|
|
33
|
+
SenderType,
|
|
34
|
+
)
|
|
35
|
+
from aidol.services import ResponseGenerationService
|
|
36
|
+
from aidol.settings import Settings
|
|
37
|
+
|
|
38
|
+
# Maximum number of messages to fetch for conversation history
|
|
39
|
+
DEFAULT_HISTORY_LIMIT = 200
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ChatroomSingleItemResponse(BaseModel):
|
|
43
|
+
"""Single item response for chatroom."""
|
|
44
|
+
|
|
45
|
+
data: Chatroom
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GenerateResponse(BaseModel):
|
|
49
|
+
"""Response schema for generate_response endpoint."""
|
|
50
|
+
|
|
51
|
+
model_config = ConfigDict(populate_by_name=True, alias_generator=camelize)
|
|
52
|
+
|
|
53
|
+
message_id: str = Field(..., description="Message ID")
|
|
54
|
+
content: str = Field(..., description="AI response content")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _to_langchain_messages(messages: list[Message]) -> list[BaseMessage]:
|
|
58
|
+
"""
|
|
59
|
+
Convert Message schemas to LangChain BaseMessage format.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
messages: List of Message from repository.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
List of LangChain BaseMessage (HumanMessage or AIMessage).
|
|
66
|
+
"""
|
|
67
|
+
result: list[BaseMessage] = []
|
|
68
|
+
for msg in messages:
|
|
69
|
+
if msg.sender_type == SenderType.USER:
|
|
70
|
+
result.append(HumanMessage(content=msg.content))
|
|
71
|
+
else:
|
|
72
|
+
result.append(AIMessage(content=msg.content))
|
|
73
|
+
return result
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ChatroomRouter(
|
|
77
|
+
BaseCrudRouter[Chatroom, ChatroomCreate, ChatroomUpdate, ChatroomRepositoryProtocol]
|
|
78
|
+
):
|
|
79
|
+
"""
|
|
80
|
+
Chatroom router with custom message endpoints.
|
|
81
|
+
|
|
82
|
+
Extends BaseCrudRouter for consistent architecture pattern.
|
|
83
|
+
Disables default CRUD endpoints and provides custom message endpoints.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
model_settings: Settings,
|
|
89
|
+
openai_settings: OpenAIAPISettings,
|
|
90
|
+
companion_repository_factory: CompanionRepositoryFactoryProtocol,
|
|
91
|
+
**kwargs,
|
|
92
|
+
):
|
|
93
|
+
super().__init__(**kwargs)
|
|
94
|
+
self.model_settings = model_settings
|
|
95
|
+
self.openai_settings = openai_settings
|
|
96
|
+
self.companion_repository_factory = companion_repository_factory
|
|
97
|
+
|
|
98
|
+
def _register_routes(self) -> None:
|
|
99
|
+
"""Register routes (fancall pattern: public CRUD + message endpoints)"""
|
|
100
|
+
# Chatroom CRUD (public, no auth)
|
|
101
|
+
self._register_public_create_route()
|
|
102
|
+
self._register_public_get_route()
|
|
103
|
+
|
|
104
|
+
# Message endpoints (public)
|
|
105
|
+
self._register_get_messages_route()
|
|
106
|
+
self._register_send_message_route()
|
|
107
|
+
self._register_generate_response_route()
|
|
108
|
+
|
|
109
|
+
def _register_public_create_route(self) -> None:
|
|
110
|
+
"""POST /{resource_name} - Create a chatroom (public, fancall pattern)"""
|
|
111
|
+
|
|
112
|
+
@self.router.post(
|
|
113
|
+
f"/{self.resource_name}",
|
|
114
|
+
response_model=ChatroomSingleItemResponse,
|
|
115
|
+
status_code=status.HTTP_201_CREATED,
|
|
116
|
+
summary="Create chatroom",
|
|
117
|
+
description="Create a new chatroom (public endpoint)",
|
|
118
|
+
)
|
|
119
|
+
async def create_chatroom(
|
|
120
|
+
request: ChatroomCreate,
|
|
121
|
+
repository: ChatroomRepositoryProtocol = Depends(self.get_repository_dep),
|
|
122
|
+
):
|
|
123
|
+
"""Create a new chatroom."""
|
|
124
|
+
created = repository.create(request)
|
|
125
|
+
return ChatroomSingleItemResponse(data=created)
|
|
126
|
+
|
|
127
|
+
def _register_public_get_route(self) -> None:
|
|
128
|
+
"""GET /{resource_name}/{id} - Get a chatroom (public, fancall pattern)"""
|
|
129
|
+
|
|
130
|
+
@self.router.get(
|
|
131
|
+
f"/{self.resource_name}/{{item_id}}",
|
|
132
|
+
response_model=ChatroomSingleItemResponse,
|
|
133
|
+
status_code=status.HTTP_200_OK,
|
|
134
|
+
summary="Get chatroom",
|
|
135
|
+
description="Get chatroom by ID (public endpoint)",
|
|
136
|
+
responses={
|
|
137
|
+
404: {"model": ErrorResponse, "description": "Chatroom not found"},
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
async def get_chatroom(
|
|
141
|
+
item_id: str,
|
|
142
|
+
repository: ChatroomRepositoryProtocol = Depends(self.get_repository_dep),
|
|
143
|
+
):
|
|
144
|
+
"""Get chatroom by ID."""
|
|
145
|
+
chatroom = self._get_item_or_404(repository, item_id)
|
|
146
|
+
return ChatroomSingleItemResponse(data=chatroom)
|
|
147
|
+
|
|
148
|
+
def _register_get_messages_route(self) -> None:
|
|
149
|
+
"""GET /{resource_name}/{id}/messages - Get messages from a chatroom"""
|
|
150
|
+
|
|
151
|
+
@self.router.get(
|
|
152
|
+
f"/{self.resource_name}/{{item_id}}/messages",
|
|
153
|
+
response_model=list[Message],
|
|
154
|
+
status_code=status.HTTP_200_OK,
|
|
155
|
+
summary="Get messages",
|
|
156
|
+
description="Get messages from a chatroom",
|
|
157
|
+
)
|
|
158
|
+
async def get_messages(
|
|
159
|
+
item_id: str,
|
|
160
|
+
limit: int = 100,
|
|
161
|
+
offset: int = 0,
|
|
162
|
+
repository: ChatroomRepositoryProtocol = Depends(self.get_repository_dep),
|
|
163
|
+
):
|
|
164
|
+
"""Get messages from a chatroom."""
|
|
165
|
+
return repository.get_messages_by_chatroom_id(
|
|
166
|
+
chatroom_id=item_id,
|
|
167
|
+
limit=limit,
|
|
168
|
+
offset=offset,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _register_send_message_route(self) -> None:
|
|
172
|
+
"""POST /{resource_name}/{id}/messages - Send a message to a chatroom"""
|
|
173
|
+
|
|
174
|
+
@self.router.post(
|
|
175
|
+
f"/{self.resource_name}/{{item_id}}/messages",
|
|
176
|
+
response_model=Message,
|
|
177
|
+
status_code=status.HTTP_201_CREATED,
|
|
178
|
+
summary="Send message",
|
|
179
|
+
description="Send a message to a chatroom",
|
|
180
|
+
)
|
|
181
|
+
async def send_message(
|
|
182
|
+
item_id: str,
|
|
183
|
+
request: MessageCreate,
|
|
184
|
+
repository: ChatroomRepositoryProtocol = Depends(self.get_repository_dep),
|
|
185
|
+
):
|
|
186
|
+
"""Send a message to a chatroom."""
|
|
187
|
+
# Verify chatroom exists
|
|
188
|
+
self._get_item_or_404(repository, item_id)
|
|
189
|
+
|
|
190
|
+
# Enforce sender_type as USER to prevent spoofing
|
|
191
|
+
request.sender_type = SenderType.USER
|
|
192
|
+
|
|
193
|
+
# Pass MessageCreate directly (aioia-core pattern)
|
|
194
|
+
return repository.add_message_to_chatroom(
|
|
195
|
+
chatroom_id=item_id,
|
|
196
|
+
message=request,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _register_generate_response_route(self) -> None:
|
|
200
|
+
"""POST /{resource_name}/{id}/companions/{companion_id}/response - Generate AI response"""
|
|
201
|
+
|
|
202
|
+
@self.router.post(
|
|
203
|
+
f"/{self.resource_name}/{{item_id}}/companions/{{companion_id}}/response",
|
|
204
|
+
response_model=GenerateResponse,
|
|
205
|
+
status_code=status.HTTP_201_CREATED,
|
|
206
|
+
summary="Generate AI response",
|
|
207
|
+
description="Generate AI response for a chatroom with a specific companion",
|
|
208
|
+
)
|
|
209
|
+
async def generate_response(
|
|
210
|
+
item_id: str,
|
|
211
|
+
companion_id: str,
|
|
212
|
+
db_session: Session = Depends(self.get_db_dep),
|
|
213
|
+
repository: ChatroomRepositoryProtocol = Depends(self.get_repository_dep),
|
|
214
|
+
):
|
|
215
|
+
"""Generate AI response for a chatroom."""
|
|
216
|
+
# Verify chatroom exists
|
|
217
|
+
self._get_item_or_404(repository, item_id)
|
|
218
|
+
|
|
219
|
+
# Get companion repository with same db session (Buppy pattern)
|
|
220
|
+
companion_repository = self.companion_repository_factory.create_repository(
|
|
221
|
+
db_session
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Get companion by ID
|
|
225
|
+
companion = companion_repository.get_by_id(companion_id)
|
|
226
|
+
if companion is None:
|
|
227
|
+
raise HTTPException(
|
|
228
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
229
|
+
detail=f"Companion with id {companion_id} not found",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Get conversation history
|
|
233
|
+
messages = repository.get_messages_by_chatroom_id(
|
|
234
|
+
chatroom_id=item_id,
|
|
235
|
+
limit=DEFAULT_HISTORY_LIMIT,
|
|
236
|
+
offset=0,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Convert to LangChain BaseMessage format
|
|
240
|
+
# Reverse: DB returns newest-first, LLM needs chronological order
|
|
241
|
+
langchain_messages = _to_langchain_messages(list(reversed(messages)))
|
|
242
|
+
|
|
243
|
+
# Create persona from companion (KST fixed for MVP)
|
|
244
|
+
persona = Persona(
|
|
245
|
+
name=companion.name,
|
|
246
|
+
system_prompt=companion.system_prompt,
|
|
247
|
+
timezone_name="Asia/Seoul",
|
|
248
|
+
)
|
|
249
|
+
provider = OpenAILLMProvider(settings=self.openai_settings)
|
|
250
|
+
model_settings = ModelSettings(chat_model=self.model_settings.openai_model)
|
|
251
|
+
|
|
252
|
+
# Generate text response using ResponseGenerationService
|
|
253
|
+
context = (
|
|
254
|
+
MessageContextBuilder(provider, persona)
|
|
255
|
+
.with_persona()
|
|
256
|
+
.with_real_time_context()
|
|
257
|
+
.with_current_conversation(langchain_messages)
|
|
258
|
+
.build()
|
|
259
|
+
)
|
|
260
|
+
service = ResponseGenerationService(provider, model_settings)
|
|
261
|
+
response_text = service.generate_response(context)
|
|
262
|
+
|
|
263
|
+
# Save companion message (repository handles commit)
|
|
264
|
+
# Use CompanionMessageCreate (no id) - aioia-core pattern
|
|
265
|
+
companion_message = repository.add_message_to_chatroom(
|
|
266
|
+
chatroom_id=item_id,
|
|
267
|
+
message=CompanionMessageCreate(
|
|
268
|
+
content=response_text,
|
|
269
|
+
companion_id=companion_id,
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return GenerateResponse(
|
|
274
|
+
message_id=companion_message.id,
|
|
275
|
+
content=response_text,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def create_chatroom_router(
|
|
280
|
+
openai_settings: OpenAIAPISettings,
|
|
281
|
+
model_settings: Settings,
|
|
282
|
+
companion_repository_factory: CompanionRepositoryFactoryProtocol,
|
|
283
|
+
db_session_factory: sessionmaker,
|
|
284
|
+
repository_factory: ChatroomRepositoryFactoryProtocol,
|
|
285
|
+
jwt_settings: JWTSettings | None = None,
|
|
286
|
+
user_info_provider: UserInfoProvider | None = None,
|
|
287
|
+
resource_name: str = "chatrooms",
|
|
288
|
+
tags: list[str] | None = None,
|
|
289
|
+
) -> APIRouter:
|
|
290
|
+
"""
|
|
291
|
+
Create chatroom router with dependency injection.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
openai_settings: OpenAI API settings for LLM provider
|
|
295
|
+
model_settings: Environment settings for aidol
|
|
296
|
+
companion_repository_factory: Factory for CompanionRepository.
|
|
297
|
+
For standalone: Use aidol.factories.CompanionRepositoryFactory.
|
|
298
|
+
For platform integration: Use CompanionRepositoryFactoryAdapter.
|
|
299
|
+
db_session_factory: Database session factory
|
|
300
|
+
repository_factory: Factory implementing ChatroomRepositoryFactoryProtocol.
|
|
301
|
+
For standalone: Use aidol.factories.ChatroomRepositoryFactory.
|
|
302
|
+
For platform integration: Use ChatroomRepositoryFactoryAdapter.
|
|
303
|
+
jwt_settings: Optional JWT settings for authentication
|
|
304
|
+
user_info_provider: Optional user info provider
|
|
305
|
+
resource_name: Resource name for routes (default: "chatrooms")
|
|
306
|
+
tags: Optional OpenAPI tags
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
FastAPI APIRouter instance
|
|
310
|
+
"""
|
|
311
|
+
router = ChatroomRouter(
|
|
312
|
+
model_settings=model_settings,
|
|
313
|
+
openai_settings=openai_settings,
|
|
314
|
+
companion_repository_factory=companion_repository_factory,
|
|
315
|
+
model_class=Chatroom,
|
|
316
|
+
create_schema=ChatroomCreate,
|
|
317
|
+
update_schema=ChatroomUpdate,
|
|
318
|
+
db_session_factory=db_session_factory,
|
|
319
|
+
repository_factory=repository_factory,
|
|
320
|
+
user_info_provider=user_info_provider,
|
|
321
|
+
jwt_secret_key=jwt_settings.secret_key if jwt_settings else None,
|
|
322
|
+
resource_name=resource_name,
|
|
323
|
+
tags=tags or ["Aidol"],
|
|
324
|
+
)
|
|
325
|
+
return router.get_router()
|
|
@@ -20,17 +20,17 @@ from aidol.protocols import (
|
|
|
20
20
|
CompanionRepositoryProtocol,
|
|
21
21
|
ImageStorageProtocol,
|
|
22
22
|
)
|
|
23
|
-
from aidol.schemas import
|
|
24
|
-
Companion,
|
|
25
|
-
CompanionCreate,
|
|
26
|
-
CompanionPublic,
|
|
27
|
-
CompanionUpdate,
|
|
28
|
-
Gender,
|
|
29
|
-
)
|
|
23
|
+
from aidol.schemas import Companion, CompanionCreate, CompanionPublic, CompanionUpdate
|
|
30
24
|
from aidol.services.companion_service import to_companion_public
|
|
31
25
|
from aidol.settings import GoogleGenAISettings
|
|
32
26
|
|
|
33
27
|
|
|
28
|
+
class CompanionSingleItemResponse(BaseModel):
|
|
29
|
+
"""Single item response for Companion (public)."""
|
|
30
|
+
|
|
31
|
+
data: CompanionPublic
|
|
32
|
+
|
|
33
|
+
|
|
34
34
|
class CompanionPaginatedResponse(BaseModel):
|
|
35
35
|
"""Paginated response for Companion (public)."""
|
|
36
36
|
|
|
@@ -84,45 +84,32 @@ class CompanionRouter(
|
|
|
84
84
|
response_model=CompanionPaginatedResponse,
|
|
85
85
|
status_code=status.HTTP_200_OK,
|
|
86
86
|
summary="List Companions",
|
|
87
|
-
description="List all Companions with
|
|
87
|
+
description="List all Companions with pagination, sorting, and filtering",
|
|
88
88
|
)
|
|
89
89
|
async def list_companions(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
current: int = Query(1, ge=1, description="Current page number"),
|
|
91
|
+
page_size: int = Query(
|
|
92
|
+
10, alias="pageSize", ge=1, le=100, description="Items per page"
|
|
93
|
+
),
|
|
94
|
+
sort_param: str | None = Query(
|
|
95
|
+
None,
|
|
96
|
+
alias="sort",
|
|
97
|
+
description='Sorting criteria in JSON format. Example: [["createdAt","desc"]]',
|
|
98
|
+
),
|
|
99
|
+
filters_param: str | None = Query(
|
|
100
|
+
None,
|
|
101
|
+
alias="filters",
|
|
102
|
+
description="Filter conditions (JSON format)",
|
|
93
103
|
),
|
|
94
|
-
aidol_id: str | None = Query(None, description="Filter by AIdol Group ID"),
|
|
95
104
|
repository: CompanionRepositoryProtocol = Depends(self.get_repository_dep),
|
|
96
105
|
):
|
|
97
|
-
"""List Companions with
|
|
98
|
-
filter_list
|
|
99
|
-
|
|
100
|
-
# Add filters only if provided
|
|
101
|
-
if gender is not None:
|
|
102
|
-
filter_list.append(
|
|
103
|
-
{"field": "gender", "operator": "eq", "value": gender.value}
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
# Filter by aidol_id if provided
|
|
107
|
-
if aidol_id is not None:
|
|
108
|
-
filter_list.append(
|
|
109
|
-
{"field": "aidol_id", "operator": "eq", "value": aidol_id}
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
# isCast is derived from aidol_id presence
|
|
113
|
-
# isCast=true → aidol_id is not null (belongs to a group)
|
|
114
|
-
# isCast=false → aidol_id is null (not in a group)
|
|
115
|
-
if is_cast is True:
|
|
116
|
-
filter_list.append(
|
|
117
|
-
{"field": "aidol_id", "operator": "ne", "value": None}
|
|
118
|
-
)
|
|
119
|
-
elif is_cast is False:
|
|
120
|
-
filter_list.append(
|
|
121
|
-
{"field": "aidol_id", "operator": "eq", "value": None}
|
|
122
|
-
)
|
|
123
|
-
|
|
106
|
+
"""List Companions with pagination, sorting, and filtering."""
|
|
107
|
+
sort_list, filter_list = self._parse_query_params(sort_param, filters_param)
|
|
124
108
|
items, total = repository.get_all(
|
|
125
|
-
|
|
109
|
+
current=current,
|
|
110
|
+
page_size=page_size,
|
|
111
|
+
sort=sort_list,
|
|
112
|
+
filters=filter_list,
|
|
126
113
|
)
|
|
127
114
|
# Convert to Public schema (exclude system_prompt)
|
|
128
115
|
public_items = [to_companion_public(c) for c in items]
|
|
@@ -133,7 +120,7 @@ class CompanionRouter(
|
|
|
133
120
|
|
|
134
121
|
@self.router.post(
|
|
135
122
|
f"/{self.resource_name}",
|
|
136
|
-
response_model=
|
|
123
|
+
response_model=CompanionSingleItemResponse,
|
|
137
124
|
status_code=status.HTTP_201_CREATED,
|
|
138
125
|
summary="Create Companion",
|
|
139
126
|
description="Create a new Companion. Returns the created companion data.",
|
|
@@ -149,17 +136,17 @@ class CompanionRouter(
|
|
|
149
136
|
|
|
150
137
|
created = repository.create(sanitized_request)
|
|
151
138
|
# Return created companion as public schema
|
|
152
|
-
return to_companion_public(created)
|
|
139
|
+
return CompanionSingleItemResponse(data=to_companion_public(created))
|
|
153
140
|
|
|
154
141
|
def _register_public_get_route(self) -> None:
|
|
155
142
|
"""GET /{resource_name}/{id} - Get a Companion (public)"""
|
|
156
143
|
|
|
157
144
|
@self.router.get(
|
|
158
145
|
f"/{self.resource_name}/{{item_id}}",
|
|
159
|
-
response_model=
|
|
146
|
+
response_model=CompanionSingleItemResponse,
|
|
160
147
|
status_code=status.HTTP_200_OK,
|
|
161
148
|
summary="Get Companion",
|
|
162
|
-
description="Get Companion by ID (public endpoint).
|
|
149
|
+
description="Get Companion by ID (public endpoint).",
|
|
163
150
|
responses={
|
|
164
151
|
404: {"model": ErrorResponse, "description": "Companion not found"},
|
|
165
152
|
},
|
|
@@ -171,17 +158,17 @@ class CompanionRouter(
|
|
|
171
158
|
"""Get Companion by ID."""
|
|
172
159
|
companion = self._get_item_or_404(repository, item_id)
|
|
173
160
|
# Return companion as public schema
|
|
174
|
-
return to_companion_public(companion)
|
|
161
|
+
return CompanionSingleItemResponse(data=to_companion_public(companion))
|
|
175
162
|
|
|
176
163
|
def _register_public_update_route(self) -> None:
|
|
177
164
|
"""PATCH /{resource_name}/{id} - Update Companion (public)"""
|
|
178
165
|
|
|
179
166
|
@self.router.patch(
|
|
180
167
|
f"/{self.resource_name}/{{item_id}}",
|
|
181
|
-
response_model=
|
|
168
|
+
response_model=CompanionSingleItemResponse,
|
|
182
169
|
status_code=status.HTTP_200_OK,
|
|
183
170
|
summary="Update Companion",
|
|
184
|
-
description="Update Companion by ID (public endpoint).
|
|
171
|
+
description="Update Companion by ID (public endpoint).",
|
|
185
172
|
responses={
|
|
186
173
|
404: {"model": ErrorResponse, "description": "Companion not found"},
|
|
187
174
|
},
|
|
@@ -206,14 +193,14 @@ class CompanionRouter(
|
|
|
206
193
|
)
|
|
207
194
|
|
|
208
195
|
# Return updated companion as public schema
|
|
209
|
-
return to_companion_public(updated)
|
|
196
|
+
return CompanionSingleItemResponse(data=to_companion_public(updated))
|
|
210
197
|
|
|
211
198
|
def _register_public_delete_route(self) -> None:
|
|
212
199
|
"""DELETE /{resource_name}/{id} - Remove Companion from Group (public)"""
|
|
213
200
|
|
|
214
201
|
@self.router.delete(
|
|
215
202
|
f"/{self.resource_name}/{{item_id}}",
|
|
216
|
-
response_model=
|
|
203
|
+
response_model=CompanionSingleItemResponse,
|
|
217
204
|
status_code=status.HTTP_200_OK,
|
|
218
205
|
summary="Remove Companion from Group",
|
|
219
206
|
description="Remove Companion from AIdol group (unassign aidol_id). Does not delete the record.",
|
|
@@ -241,7 +228,7 @@ class CompanionRouter(
|
|
|
241
228
|
)
|
|
242
229
|
|
|
243
230
|
# Return updated companion as public schema
|
|
244
|
-
return to_companion_public(updated)
|
|
231
|
+
return CompanionSingleItemResponse(data=to_companion_public(updated))
|
|
245
232
|
|
|
246
233
|
|
|
247
234
|
def create_companion_router(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Context engineering for AIdol.
|
|
2
|
+
|
|
3
|
+
This module provides context composition utilities for LLM calls.
|
|
4
|
+
Integrators can extend these base implementations for platform-specific features.
|
|
5
|
+
|
|
6
|
+
Components:
|
|
7
|
+
- MessageContextBuilder: Builder pattern for assembling LLM context
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from aidol.context.builder import (
|
|
11
|
+
MessageContextBuilder,
|
|
12
|
+
deduplicate_consecutive_same_role_messages,
|
|
13
|
+
ensure_first_user_message,
|
|
14
|
+
format_datetime_korean,
|
|
15
|
+
format_utc_offset,
|
|
16
|
+
verify_system_messages_at_front,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"MessageContextBuilder",
|
|
21
|
+
"deduplicate_consecutive_same_role_messages",
|
|
22
|
+
"ensure_first_user_message",
|
|
23
|
+
"format_datetime_korean",
|
|
24
|
+
"format_utc_offset",
|
|
25
|
+
"verify_system_messages_at_front",
|
|
26
|
+
]
|