mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2026-11-01 01:24:45 +03:00
Merge pull request #246 from grillazz/245-lets-try-httpx2
245 lets try httpx2
This commit is contained in:
@@ -33,6 +33,7 @@
|
|||||||
<li><a href="#testing-strategy">Testing Strategy</a></li>
|
<li><a href="#testing-strategy">Testing Strategy</a></li>
|
||||||
<li><a href="#uv-knowledge-and-inspirations">UV knowledge and inspirations</a></li>
|
<li><a href="#uv-knowledge-and-inspirations">UV knowledge and inspirations</a></li>
|
||||||
<li><a href="#large-language-model">Integration with local LLM</a></li>
|
<li><a href="#large-language-model">Integration with local LLM</a></li>
|
||||||
|
<li><a href="#websocket-ai-chat-service">Websocket AI chat service</a></li>
|
||||||
<li><a href="#ha-sample-with-nginx-as-load-balancer">High Availability sample with nginx as load balancer</a></li>
|
<li><a href="#ha-sample-with-nginx-as-load-balancer">High Availability sample with nginx as load balancer</a></li>
|
||||||
<li><a href="#performance-profiling-with-pyinstrument">Performance Profiling with Pyinstrument</a></li>
|
<li><a href="#performance-profiling-with-pyinstrument">Performance Profiling with Pyinstrument</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -179,6 +180,65 @@ ollama run llama3.2
|
|||||||
|
|
||||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
|
### Websocket AI chat service
|
||||||
|
A general-purpose, websocket-based chat service lives at `/v1/chat/ws`, built with FastAPI 0.139.0
|
||||||
|
and Pydantic models for every message shape exchanged over the wire. It follows the
|
||||||
|
[Pydantic AI chat-app](https://github.com/pydantic/pydantic-ai/blob/main/docs/web.md) interaction
|
||||||
|
pattern - a session id, role-labeled messages, and streamed assistant chunks - so a UI can be wired
|
||||||
|
up later without changing the backend contract.
|
||||||
|
|
||||||
|
**Module layout**
|
||||||
|
- `app/schemas/chat.py` — Pydantic `BaseModel`s for `ChatMessage` (role/content) and the
|
||||||
|
client/server websocket event envelopes (discriminated on a `type` field).
|
||||||
|
- `app/services/chat_agent.py` — the pluggable async `ChatAgent` adapter `Protocol`, a
|
||||||
|
dependency-free `LocalEchoAgent` stub (default, no API keys/network required), and an
|
||||||
|
`OllamaChatAgent` example that streams from an OpenAI-compatible `/chat/completions` endpoint.
|
||||||
|
- `app/services/chat_session.py` — a small in-memory `ChatSessionManager` tracking one
|
||||||
|
`ChatSession` (id + message history) per open websocket connection.
|
||||||
|
- `app/api/chat.py` (re-exported as `app/ws.py`) — the `/ws` websocket route implementing the
|
||||||
|
chat flow.
|
||||||
|
|
||||||
|
**Lifespan wiring**: `app/main.py`'s `lifespan` builds the configured `ChatAgent` via
|
||||||
|
`build_chat_agent(settings.chat)` and a fresh `ChatSessionManager` on startup (`app.chat_agent`
|
||||||
|
/ `app.state.chat_sessions`), and calls `chat_agent.aclose()` on shutdown to release any held
|
||||||
|
connections.
|
||||||
|
|
||||||
|
**Configuration** (`ChatConfig` in `app/config.py`, all overridable via environment variables):
|
||||||
|
|
||||||
|
| Env var | Default | Description |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `CHAT_BACKEND` | `stub` | `stub` (local echo, no network) or `ollama` (OpenAI-compatible streaming) |
|
||||||
|
| `CHAT_BASE_URL` | `http://localhost:11434/v1` | Base URL used by the `ollama` backend |
|
||||||
|
| `CHAT_MODEL` | `llama3.2` | Model name sent to the `ollama` backend |
|
||||||
|
| `CHAT_STREAM_DELAY` | `0.02` | Per-word delay (seconds) the stub uses to emulate token streaming |
|
||||||
|
|
||||||
|
**Example websocket flow** (one JSON object per text frame):
|
||||||
|
```
|
||||||
|
-> (connect to ws://localhost:8080/v1/chat/ws)
|
||||||
|
<- {"type": "connected", "session_id": "..."}
|
||||||
|
-> {"type": "start", "system_prompt": "You are a helpful assistant."}
|
||||||
|
<- {"type": "started", "session_id": "..."}
|
||||||
|
-> {"type": "user_message", "content": "Hello there"}
|
||||||
|
<- {"type": "assistant_chunk", "index": 0, "content": "Hello "}
|
||||||
|
<- {"type": "assistant_chunk", "index": 1, "content": "there! "}
|
||||||
|
<- {"type": "assistant_message", "message": {"role": "assistant", "content": "Hello there! "}}
|
||||||
|
-> {"type": "history_request"}
|
||||||
|
<- {"type": "history", "messages": [...]}
|
||||||
|
-> {"type": "end"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Swapping in a real model**: implement the `ChatAgent` protocol (an async `stream_reply(messages)`
|
||||||
|
generator yielding text chunks, plus `aclose()`) in a new class - e.g. wrapping the OpenAI SDK - and
|
||||||
|
return it from `build_chat_agent` for a new `backend` value. No changes are needed in
|
||||||
|
`app/api/chat.py` or the session manager.
|
||||||
|
|
||||||
|
Run the tests for this service locally with:
|
||||||
|
```shell
|
||||||
|
pytest tests/api/test_chat_ws.py -vv
|
||||||
|
```
|
||||||
|
|
||||||
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
### HA sample with nginx as load balancer
|
### HA sample with nginx as load balancer
|
||||||
Sample high availability setup with nginx as load balancer and 2 uvicorn instances running on different ports.
|
Sample high availability setup with nginx as load balancer and 2 uvicorn instances running on different ports.
|
||||||
```shell
|
```shell
|
||||||
|
|||||||
+25
-24
@@ -1,50 +1,51 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "fastapi-sqlalchemy-asyncpg"
|
name = "fastapi-sqlalchemy-asyncpg"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
description = "A modern FastAPI application with SQLAlchemy 2.0 and AsyncPG for high-performance async database operations. Features include JWT authentication with Redis token storage, password hashing, connection pooling, data processing with Polars, Rich logging, task scheduling with APScheduler, and Shakespeare datasets integration."
|
description = "A modern FastAPI application with SQLAlchemy 2.1 and AsyncPG for high-performance async database operations. Features include JWT authentication with Redis token storage, password hashing, connection pooling, data processing with Polars, Rich logging, task scheduling with APScheduler, and Shakespeare datasets integration."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.14,<3.15"
|
requires-python = ">=3.14,<3.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastapi==0.139.2",
|
"fastapi==0.159.1",
|
||||||
"pydantic[email]==2.13.4",
|
"pydantic[email]==2.14.1",
|
||||||
"pydantic-settings==2.14.2",
|
"pydantic-settings==2.16.1",
|
||||||
"sqlalchemy==2.0.51",
|
"sqlalchemy==2.1.6",
|
||||||
"uvicorn[standard]==0.51.0",
|
"uvicorn[standard]==0.54.0",
|
||||||
"asyncpg==0.31.0",
|
"asyncpg==0.32.0",
|
||||||
"alembic==1.18.5",
|
"alembic==1.20.1",
|
||||||
"httpx==0.28.1",
|
"httpx==0.28.1",
|
||||||
"uvloop==0.22.1",
|
"uvloop==0.23.0",
|
||||||
"httptools==0.8.0",
|
"httptools==0.9.0",
|
||||||
"rich==15.0.0",
|
"rich==15.0.0",
|
||||||
"pyjwt==2.13.0",
|
"pyjwt==2.15.1",
|
||||||
"redis==8.0.1",
|
"redis==8.1.0",
|
||||||
"bcrypt==5.0.0",
|
"bcrypt==5.0.0",
|
||||||
"polars==1.42.1",
|
"polars==2.0.0",
|
||||||
"python-multipart==0.0.32",
|
"python-multipart==0.0.32",
|
||||||
"fastexcel==0.20.2",
|
"fastexcel==0.21.0",
|
||||||
"granian==2.7.9",
|
"granian==2.8.5",
|
||||||
"apscheduler[redis,sqlalchemy]==4.0.0a6",
|
"apscheduler[redis,sqlalchemy]==4.0.0a6",
|
||||||
"rotoger==0.3.0",
|
"rotoger==0.3.0",
|
||||||
"pyinstrument>=5.1.2",
|
"pyinstrument==5.1.3",
|
||||||
"websockets>=16.1.1",
|
"websockets==17.3",
|
||||||
"jinja2>=3.1.6",
|
"jinja2==3.1.6",
|
||||||
|
"whenever==0.10.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"ruff==0.15.22",
|
"ruff==0.17.1",
|
||||||
"devtools[pygments]==0.12.2",
|
"devtools[pygments]==0.13.0",
|
||||||
"pyupgrade==3.21.2",
|
"pyupgrade==3.22.0",
|
||||||
"ipython==9.15.0",
|
"ipython==9.17.1",
|
||||||
"tryceratops==2.4.1",
|
"tryceratops==2.4.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
test = [
|
test = [
|
||||||
"pytest==9.1.1",
|
"pytest==9.1.1",
|
||||||
"pytest-cov==7.1.0",
|
"pytest-cov==7.1.0",
|
||||||
"inline-snapshot==0.35.2",
|
"inline-snapshot==0.36.1",
|
||||||
"dirty-equals==0.11",
|
"dirty-equals==0.11",
|
||||||
"polyfactory==3.3.0",
|
"polyfactory==3.5.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.pyrefly.errors]
|
[tool.pyrefly.errors]
|
||||||
|
|||||||
Reference in New Issue
Block a user