Skip to content

Commit

Permalink
add fastapi tutorial & web chat (duplicate #104 ) (#110)
Browse files Browse the repository at this point in the history
* add: json storage with fastapi

* fix: pipeline request validation error

* remove flask tutorial

This tutorial is no longer needed now that FastAPI exists.

* move fast_api tutorial to tutorials/messengers/web_api_interface

* modify FastAPI example

- Remove db context_storage (there is no reason to show it in an interface tutorial)
- Add endpoint output annotation
- Run pipeline in async

* add websocket_chat tutorial

* update docs/source/conf.py

- Rename Messengers section to Interfaces
- Add Web API subsection

* reformat

* reformat

* add/tutorials_fastapi: rm tutorials/context_storages/8_json_storage_with_web_api.py

---------

Co-authored-by: romanzoniit <[email protected]>
Co-authored-by: Roman Zlobin <[email protected]>
  • Loading branch information
3 people authored Apr 24, 2023
1 parent 1e175ed commit ac6d951
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 47 deletions.
3 changes: 2 additions & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,10 @@ def setup(_):
("tutorials.context_storages", "Context Storages"),
(
"tutorials.messengers",
"Messengers",
"Interfaces",
[
("telegram", "Telegram"),
("web_api_interface", "Web API"),
],
),
("tutorials.pipeline", "Pipeline"),
Expand Down
8 changes: 8 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,14 @@ def merge_req_lists(*req_lists: List[str]) -> List[str]:
"flask[async]>=2.1.2",
"psutil>=5.9.1",
"telethon>=1.27.0,<2.0",
"anyio>=3.6.2",
"fastapi>=0.95.1",
"idna>=3.4",
"sniffio>=1.3.0",
"starlette>=0.26.1",
"h11>=0.14.0",
"uvicorn>=0.21.1",
"websockets>=11.0",
],
requests_requirements,
)
Expand Down
46 changes: 0 additions & 46 deletions tutorials/context_storages/8_json_storage_with_web_api.py

This file was deleted.

53 changes: 53 additions & 0 deletions tutorials/messengers/web_api_interface/1_fastapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# %% [markdown]
"""
# Web API: 1. FastAPI
This tutorial shows how to create an API for DFF using FastAPI.
You can see the result at http://127.0.0.1:8000/docs.
"""


# %%
from dff.script import Message
from dff.pipeline import Pipeline
from dff.utils.testing import TOY_SCRIPT, is_interactive_mode

import uvicorn
from pydantic import BaseModel
from fastapi import FastAPI


# %%
pipeline = Pipeline.from_script(
TOY_SCRIPT, ("greeting_flow", "start_node"), ("greeting_flow", "fallback_node")
)


# %%
app = FastAPI()


class Output(BaseModel):
user_id: int
response: Message


@app.post("/chat", response_model=Output)
async def respond(
user_id: int,
user_message: str,
):
request = Message(text=user_message)
context = await pipeline._run_pipeline(request, user_id) # run in async
return {"user_id": user_id, "response": context.last_response}


# %%
if __name__ == "__main__":
if is_interactive_mode(): # do not run this during doc building
uvicorn.run(
app,
host="127.0.0.1",
port=8000,
)
105 changes: 105 additions & 0 deletions tutorials/messengers/web_api_interface/2_websocket_chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# %% [markdown]
"""
# Web API: 2. WebSocket Chat
This tutorial shows how to create a Web chat on FastAPI using websockets.
You can see the result at http://127.0.0.1:8000/.
This tutorial is a modified version of the FastAPI tutorial on WebSockets:
https://fastapi.tiangolo.com/advanced/websockets/.
As mentioned in that tutorial,
> ... for this example, we'll use a very simple HTML document with some JavaScript,
> all inside a long string.
> This, of course, is not optimal and you wouldn't use it for production.
"""


# %%
from dff.script import Message
from dff.pipeline import Pipeline
from dff.utils.testing import TOY_SCRIPT, is_interactive_mode

import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse


# %%
pipeline = Pipeline.from_script(
TOY_SCRIPT, ("greeting_flow", "start_node"), ("greeting_flow", "fallback_node")
)


# %%
app = FastAPI()

html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<input type="text" id="messageText" autocomplete="off"/>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var client_id = Date.now();
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""


@app.get("/")
async def get():
return HTMLResponse(html)


@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: int):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
await websocket.send_text(f"User: {data}")
request = Message(text=data)
context = await pipeline._run_pipeline(request, client_id)
response = context.last_response.text
if response is not None:
await websocket.send_text(f"Bot: {response}")
else:
await websocket.send_text("Bot did not return text.")
except WebSocketDisconnect: # ignore disconnections
pass


# %%
if __name__ == "__main__":
if is_interactive_mode(): # do not run this during doc building
uvicorn.run(
app,
host="127.0.0.1",
port=8000,
)

0 comments on commit ac6d951

Please sign in to comment.