strat-chatbot/app/models.py
Cal Corum c42fea66ba feat: initial chatbot implementation with FastAPI, ChromaDB, Discord bot, and Gitea integration
- Add vector store with sentence-transformers for semantic search
- FastAPI backend with /chat and /health endpoints
- Conversation state persistence via SQLite
- OpenRouter integration with structured JSON responses
- Discord bot with /ask slash command and reply-based follow-ups
- Automated Gitea issue creation for unanswered questions
- Docker support with docker-compose for easy deployment
- Example rule file and ingestion script
- Comprehensive documentation in README
2026-03-08 15:19:26 -05:00

101 lines
2.9 KiB
Python

"""Data models for rules and conversations."""
from pydantic import BaseModel, Field
from typing import Optional
from datetime import datetime
class RuleMetadata(BaseModel):
"""Frontmatter metadata for a rule document."""
rule_id: str = Field(..., description="Unique rule identifier, e.g. '5.2.1(b)'")
title: str = Field(..., description="Rule title")
section: str = Field(..., description="Section/category name")
parent_rule: Optional[str] = Field(
None, description="Parent rule ID for hierarchical rules"
)
last_updated: str = Field(
default_factory=lambda: datetime.now().strftime("%Y-%m-%d"),
description="Last update date",
)
page_ref: Optional[str] = Field(
None, description="Reference to page number in rulebook"
)
class RuleDocument(BaseModel):
"""Complete rule document with metadata and content."""
metadata: RuleMetadata
content: str = Field(..., description="Rule text and examples")
source_file: str = Field(..., description="Source file path")
embedding: Optional[list[float]] = None
def to_chroma_metadata(self) -> dict:
"""Convert to ChromaDB metadata format."""
return {
"rule_id": self.metadata.rule_id,
"title": self.metadata.title,
"section": self.metadata.section,
"parent_rule": self.metadata.parent_rule or "",
"page_ref": self.metadata.page_ref or "",
"last_updated": self.metadata.last_updated,
"source_file": self.source_file,
}
class Conversation(BaseModel):
"""Conversation session."""
id: str
user_id: str # Discord user ID
channel_id: str # Discord channel ID
created_at: datetime = Field(default_factory=datetime.now)
last_activity: datetime = Field(default_factory=datetime.now)
class Message(BaseModel):
"""Individual message in a conversation."""
id: str
conversation_id: str
content: str
is_user: bool
parent_id: Optional[str] = None
created_at: datetime = Field(default_factory=datetime.now)
class ChatRequest(BaseModel):
"""Incoming chat request from Discord."""
message: str
conversation_id: Optional[str] = None
parent_message_id: Optional[str] = None
user_id: str
channel_id: str
class ChatResponse(BaseModel):
"""Response to chat request."""
response: str
conversation_id: str
message_id: str
parent_message_id: Optional[str] = None
cited_rules: list[str] = Field(default_factory=list)
confidence: float = Field(..., ge=0.0, le=1.0)
needs_human: bool = Field(
default=False,
description="Whether the question needs human review (unanswered)",
)
class RuleSearchResult(BaseModel):
"""Result from vector search."""
rule_id: str
title: str
content: str
section: str
similarity: float = Field(..., ge=0.0, le=1.0)