Skip to content

Commit

Permalink
Fix serving ui from backend container
Browse files Browse the repository at this point in the history
  • Loading branch information
nfcampos committed May 11, 2024
1 parent 56fdd2f commit 8ec2b89
Show file tree
Hide file tree
Showing 16 changed files with 78 additions and 55 deletions.
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,8 @@ COPY ./backend .

# Copy the frontend build
COPY --from=builder /frontend/dist ./ui
COPY --from=builder /frontend/dist/index.html ./ui/404.html

ENTRYPOINT [ "uvicorn", "app.server:app", "--host", "0.0.0.0", "--log-config", "log_config.json" ]
ENV PORT=8000

ENTRYPOINT uvicorn app.server:app --host 0.0.0.0 --port $PORT --log-config log_config.json
2 changes: 1 addition & 1 deletion backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ RUN poetry config virtualenvs.create false \
# Copy the rest of application code
COPY . .

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --start-interval=1s --retries=3 CMD [ "curl", "-f", "http://localhost:8000/health" ]
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --start-interval=1s --retries=3 CMD [ "curl", "-f", "http://localhost:8000/ok" ]

ENTRYPOINT [ "uvicorn", "app.server:app", "--host", "0.0.0.0", "--log-config", "log_config.json" ]
30 changes: 26 additions & 4 deletions backend/app/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,37 @@
from fastapi import APIRouter
import orjson
from fastapi import APIRouter, Form, UploadFile, HTTPException

import app.storage as storage
from app.auth.handlers import AuthedUser
from app.upload import convert_ingestion_input_to_blob, ingest_runnable
from app.api.assistants import router as assistants_router
from app.api.runs import router as runs_router
from app.api.threads import router as threads_router

router = APIRouter()


@router.get("/ok")
async def ok():
return {"ok": True}
@router.post("/ingest", description="Upload files to the given assistant.")
async def ingest_files(
files: list[UploadFile], user: AuthedUser, config: str = Form(...)
) -> None:
"""Ingest a list of files."""
config = orjson.loads(config)

assistant_id = config["configurable"].get("assistant_id")
if assistant_id is not None:
assistant = await storage.get_assistant(user["user_id"], assistant_id)
if assistant is None:
raise HTTPException(status_code=404, detail="Assistant not found.")

thread_id = config["configurable"].get("thread_id")
if thread_id is not None:
thread = await storage.get_thread(user["user_id"], thread_id)
if thread is None:
raise HTTPException(status_code=404, detail="Thread not found.")

file_blobs = [convert_ingestion_input_to_blob(file) for file in files]
return ingest_runnable.batch(file_blobs, config)


router.include_router(
Expand Down
51 changes: 19 additions & 32 deletions backend/app/server.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import os
from pathlib import Path
from typing import Any, MutableMapping

from fastapi.exception_handlers import http_exception_handler
import httpx
import orjson
import structlog
from fastapi import FastAPI, Form, UploadFile
from fastapi import FastAPI
from fastapi.exception_handlers import http_exception_handler
from fastapi.exceptions import HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from starlette.responses import Response

import app.storage as storage
from app.api import router as api_router
from app.auth.handlers import AuthedUser
from app.lifespan import lifespan
from app.upload import convert_ingestion_input_to_blob, ingest_runnable

logger = structlog.get_logger(__name__)

Expand All @@ -31,41 +30,29 @@ async def httpx_http_status_error_handler(request, exc: httpx.HTTPStatusError):
ROOT = Path(__file__).parent.parent


app.include_router(api_router)

@app.get("/ok")
async def ok():
return {"ok": True}

@app.post("/ingest", description="Upload files to the given assistant.")
async def ingest_files(
files: list[UploadFile], user: AuthedUser, config: str = Form(...)
) -> None:
"""Ingest a list of files."""
config = orjson.loads(config)

assistant_id = config["configurable"].get("assistant_id")
if assistant_id is not None:
assistant = await storage.get_assistant(user["user_id"], assistant_id)
if assistant is None:
raise HTTPException(status_code=404, detail="Assistant not found.")
app.include_router(api_router, prefix="/api")

thread_id = config["configurable"].get("thread_id")
if thread_id is not None:
thread = await storage.get_thread(user["user_id"], thread_id)
if thread is None:
raise HTTPException(status_code=404, detail="Thread not found.")

file_blobs = [convert_ingestion_input_to_blob(file) for file in files]
return ingest_runnable.batch(file_blobs, config)

ui_dir = str(ROOT / "ui")

@app.get("/health")
async def health() -> dict:
return {"status": "ok"}

class StaticFilesSpa(StaticFiles):
async def get_response(
self, path: str, scope: MutableMapping[str, Any]
) -> Response:
res = await super().get_response(path, scope)
if isinstance(res, FileResponse) and res.status_code == 404:
res.status_code = 200
return res

ui_dir = str(ROOT / "ui")

if os.path.exists(ui_dir):
app.mount("", StaticFiles(directory=ui_dir, html=True), name="ui")
app.mount("", StaticFilesSpa(directory=ui_dir, html=True), name="ui")
else:
logger.warn("No UI directory found, serving API only.")

Expand Down
9 changes: 6 additions & 3 deletions backend/app/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,11 @@ async def get_or_create_user(sub: str) -> tuple[User, bool]:
async with get_pg_pool().acquire() as conn:
if user := await conn.fetchrow('SELECT * FROM "user" WHERE sub = $1', sub):
return user, False
user = await conn.fetchrow(
if user := await conn.fetchrow(
'INSERT INTO "user" (sub) VALUES ($1) ON CONFLICT (sub) DO NOTHING RETURNING *',
sub,
)
return user, True
):
return user, True
if user := await conn.fetchrow('SELECT * FROM "user" WHERE sub = $1', sub):
return user, False
raise RuntimeError("User creation failed.")
1 change: 1 addition & 0 deletions compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ services:
- --reload
frontend:
container_name: opengpts-frontend
pull_policy: build
build:
context: frontend
depends_on:
Expand Down
7 changes: 7 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
langgraph build -t langchain/opengpts-langgraph:0.1.5 --platform linux/amd64,linux/arm64
docker push langchain/opengpts-langgraph:0.1.5
gcloud beta run deploy opengpts-demo-langgraph --image langchain/opengpts-langgraph:0.1.5 --region us-central1 --project langchain-dev --env-vars-file .env.gcp.yaml

docker build -t langchain/opengpts-backend:0.1.1 --platform linux/amd64,linux/arm64 .
docker push langchain/opengpts-backend:0.1.1
gcloud beta run deploy opengpts-demo-backend --image langchain/opengpts-backend:0.1.1 --region us-central1 --project langchain-dev --env-vars-file .env.gcp.yaml
4 changes: 2 additions & 2 deletions frontend/src/api/assistants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export async function getAssistant(
assistantId: string,
): Promise<Config | null> {
try {
const response = await fetch(`/assistants/${assistantId}`);
const response = await fetch(`/api/assistants/${assistantId}`);
if (!response.ok) {
return null;
}
Expand All @@ -17,7 +17,7 @@ export async function getAssistant(

export async function getAssistants(): Promise<Config[] | null> {
try {
const response = await fetch(`/assistants/`);
const response = await fetch(`/api/assistants/`);
if (!response.ok) {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/api/threads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Chat } from "../types";

export async function getThread(threadId: string): Promise<Chat | null> {
try {
const response = await fetch(`/threads/${threadId}`);
const response = await fetch(`/api/threads/${threadId}`);
if (!response.ok) {
return null;
}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/hooks/useChatList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function useChatList(): ChatListProps {

useEffect(() => {
async function fetchChats() {
const chats = await fetch("/threads/", {
const chats = await fetch("/api/threads/", {
headers: {
Accept: "application/json",
},
Expand All @@ -45,7 +45,7 @@ export function useChatList(): ChatListProps {
}, []);

const createChat = useCallback(async (name: string, assistant_id: string) => {
const response = await fetch(`/threads`, {
const response = await fetch(`/api/threads`, {
method: "POST",
body: JSON.stringify({ assistant_id, name }),
headers: {
Expand Down Expand Up @@ -77,7 +77,7 @@ export function useChatList(): ChatListProps {

const deleteChat = useCallback(
async (thread_id: string) => {
await fetch(`/threads/${thread_id}`, {
await fetch(`/api/threads/${thread_id}`, {
method: "DELETE",
headers: {
Accept: "application/json",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useChatMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Message } from "../types";
import { StreamState, mergeMessagesById } from "./useStreamState";

async function getState(threadId: string) {
const { values, next } = await fetch(`/threads/${threadId}/state`, {
const { values, next } = await fetch(`/api/threads/${threadId}/state`, {
headers: {
Accept: "application/json",
},
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/hooks/useConfigList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export function useConfigList(): ConfigListProps {
assistantId?: string,
): Promise<string> => {
const confResponse = await fetch(
assistantId ? `/assistants/${assistantId}` : "/assistants",
assistantId ? `/api/assistants/${assistantId}` : "/api/assistants",
{
method: assistantId ? "PUT" : "POST",
body: JSON.stringify({ name, config, public: isPublic }),
Expand All @@ -92,7 +92,7 @@ export function useConfigList(): ConfigListProps {
"config",
JSON.stringify({ configurable: { assistant_id } }),
);
await fetch(`/ingest`, {
await fetch(`/api/ingest`, {
method: "POST",
body: formData,
});
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useMessageEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export function useMessageEditing(
}, []);
const commitEdits = useCallback(async () => {
if (!threadId) return;
fetch(`/threads/${threadId}/state`, {
fetch(`/api/threads/${threadId}/state`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ values: Object.values(editing) }),
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function useSchemas() {

useEffect(() => {
async function save() {
const configSchema = await fetch("/runs/config_schema")
const configSchema = await fetch("/api/runs/config_schema")
.then((r) => r.json())
.then(simplifySchema);
setSchemas({
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useStreamState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function useStreamState(): StreamStateProps {
setController(controller);
setCurrent({ status: "inflight", messages: input || [] });

await fetchEventSource("/runs/stream", {
await fetchEventSource("/api/runs/stream", {
signal: controller.signal,
method: "POST",
headers: { "Content-Type": "application/json" },
Expand Down
4 changes: 2 additions & 2 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ export default defineConfig({
plugins: [react()],
server: {
watch: {
usePolling: true
usePolling: true,
},
proxy: {
"^/(assistants|threads|ingest|runs)": {
"/api": {
target: process.env.VITE_BACKEND_URL || "http://127.0.0.1:8100",
changeOrigin: true,
rewrite: (path) => path.replace("/____LANGSERVE_BASE_URL", ""),
Expand Down

0 comments on commit 8ec2b89

Please sign in to comment.