Skip to content

Conversation

@ywkim
Copy link
Contributor

@ywkim ywkim commented Jan 31, 2026

Buppy에서 추출한 chatroom 도메인 참조 구현을 추가합니다.

목적

aidol standalone 패키지에 채팅 기능의 참조 구현을 제공하여:

  • Buppy 통합 시 Adapter 패턴의 기반 코드 역할
  • aidol 독립 배포 시 즉시 사용 가능한 채팅 기능
  • LLM Provider 추상화 및 컨텍스트 빌더 패턴 표준화

주요 추가 사항

🔧 Backend

  • 스키마: Chatroom, Message, Persona, ModelSettings
  • 모델: DBChatroom, DBMessage (SQLAlchemy ORM)
  • Repository: ChatroomRepository (메시지 CRUD)
  • Provider: LLMProvider Protocol + OpenAI 구현
  • Context: MessageContextBuilder (한국어 시간 포맷, Provider 제약 처리)
  • Service: ResponseGenerationService (AI 응답 생성)
  • API: ChatroomRouter (메시지 전송, AI 응답 엔드포인트)

🎨 Frontend

  • 스키마: Chatroom, Message, SenderType (Zod 검증)
  • Repository: ChatroomRepository, LocalChatroomIdsRepository, LocalClaimTokenRepository
  • 컴포넌트: ChatRoom, MessageInput, MessageList (Markdown 지원)

📦 의존성

Backend: langchain-core, langchain-openai, litellm
Frontend: react-markdown, remark-breaks, remark-gfm

구조 변경

Backend

backend/aidol/
├── schemas/
│   ├── chatroom.py          # Chatroom, Message, SenderType
│   ├── persona.py           # Persona (timezone 포함)
│   └── model_settings.py    # ModelSettings
├── models/chatroom.py       # DBChatroom, DBMessage
├── repositories/chatroom.py # ChatroomRepository
├── providers/llm/
│   ├── base.py              # LLMProvider Protocol
│   └── openai.py            # OpenAILLMProvider
├── context/builder.py       # MessageContextBuilder
├── services/
│   └── response_generation_service.py
└── api/chatroom.py          # ChatroomRouter

Frontend

frontend/src/
├── schemas/chatroom.ts
├── repositories/
│   ├── ChatroomRepository.ts
│   ├── LocalChatroomIdsRepository.ts
│   └── LocalClaimTokenRepository.ts
└── components/chatroom/
    ├── ChatRoom.tsx
    ├── MessageInput.tsx
    └── MessageList.tsx

테스트 체크리스트

  • Backend lint/type-check 통과 확인
  • Frontend lint/type-check 통과 확인
  • Buppy 통합 시 import 경로 동작 확인

참고

  • 출처: ~/buppy/backend/aidol_internal/, ~/buppy/frontend/src/lib/aidol/
  • 관련 PR: algorima/buppy#1231 (fanchat-module-extraction)

ywkim and others added 13 commits January 31, 2026 13:50
Add schemas for chatroom domain:
- Chatroom, ChatroomCreate, ChatroomUpdate
- Message, MessageCreate, CompanionMessage, CompanionMessageCreate
- SenderType enum (USER, COMPANION)
- Persona with timezone support
- ModelSettings for LLM configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add database models for chatroom domain:
- DBChatroom: name, language with indexes
- DBMessage: chatroom_id FK, sender_type, content with indexes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add database-backed chatroom repository:
- Extends BaseRepository for CRUD operations
- add_message_to_chatroom: Create messages with auto-generated IDs
- get_messages_by_chatroom_id: Paginated message retrieval

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add LLM provider abstractions:
- LLMProvider Protocol for platform-agnostic LLM integration
- ProviderConstraints for context building (alternating turns, etc.)
- OpenAILLMProvider using LangChain ChatOpenAI
- lookup_context_window using LiteLLM model_cost

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add context engineering utilities:
- MessageContextBuilder: Builder pattern for LLM context assembly
- Provider constraint handling (combine_system_messages, alternating turns)
- format_datetime_korean: Korean datetime formatting
- format_utc_offset: UTC offset string formatting
- Helper functions for message validation and deduplication

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add service for TEXT response generation:
- Provides response format prompts (empty for TEXT)
- Generates response from prepared context via LLMProvider
- Extensible for AUDIO/VIDEO response types

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add chatroom API router:
- ChatroomRouter extending BaseCrudRouter
- Public create/get chatroom endpoints
- GET /messages: Paginated message retrieval
- POST /messages: Send user message
- POST /companions/{id}/response: Generate AI response
- ChatroomRepositoryProtocol and factory
- Settings with AIDOL_OPENAI_MODEL env var

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add dependencies for chatroom domain:
- langchain-core>=0.3.0: LangChain message types
- langchain-openai>=0.3.0: ChatOpenAI provider
- litellm>=1.60.0: Model context window lookup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add TypeScript schemas for chatroom domain:
- Chatroom, ChatroomCreate with Zod validation
- Message with SenderType enum (USER, COMPANION)
- Type guards: isUser, isCompanion

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Add repositories for chatroom domain:
- ChatroomRepository: CRUD, message send/get, AI response generation
- LocalChatroomIdsRepository: companionId → chatroomId mapping
- LocalClaimTokenRepository: shared claim token storage

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Add chatroom components:
- ChatRoom: Container combining MessageList and MessageInput
- MessageList: Message display with markdown support, loading skeleton
- MessageInput: Text input with send button

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Update entry points to export chatroom functionality:
- index.ts: Export ChatroomRepository, types, and schemas
- client.ts: Export ChatRoom, MessageInput, MessageList components

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Add dependencies for chatroom message rendering:
- react-markdown@^9.0.0: Markdown rendering in messages
- remark-breaks@^4.0.0: Line break support
- remark-gfm@^4.0.0: GitHub Flavored Markdown support

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

이 PR은 Buppy에서 추출한 채팅방 도메인의 참조 구현을 추가하는 중요한 변경 사항을 담고 있습니다. 백엔드 API, 데이터베이스 모델, 서비스 및 프론트엔드 컴포넌트를 포함하여 완전한 기능을 갖춘 모듈을 도입했습니다. 전반적으로 코드 구조가 잘 잡혀 있고, 프로토콜을 사용한 의존성 주입 등 좋은 설계 패턴을 따르고 있습니다. 다만, 몇 가지 중요한 수정이 필요한 부분이 있습니다. 특히 LLM에 전달되는 대화 기록의 순서가 반대로 되어 있어 시급한 수정이 필요하며, 데이터베이스 모델과 프론트엔드 컴포넌트에서 약간의 개선이 가능합니다. 자세한 내용은 아래 주석을 참고해 주세요.

)

# Convert to LangChain BaseMessage format
langchain_messages = _to_langchain_messages(messages)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

대화 기록이 LLM에 역순으로 전달되고 있습니다. repository.get_messages_by_chatroom_id는 최신 메시지부터 가져오도록(created_at.desc()) 구현되어 있는데, 이 순서를 뒤집지 않고 _to_langchain_messages로 전달하고 있습니다. LLM은 시간 순서에 맞는 대화 기록을 받아야 정확한 맥락을 파악할 수 있으므로, 메시지 목록을 올바른 순서로 뒤집어 전달해야 합니다.

Suggested change
langchain_messages = _to_langchain_messages(messages)
langchain_messages = _to_langchain_messages(messages[::-1])

Comment on lines +41 to +59
def _convert_db_message_to_model(db_message: DBMessage) -> Message:
"""Convert DB message to Pydantic model.

Returns CompanionMessage if sender_type is COMPANION, else Message.
"""
if db_message.sender_type == SenderType.COMPANION.value:
return CompanionMessage(
id=db_message.id,
sender_type=SenderType.COMPANION,
content=db_message.content,
created_at=db_message.created_at.replace(tzinfo=timezone.utc),
)

return Message(
id=db_message.id,
sender_type=SenderType(db_message.sender_type),
content=db_message.content,
created_at=db_message.created_at.replace(tzinfo=timezone.utc),
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

_convert_db_message_to_model 함수는 sender_typeCOMPANION이 아닌 모든 경우를 SenderType enum으로 변환하려고 시도합니다. 하지만 DBMessage 모델의 주석에 따르면 sender_type은 "system"이나 "unknown"일 수도 있습니다. 현재 SenderType enum에는 이 값들이 정의되어 있지 않아, 해당 값들이 DB에 저장될 경우 ValueError가 발생할 수 있습니다. 모든 가능한 sender_type 값을 처리하도록 코드를 수정해야 합니다.

def _convert_db_message_to_model(db_message: DBMessage) -> Message:
    """Convert DB message to Pydantic model.

    Returns CompanionMessage if sender_type is COMPANION, else Message.
    """
    sender_type_value = db_message.sender_type
    if sender_type_value == SenderType.COMPANION.value:
        return CompanionMessage(
            id=db_message.id,
            sender_type=SenderType.COMPANION,
            content=db_message.content,
            created_at=db_message.created_at.replace(tzinfo=timezone.utc),
        )

    try:
        sender_type = SenderType(sender_type_value)
    except ValueError:
        # "system"이나 "unknown"과 같이 SenderType에 정의되지 않은 값에 대한 폴백 처리
        sender_type = SenderType.USER

    return Message(
        id=db_message.id,
        sender_type=sender_type,
        content=db_message.content,
        created_at=db_message.created_at.replace(tzinfo=timezone.utc),
    )

Comment on lines +32 to +38
className="input bg-neutral/30 text-base-content placeholder:text-base-content focus:border-primary w-full pr-12 focus:outline-hidden"
/>
<button
type="submit"
className="btn btn-circle btn-ghost text-primary hover:bg-primary hover:text-primary-content absolute top-1/2 right-2 -translate-y-1/2 focus:outline-hidden"
>
<PaperAirplaneIcon className="size-6" />

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

focus:outline-hidden 클래스는 키보드 사용자가 현재 포커스된 요소를 식별하기 어렵게 만들어 웹 접근성을 저해합니다. 접근성 가이드라인에 따라 포커스 인디케이터는 항상 명확하게 보여야 합니다. inputbutton 요소 모두에서 이 클래스를 제거하는 것을 권장합니다.

# Get conversation history
messages = repository.get_messages_by_chatroom_id(
chatroom_id=item_id,
limit=200,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

대화 기록을 가져오는 limit 값이 200으로 하드코딩되어 있습니다. 이러한 '매직 넘버'는 코드의 가독성을 해치고 향후 유지보수를 어렵게 만들 수 있습니다. 이 값을 파일 상단에 상수로 정의하여 관리하는 것이 좋습니다.

Comment on lines +33 to +35
chatroom_id: Mapped[str] = mapped_column(
ForeignKey("chatrooms.id"), nullable=False, index=True
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

chatroom_id 컬럼에 index=True가 설정되어 있지만, __table_args__에서 ("ix_messages_chatroom_created", "chatroom_id", "created_at") 복합 인덱스의 첫 번째 컬럼으로도 사용되고 있습니다. 이는 중복된 인덱스 생성으로 이어질 수 있습니다. 데이터베이스는 보통 복합 인덱스의 첫 번째 컬럼을 단일 컬럼 쿼리에도 효율적으로 사용할 수 있습니다. GENERAL_RULES에 따라 중복을 피하기 위해 index=True를 제거하는 것이 좋습니다.

Suggested change
chatroom_id: Mapped[str] = mapped_column(
ForeignKey("chatrooms.id"), nullable=False, index=True
)
chatroom_id: Mapped[str] = mapped_column(
ForeignKey("chatrooms.id"), nullable=False
)
References
  1. When defining SQLAlchemy models, if an index is explicitly created using Index() in __table_args__, do not also set index=True on the column definition to avoid redundancy.

Comment on lines +82 to +83
openai_api_key=self._settings.api_key, # type: ignore[arg-type]
openai_organization=self._settings.organization, # type: ignore[arg-type]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

openai_api_keyopenai_organizationtype: ignore[arg-type] 주석이 사용되었습니다. langchain-openaiChatOpenAISecretStr 타입을 지원하므로 이 주석은 불필요해 보입니다. 코드 가독성과 유지보수성을 위해 불필요한 type: ignore는 제거하는 것이 좋습니다. 만약 실제로 타입 오류가 발생한다면, 무시하기보다는 명시적인 타입 변환 등 근본적인 해결책을 찾는 것이 바람직합니다.

Suggested change
openai_api_key=self._settings.api_key, # type: ignore[arg-type]
openai_organization=self._settings.organization, # type: ignore[arg-type]
openai_api_key=self._settings.api_key,
openai_organization=self._settings.organization,

},
)}
>
<div className="max-w-[60%] break-words text-base">

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

text-base 클래스는 Tailwind CSS의 기본 유틸리티로, 프로젝트 스타일 가이드(77번 라인)에 따라 사용이 금지되어 있습니다. 대신 tailwind.config.ts에 정의된 MD3 타입 스케일(text-body-m 등)을 사용해야 합니다.

Suggested change
<div className="max-w-[60%] break-words text-base">
<div className="max-w-[60%] break-words text-body-m">
References
  1. Tailwind의 기본 타이포그래피 유틸리티(text-base, text-lg 등) 사용을 금지하고, MD3 타입 스케일에 정의된 명명된 유틸리티(text-body-m 등)를 사용해야 합니다. (link)

ywkim added a commit that referenced this pull request Jan 31, 2026
- Add providers/ pattern (LLMProvider Protocol)
- Add services/ pattern (ResponseGenerationService)
- Reference chatroom domain implementation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants