py-aidol 0.3.0__tar.gz → 0.5.0__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.3.0 → py_aidol-0.5.0}/PKG-INFO +26 -6
- {py_aidol-0.3.0 → py_aidol-0.5.0}/README.md +22 -5
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/api/__init__.py +3 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/api/aidol.py +7 -6
- py_aidol-0.5.0/aidol/api/chatroom.py +325 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/api/common.py +4 -3
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/api/companion.py +23 -16
- py_aidol-0.5.0/aidol/context/__init__.py +26 -0
- py_aidol-0.5.0/aidol/context/builder.py +376 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/factories.py +8 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/models/__init__.py +2 -1
- py_aidol-0.5.0/aidol/models/chatroom.py +48 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/protocols.py +64 -0
- py_aidol-0.5.0/aidol/providers/__init__.py +9 -0
- py_aidol-0.5.0/aidol/providers/llm/__init__.py +15 -0
- py_aidol-0.5.0/aidol/providers/llm/base.py +147 -0
- py_aidol-0.5.0/aidol/providers/llm/openai.py +101 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/repositories/__init__.py +2 -0
- py_aidol-0.5.0/aidol/repositories/chatroom.py +142 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/schemas/__init__.py +35 -0
- py_aidol-0.5.0/aidol/schemas/chatroom.py +147 -0
- py_aidol-0.5.0/aidol/schemas/model_settings.py +35 -0
- py_aidol-0.5.0/aidol/schemas/persona.py +20 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/services/__init__.py +2 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/services/image_generation_service.py +24 -12
- py_aidol-0.5.0/aidol/services/response_generation_service.py +63 -0
- py_aidol-0.5.0/aidol/settings.py +46 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/pyproject.toml +4 -1
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/__init__.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/api/lead.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/models/aidol.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/models/aidol_lead.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/models/companion.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/py.typed +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/repositories/aidol.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/repositories/aidol_lead.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/repositories/companion.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/schemas/aidol.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/schemas/aidol_lead.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/schemas/companion.py +0 -0
- {py_aidol-0.3.0 → py_aidol-0.5.0}/aidol/services/companion_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.0
|
|
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)
|
|
@@ -34,7 +37,7 @@ AI 아이돌 그룹 생성 및 채팅 Python 패키지
|
|
|
34
37
|
## 주요 기능
|
|
35
38
|
|
|
36
39
|
- AI 아이돌 그룹/멤버 CRUD
|
|
37
|
-
-
|
|
40
|
+
- Google Gemini 이미지 생성 (엠블럼, 프로필)
|
|
38
41
|
- 텍스트 채팅 (페르소나 기반 응답)
|
|
39
42
|
- Buppy 통합 Adapter 패턴
|
|
40
43
|
|
|
@@ -62,7 +65,7 @@ from aidol.factories import AIdolRepositoryFactory, CompanionRepositoryFactory
|
|
|
62
65
|
# AIdol 라우터
|
|
63
66
|
aidol_router = AIdolRouter(
|
|
64
67
|
repository_factory=AIdolRepositoryFactory(),
|
|
65
|
-
|
|
68
|
+
google_settings=google_settings,
|
|
66
69
|
image_storage=image_storage,
|
|
67
70
|
)
|
|
68
71
|
|
|
@@ -87,11 +90,27 @@ make format
|
|
|
87
90
|
|
|
88
91
|
## 환경 변수
|
|
89
92
|
|
|
90
|
-
###
|
|
93
|
+
### 이미지 생성 인증 (선택, ADC 지원)
|
|
91
94
|
|
|
92
95
|
| 변수 | 설명 |
|
|
93
96
|
|------|------|
|
|
94
|
-
| `
|
|
97
|
+
| `GOOGLE_API_KEY` | Google API 키 (Google AI API) |
|
|
98
|
+
| `GOOGLE_CLOUD_PROJECT` | GCP 프로젝트 ID (Vertex AI) |
|
|
99
|
+
|
|
100
|
+
**인증 방법:**
|
|
101
|
+
|
|
102
|
+
**Option 1: Google AI API (API Key)**
|
|
103
|
+
```bash
|
|
104
|
+
export GOOGLE_API_KEY=your-api-key
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Option 2: Vertex AI (ADC)**
|
|
108
|
+
```bash
|
|
109
|
+
export GOOGLE_CLOUD_PROJECT=your-project-id
|
|
110
|
+
gcloud auth application-default login # 로컬 개발
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
> **참고**: Vertex AI 사용 시 `location=global`이 하드코딩되어 있습니다 (Gemini 이미지 생성 모델 요구사항).
|
|
95
114
|
|
|
96
115
|
### 선택
|
|
97
116
|
|
|
@@ -106,7 +125,8 @@ make format
|
|
|
106
125
|
|
|
107
126
|
- aioia-core (공통 인프라)
|
|
108
127
|
- FastAPI, SQLAlchemy, Pydantic
|
|
109
|
-
-
|
|
128
|
+
- Google Generative AI (이미지 생성)
|
|
129
|
+
- OpenAI (채팅)
|
|
110
130
|
- Pillow (이미지 처리)
|
|
111
131
|
|
|
112
132
|
## 라이선스
|
|
@@ -5,7 +5,7 @@ AI 아이돌 그룹 생성 및 채팅 Python 패키지
|
|
|
5
5
|
## 주요 기능
|
|
6
6
|
|
|
7
7
|
- AI 아이돌 그룹/멤버 CRUD
|
|
8
|
-
-
|
|
8
|
+
- Google Gemini 이미지 생성 (엠블럼, 프로필)
|
|
9
9
|
- 텍스트 채팅 (페르소나 기반 응답)
|
|
10
10
|
- Buppy 통합 Adapter 패턴
|
|
11
11
|
|
|
@@ -33,7 +33,7 @@ from aidol.factories import AIdolRepositoryFactory, CompanionRepositoryFactory
|
|
|
33
33
|
# AIdol 라우터
|
|
34
34
|
aidol_router = AIdolRouter(
|
|
35
35
|
repository_factory=AIdolRepositoryFactory(),
|
|
36
|
-
|
|
36
|
+
google_settings=google_settings,
|
|
37
37
|
image_storage=image_storage,
|
|
38
38
|
)
|
|
39
39
|
|
|
@@ -58,11 +58,27 @@ make format
|
|
|
58
58
|
|
|
59
59
|
## 환경 변수
|
|
60
60
|
|
|
61
|
-
###
|
|
61
|
+
### 이미지 생성 인증 (선택, ADC 지원)
|
|
62
62
|
|
|
63
63
|
| 변수 | 설명 |
|
|
64
64
|
|------|------|
|
|
65
|
-
| `
|
|
65
|
+
| `GOOGLE_API_KEY` | Google API 키 (Google AI API) |
|
|
66
|
+
| `GOOGLE_CLOUD_PROJECT` | GCP 프로젝트 ID (Vertex AI) |
|
|
67
|
+
|
|
68
|
+
**인증 방법:**
|
|
69
|
+
|
|
70
|
+
**Option 1: Google AI API (API Key)**
|
|
71
|
+
```bash
|
|
72
|
+
export GOOGLE_API_KEY=your-api-key
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Option 2: Vertex AI (ADC)**
|
|
76
|
+
```bash
|
|
77
|
+
export GOOGLE_CLOUD_PROJECT=your-project-id
|
|
78
|
+
gcloud auth application-default login # 로컬 개발
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **참고**: Vertex AI 사용 시 `location=global`이 하드코딩되어 있습니다 (Gemini 이미지 생성 모델 요구사항).
|
|
66
82
|
|
|
67
83
|
### 선택
|
|
68
84
|
|
|
@@ -77,7 +93,8 @@ make format
|
|
|
77
93
|
|
|
78
94
|
- aioia-core (공통 인프라)
|
|
79
95
|
- FastAPI, SQLAlchemy, Pydantic
|
|
80
|
-
-
|
|
96
|
+
- Google Generative AI (이미지 생성)
|
|
97
|
+
- OpenAI (채팅)
|
|
81
98
|
- Pillow (이미지 처리)
|
|
82
99
|
|
|
83
100
|
## 라이선스
|
|
@@ -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
|
]
|
|
@@ -21,6 +21,7 @@ from aidol.protocols import (
|
|
|
21
21
|
ImageStorageProtocol,
|
|
22
22
|
)
|
|
23
23
|
from aidol.schemas import AIdol, AIdolCreate, AIdolPublic, AIdolUpdate
|
|
24
|
+
from aidol.settings import GoogleGenAISettings
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
class AIdolCreateResponse(BaseModel):
|
|
@@ -41,11 +42,11 @@ class AIdolRouter(
|
|
|
41
42
|
|
|
42
43
|
def __init__(
|
|
43
44
|
self,
|
|
44
|
-
|
|
45
|
+
google_settings: GoogleGenAISettings | None,
|
|
45
46
|
image_storage: ImageStorageProtocol,
|
|
46
47
|
**kwargs,
|
|
47
48
|
):
|
|
48
|
-
self.
|
|
49
|
+
self.google_settings = google_settings
|
|
49
50
|
self.image_storage = image_storage
|
|
50
51
|
super().__init__(**kwargs)
|
|
51
52
|
|
|
@@ -55,7 +56,7 @@ class AIdolRouter(
|
|
|
55
56
|
register_image_generation_route(
|
|
56
57
|
router=self.router,
|
|
57
58
|
resource_name=self.resource_name,
|
|
58
|
-
|
|
59
|
+
google_settings=self.google_settings,
|
|
59
60
|
image_storage=self.image_storage,
|
|
60
61
|
)
|
|
61
62
|
|
|
@@ -142,7 +143,7 @@ class AIdolRouter(
|
|
|
142
143
|
|
|
143
144
|
|
|
144
145
|
def create_aidol_router(
|
|
145
|
-
|
|
146
|
+
google_settings: GoogleGenAISettings | None,
|
|
146
147
|
db_session_factory: sessionmaker,
|
|
147
148
|
repository_factory: AIdolRepositoryFactoryProtocol,
|
|
148
149
|
image_storage: ImageStorageProtocol,
|
|
@@ -155,7 +156,7 @@ def create_aidol_router(
|
|
|
155
156
|
Create AIdol router with dependency injection.
|
|
156
157
|
|
|
157
158
|
Args:
|
|
158
|
-
|
|
159
|
+
google_settings: Google API settings (uses ADC if api_key is None)
|
|
159
160
|
db_session_factory: Database session factory
|
|
160
161
|
repository_factory: Factory implementing AIdolRepositoryFactoryProtocol
|
|
161
162
|
image_storage: Image storage for permanent URLs
|
|
@@ -168,7 +169,7 @@ def create_aidol_router(
|
|
|
168
169
|
FastAPI APIRouter instance
|
|
169
170
|
"""
|
|
170
171
|
router = AIdolRouter(
|
|
171
|
-
|
|
172
|
+
google_settings=google_settings,
|
|
172
173
|
image_storage=image_storage,
|
|
173
174
|
model_class=AIdol,
|
|
174
175
|
create_schema=AIdolCreate,
|
|
@@ -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()
|
|
@@ -14,12 +14,13 @@ from aidol.schemas import (
|
|
|
14
14
|
ImageGenerationResponse,
|
|
15
15
|
)
|
|
16
16
|
from aidol.services import ImageGenerationService
|
|
17
|
+
from aidol.settings import GoogleGenAISettings
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
def register_image_generation_route(
|
|
20
21
|
router: APIRouter,
|
|
21
22
|
resource_name: str,
|
|
22
|
-
|
|
23
|
+
google_settings: GoogleGenAISettings | None,
|
|
23
24
|
image_storage: ImageStorageProtocol,
|
|
24
25
|
) -> None:
|
|
25
26
|
"""
|
|
@@ -28,7 +29,7 @@ def register_image_generation_route(
|
|
|
28
29
|
Args:
|
|
29
30
|
router: FastAPI APIRouter instance
|
|
30
31
|
resource_name: Resource name for the route path
|
|
31
|
-
|
|
32
|
+
google_settings: Google API settings (API Key or Vertex AI with ADC)
|
|
32
33
|
image_storage: Image Storage instance
|
|
33
34
|
"""
|
|
34
35
|
|
|
@@ -45,7 +46,7 @@ def register_image_generation_route(
|
|
|
45
46
|
async def generate_image(request: ImageGenerationRequest):
|
|
46
47
|
"""Generate image from prompt."""
|
|
47
48
|
# Generate and download image
|
|
48
|
-
service = ImageGenerationService(
|
|
49
|
+
service = ImageGenerationService(settings=google_settings)
|
|
49
50
|
image = service.generate_and_download_image(
|
|
50
51
|
prompt=request.prompt,
|
|
51
52
|
size="1024x1024",
|