Skip to content

Commit

Permalink
add chatlogs (#186)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfredfrancis authored Jan 28, 2025
1 parent b81df3f commit 5ec4103
Show file tree
Hide file tree
Showing 16 changed files with 395 additions and 38 deletions.
8 changes: 5 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
FROM python:3.12-bullseye
FROM --platform=linux/x86_64 python:3.12.7-slim

WORKDIR /usr/src/app

RUN pip install --upgrade pip
RUN apt-get update && apt-get install -y \
build-essential \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY . .
Expand Down
Empty file added app/admin/chatlogs/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions app/admin/chatlogs/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from fastapi import APIRouter
from typing import Optional
from datetime import datetime
import app.admin.chatlogs.store as store

router = APIRouter(prefix="/chatlogs", tags=["chatlogs"])


@router.get("/")
async def list_chatlogs(
page: int = 1,
limit: int = 10,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
):
"""Get paginated chat conversation history with optional date filtering"""
return await store.list_chatlogs(page, limit, start_date, end_date)


@router.get("/{thread_id}")
async def get_chat_thread(thread_id: str):
"""Get complete conversation history for a specific thread"""
conversation = await store.get_chat_thread(thread_id)
if not conversation:
return {"error": "Conversation not found"}

return conversation
31 changes: 31 additions & 0 deletions app/admin/chatlogs/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from pydantic import BaseModel
from typing import Dict, List, Optional
from datetime import datetime


class ChatMessage(BaseModel):
text: str
context: Optional[Dict] = {}


class ChatThreadInfo(BaseModel):
thread_id: str
date: datetime


class BotNessage(BaseModel):
text: str


class ChatLog(BaseModel):
user_message: ChatMessage
bot_message: List[BotNessage]
date: datetime
context: Optional[Dict] = {}


class ChatLogResponse(BaseModel):
total: int
page: int
limit: int
conversations: List[ChatThreadInfo]
83 changes: 83 additions & 0 deletions app/admin/chatlogs/store.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from typing import List, Optional
from datetime import datetime
from app.database import client
from .schemas import ChatLog, ChatLogResponse, ChatThreadInfo

# Initialize MongoDB collection
collection = client["chatbot"]["state"]


async def list_chatlogs(
page: int = 1,
limit: int = 10,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
) -> ChatLogResponse:
skip = (page - 1) * limit

# Build query filter
query = {}
if start_date or end_date:
query["date"] = {}
if start_date:
query["date"]["$gte"] = start_date
if end_date:
query["date"]["$lte"] = end_date

# Get total count of unique threads for pagination
pipeline = [
{"$match": query},
{"$group": {"_id": "$thread_id"}},
{"$count": "total"},
]
result = await collection.aggregate(pipeline).to_list(1)
total = result[0]["total"] if result else 0

# Get paginated results grouped by thread_id with latest date
pipeline = [
{"$match": query},
{"$sort": {"date": -1}},
{
"$group": {
"_id": "$thread_id",
"thread_id": {"$first": "$thread_id"},
"date": {"$first": "$date"},
}
},
{"$sort": {"date": -1}},
{"$skip": skip},
{"$limit": limit},
]

conversations = []
async for doc in collection.aggregate(pipeline):
conversations.append(
ChatThreadInfo(thread_id=doc["thread_id"], date=doc["date"])
)

return ChatLogResponse(
total=total, page=page, limit=limit, conversations=conversations
)


async def get_chat_thread(thread_id: str) -> List[ChatLog]:
"""Get complete conversation history for a specific thread"""

cursor = collection.find({"thread_id": thread_id}).sort("date", 1)
messages = await cursor.to_list(length=None)

if not messages:
return None

chat_logs = []
for msg in messages:
chat_logs.append(
ChatLog(
user_message=msg["user_message"],
bot_message=msg["bot_message"],
date=msg["date"],
context=msg.get("context", {}),
)
)

return chat_logs
1 change: 0 additions & 1 deletion app/bot/memory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def update(self, user_message: UserMessage):
self.missing_parameters = []
self.complete = False
self.current_node = None
self.date = None

def get_active_intent_id(self):
if self.intent:
Expand Down
9 changes: 6 additions & 3 deletions app/bot/nlu/entity_extractors/crf_entity_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import logging
from typing import Dict, Any, List, Optional
from app.bot.nlu.pipeline import NLUComponent
import os

MODEL_NAME = "crf__entity_extractor.model"
MODEL_NAME = "crf_entity_extractor.model"
logger = logging.getLogger(__name__)


Expand Down Expand Up @@ -119,7 +120,8 @@ def train(self, training_data: List[Dict[str, Any]], model_path: str) -> None:
"feature.possible_transitions": True,
}
)
trainer.train(f"{model_path}/{MODEL_NAME}")
path = os.path.join(model_path, MODEL_NAME)
trainer.train(path)

def load(self, model_path: str) -> bool:
"""
Expand All @@ -129,7 +131,8 @@ def load(self, model_path: str) -> bool:
"""
try:
self.tagger = pycrfsuite.Tagger()
self.tagger.open(f"{model_path}/entity_model.model")
path = os.path.join(model_path, MODEL_NAME)
self.tagger.open(path)
return True
except Exception as e:
logger.error(f"Error loading CRF model: {e}")
Expand Down
4 changes: 4 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from app.admin.train.routes import router as train_router
from app.admin.test.routes import router as test_router
from app.admin.integrations.routes import router as integrations_router
from app.admin.chatlogs.routes import router as chatlogs_router


from app.bot.channels.rest.routes import router as rest_router
from app.bot.channels.facebook.routes import router as facebook_router
Expand Down Expand Up @@ -54,6 +56,8 @@ async def root():
admin_router.include_router(train_router)
admin_router.include_router(test_router)
admin_router.include_router(integrations_router)
admin_router.include_router(chatlogs_router)


app.include_router(admin_router)

Expand Down
7 changes: 1 addition & 6 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@ version: '2'
name: ai-chatbot-framework-dev
services:
mongodb:
container_name: mongodb
image: mongo:4.2.20
hostname: mongodb
volumes:
- mongodbdata:/data

migrate:
container_name: migrate
build:
context: .
dockerfile: Dockerfile
Expand All @@ -21,7 +19,6 @@ services:
- mongodb

app:
container_name: backend
build:
context: .
dockerfile: Dockerfile
Expand All @@ -38,11 +35,9 @@ services:
- mongodb

frontend:
container_name: frontend
hostname: frontend
build:
context: ./frontend
dockerfile: ./dev.Dockerfile
dockerfile: dev.Dockerfile
volumes:
- ./frontend/app:/app/app
- ./frontend/public:/app/public
Expand Down
1 change: 1 addition & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
.next/
12 changes: 4 additions & 8 deletions frontend/app/admin/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import React, { useState, useEffect, useRef, useCallback } from 'react';
import { converse, ChatState, UserMessage } from '../../services/chat';
import { v4 as uuidv4 } from 'uuid';
import './style.css';

interface Message {
Expand All @@ -13,17 +12,12 @@ interface Message {
const ChatPage: React.FC = () => {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const [threadId, setThreadId] = useState<string>('');
const threadId = "test-user"
const [isLoading, setIsLoading] = useState(false);
const [chatState, setChatState] = useState<ChatState | null>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const isInitialMount = useRef(true);

// Initialize threadId on client side only
useEffect(() => {
setThreadId(uuidv4());
}, []);

const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
Expand All @@ -41,7 +35,9 @@ const ChatPage: React.FC = () => {
const initialMessage: UserMessage = {
thread_id: threadId,
text: '/init_conversation',
context: {}
context: {
username: "Admin"
}
};

try {
Expand Down
Loading

0 comments on commit 5ec4103

Please sign in to comment.