Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS-101 Add report director #31

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions backend/src/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from src.chat_storage_service import get_chat_message
from src.directors.report_director import report_on_file_upload
from src.session.file_uploads import clear_session_file_uploads
from src.session.redis_session_middleware import reset_session
from src.utils import Config, test_connection
from src.director import question, dataset_upload
from src.directors.chat_director import question, dataset_upload
from src.websockets.connection_manager import connection_manager, parse_message
from src.session import RedisSessionMiddleware
from src.suggestions_generator import generate_suggestions
from src.file_upload_service import handle_file_upload, get_file_upload
from src.utils.file_utils import get_file_upload

config_file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "config.ini"))
logging.config.fileConfig(fname=config_file_path, disable_existing_loggers=False)
Expand Down Expand Up @@ -116,12 +117,12 @@ async def suggestions():
return JSONResponse(status_code=500, content=suggestions_failed_response)


@app.post("/uploadfile")
async def create_upload_file(file: UploadFile):
@app.post("/report")
async def report(file: UploadFile):
logger.info(f"upload file type={file.content_type} name={file.filename} size={file.size}")
try:
upload_id = handle_file_upload(file)
return JSONResponse(status_code=200, content={"filename": file.filename, "id": upload_id})
processed_upload = await report_on_file_upload(file)
return JSONResponse(status_code=200, content=processed_upload)
except HTTPException as he:
raise he
except Exception as e:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
logger = logging.getLogger(__name__)
config = Config()
engine = PromptEngine()
director_prompt = engine.load_prompt("director")
director_prompt = engine.load_prompt("chat_director")


async def question(question: str) -> ChatResponse:
Expand Down
23 changes: 23 additions & 0 deletions backend/src/directors/report_director.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

from typing import TypedDict
from fastapi import UploadFile

from src.utils.scratchpad import clear_scratchpad, update_scratchpad
from src.utils.file_utils import handle_file_upload

class FileUploadReport(TypedDict):
id: str
filename: str | None
report: str | None

async def report_on_file_upload(upload:UploadFile) -> FileUploadReport:

file = handle_file_upload(upload)

update_scratchpad(result=file["content"])

report = "#Report on upload as markdown" # await report_agent.invoke(file["content"])
Copy link
Collaborator

Choose a reason for hiding this comment

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

are we supposed to have this bit of commented out code?
and i this report just a placeholder or something? what is it used for

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe this is the part where we will plug in the report agent once it's complete. For now, it just returns some placeholder markdown

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, this is just a sample response and to show where the report agent will be integrated. The placeholder report isn't used by anything yet.


clear_scratchpad()

return {"filename": file["filename"], "id": file["uploadId"], "report": report}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

MAX_FILE_SIZE = 10*1024*1024

def handle_file_upload(file:UploadFile) -> str:
def handle_file_upload(file:UploadFile) -> FileUpload:

if (file.size or 0) > MAX_FILE_SIZE:
raise HTTPException(status_code=413, detail=f"File upload must be less than {MAX_FILE_SIZE} bytes")
Expand Down Expand Up @@ -49,7 +49,7 @@ def handle_file_upload(file:UploadFile) -> str:

update_session_file_uploads(session_file)

return session_file["uploadId"]
return session_file

def get_file_upload(upload_id) -> FileUpload | None:
return get_session_file_upload(upload_id)
Expand Down
11 changes: 11 additions & 0 deletions backend/tests/api/app_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from fastapi.testclient import TestClient
import pytest
from src.chat_storage_service import ChatResponse
from src.directors.report_director import FileUploadReport
from src.api import app, healthy_response, unhealthy_neo4j_response, chat_fail_response

client = TestClient(app)
Expand Down Expand Up @@ -77,6 +78,16 @@ def test_chat_message_not_found(mocker):
mock_get_chat_message.assert_called_with("123")
assert response.status_code == 404

def test_report_response_success(mocker):
mock_reponse = FileUploadReport(filename="filename", id="1", report="some report md")
mock_report = mocker.patch("src.api.app.report_on_file_upload", return_value=mock_reponse)

response = client.post("/report", files={"file": ("filename", "test data".encode("utf-8"), "text/plain")})

mock_report.assert_called_once()
assert response.status_code == 200
assert response.json() == {'filename': 'filename', 'id': '1', 'report': 'some report md'}

@pytest.mark.asyncio
async def test_lifespan_populates_db(mocker) -> None:
mock_dataset_upload = mocker.patch("src.api.app.dataset_upload", return_value=mocker.Mock())
Expand Down
22 changes: 22 additions & 0 deletions backend/tests/directors/report_director_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from io import BytesIO
from fastapi import UploadFile
from fastapi.datastructures import Headers
import pytest

from src.session.file_uploads import FileUpload
from src.directors.report_director import report_on_file_upload

@pytest.mark.asyncio
async def test_report_on_file_upload(mocker):

file_upload = FileUpload(uploadId="1", filename="test.txt", content="test", contentType="text/plain", size=4)

mock_handle_file_upload = mocker.patch("src.directors.report_director.handle_file_upload", return_value=file_upload)

headers = Headers({"content-type": "text/plain"})
file = BytesIO(b"test content")
request_upload_file = UploadFile(file=file, size=12, headers=headers, filename="test.txt")
response = await report_on_file_upload(request_upload_file)

mock_handle_file_upload.assert_called_once_with(request_upload_file)
assert response == {"filename": "test.txt", "id": "1", "report": "#Report on upload as markdown"}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
from fastapi.datastructures import Headers
import pytest

from src.session.file_uploads import FileUpload
from src.file_upload_service import handle_file_upload
from src.utils.file_utils import handle_file_upload

def test_handle_file_upload_size():

Expand All @@ -28,35 +27,23 @@ def test_handle_file_upload_unsupported_type():

def test_handle_file_upload_text(mocker):

mock = mocker.patch("src.file_upload_service.update_session_file_uploads", MagicMock())
mock = mocker.patch("src.utils.file_utils.update_session_file_uploads", MagicMock())

headers = Headers({"content-type": "text/plain"})
file = BytesIO(b"test content")
id = handle_file_upload(UploadFile(file=file, size=12, headers=headers, filename="test.txt"))

session_file = FileUpload(uploadId=id,
contentType="text/plain" ,
filename="test.txt",
content="test content",
size=12)
session_file = handle_file_upload(UploadFile(file=file, size=12, headers=headers, filename="test.txt"))

mock.assert_called_with(session_file)


def test_handle_file_upload_pdf(mocker):

mock = mocker.patch("src.file_upload_service.update_session_file_uploads", MagicMock())
pdf_mock = mocker.patch("src.file_upload_service.PdfReader", MagicMock())
mock = mocker.patch("src.utils.file_utils.update_session_file_uploads", MagicMock())
pdf_mock = mocker.patch("src.utils.file_utils.PdfReader", MagicMock())


headers = Headers({"content-type": "application/pdf"})
id = handle_file_upload(UploadFile(file=BytesIO(), size=12, headers=headers, filename="test.pdf"))

session_file = FileUpload(uploadId=id,
contentType="application/pdf" ,
filename="test.pdf",
content="",
size=12)
session_file = handle_file_upload(UploadFile(file=BytesIO(), size=12, headers=headers, filename="test.pdf"))

pdf_mock.assert_called_once()
mock.assert_called_with(session_file)
Expand Down
Loading