diff --git a/docs/guides/tutorials/fastapi_gelai_searchbot.rst b/docs/guides/tutorials/fastapi_gelai_searchbot.rst
index 3f5a35b21338..243eb08e2425 100644
--- a/docs/guides/tutorials/fastapi_gelai_searchbot.rst
+++ b/docs/guides/tutorials/fastapi_gelai_searchbot.rst
@@ -1,8 +1,8 @@
.. _ref_guide_fastapi_gelai_searchbot:
-=======
+===================
FastAPI (Searchbot)
-=======
+===================
:edb-alt-title: Building a searchbot with memory using FastAPI and Gel AI
@@ -36,12 +36,14 @@ follow their `installation instructions
`_ or simply run:
.. code-block:: bash
+
$ curl -LsSf https://astral.sh/uv/install.sh | sh
Once that is done, we can use uv to create scaffolding for our project following
the `documentation `_:
.. code-block:: bash
+
$ uv init searchbot \
&& cd searchbot
@@ -54,6 +56,7 @@ will create our virtual environment in a ``.venv`` directory and ensure it's
ready. As a last step, we'll activate the environment and get started.
.. code-block:: bash
+
$ uv add "fastapi[standard]" \
&& uv add gel \
&& uv sync \
@@ -75,6 +78,7 @@ user query as an input and echoes it as an output. First, let's make a directory
called ``app`` in our project root, and put an empty ``__init__.py`` there.
.. code-block:: bash
+
$ mkdir app && touch app/__init__.py
Create a file called ``main.py`` inside the ``app`` directory and put the "Hello
@@ -95,12 +99,14 @@ World" example in it:
To start the server, run:
.. code-block:: bash
+
$ fastapi dev app/main.py
Once the server gets up and running, we can make sure it works using FastAPI's
built-in UI at _, or manually by using ``curl``:
.. code-block:: bash
+
$ curl -X 'GET' \
'http://127.0.0.1:8000/' \
-H 'accept: application/json'
@@ -123,6 +129,7 @@ Let's add the following to our ``main.py``:
.. code-block:: python
:caption: app/main.py
+
from pydantic import BaseModel
@@ -137,6 +144,7 @@ Now we can define our endpoint and set the two classes we just added as its
argument and return type.
.. code-block:: python
+
@app.post("/search")
async def search(search_terms: SearchTerms) -> SearchResult:
return SearchResult(response=search_terms.query)
@@ -145,6 +153,7 @@ Same as before, we can test the endpoint using the UI, or by sending a request
with ``curl``:
.. code-block:: bash
+
$ curl -X 'POST' \
'http://127.0.0.1:8000/search' \
-H 'accept: application/json' \
@@ -172,6 +181,7 @@ way for us to get our search results is to employ the ``googlesearch-python``
library:
.. code-block:: bash
+
$ uv add googlesearch-python
As you can see from it's `repository
@@ -249,11 +259,13 @@ making HTTP requests, and Beautiful Soup, which is a commonly used HTML parsing
library. Let's add them by running:
.. code-block:: bash
+
$ uv add beautifulsoup4 requests
... and test out LLM-generated solution to see if it works:
.. code-block:: bash
+
$ python3 app/web.py
https://www.geldata.com
@@ -293,6 +305,7 @@ Step 4. Connect to the LLM
==========================
.. note::
+
add links to documentation
Now that we're capable of scraping text from search results, we can forward
@@ -304,6 +317,7 @@ how ubiquitous it is. To avoid delicate fiddling with HTML requests, let's add
their library as another dependency:
.. code-block:: bash
+
$ uv add openai
Then we can grab some code straight from their `API documentation
@@ -311,6 +325,7 @@ Then we can grab some code straight from their `API documentation
generation like this:
.. code-block:: python
+
from openai import OpenAI
from dotenv import load_dotenv()
@@ -360,6 +375,7 @@ root directory and put your api key in there:
.. code-block:: bash
:caption: .env
+
OPENAI_API_KEY="sk-..."
And as usual, let's reflect the new capabilities in the app and test it:
@@ -375,6 +391,7 @@ And as usual, let's reflect the new capabilities in the app and test it:
)
.. code-block:: bash
+
curl -X 'POST' \
'http://127.0.0.1:8000/search' \
-H 'accept: application/json' \
@@ -405,10 +422,11 @@ from the context of the entire conversation.
Now's a good time to introduce Gel.
In case you need installation instructions, take a look at :ref:`Quickstart UI
-<_ref_quickstart>`. Once Gel CLI is present in your system, initialize the
+`. Once Gel CLI is present in your system, initialize the
project like this:
.. code-block:: bash
+
$ gel project init --non-interactive
This command is going to put some project scaffolding inside our app, spin up a
@@ -421,8 +439,8 @@ connection incantations.
Defining the schema
-------------------
-The database :ref:`schema <_ref_datamodel_index>` in Gel is defined
-declaratively. The :ref:`gel project init <_ref_cli_edgedb_project_init>`
+The database :ref:`schema ` in Gel is defined
+declaratively. The :ref:`gel project init `
command has created a file called ``dbchema/default.esdl``, which we're going to
use to define our types.
@@ -430,13 +448,14 @@ We obviously want to keep track of messages, so we need to represent those in
the schema. By convention established in the LLM space, each message is going to
have a role in addition to the message content itself. We can also get Gel to
automatically keep track of message's creation time by adding a property callled
-``timestamp`` and setting its :ref:`default value <_ref_datamodel_props>` to the
-output of the :ref:`datetime_current() <_ref_std_datetime>` function. Finally,
+``timestamp`` and setting its :ref:`default value ` to the
+output of the :ref:`datetime_current() ` function. Finally,
LLM messages in our searchbot have souce URLs associated with them. Let's keep
track of those too, by adding a :ref:`multi-link property
-<_ref_datamodel_links>`.
+`.
.. code-block:: sdl
+
type Message {
role: str;
body: str;
@@ -450,6 +469,7 @@ Messages are grouped together into a chat, so let's add that entity to our
schema too.
.. code-block:: sdl
+
type Chat {
multi messages: Message;
}
@@ -457,10 +477,11 @@ schema too.
And chats all belong to a certain user, making up their chat history. One other
thing we'd like to keep track of about our users is their username, and it would
make sense for us to make sure that it's unique by using an ``excusive``
-:ref:`constraint <_ref_datamodel_constraints>`.
+:ref:`constraint `.
.. code-block:: sdl
+
type User {
name: str {
constraint exclusive;
@@ -475,6 +496,7 @@ AI down the road, but we're gonna come back to that later.
For now, this is the entire schema we came up with:
.. code-block:: sdl
+
module default {
type Message {
role: str;
@@ -497,14 +519,16 @@ For now, this is the entire schema we came up with:
}
}
-Let's use the :ref:`gel migration create <_ref_cli_edgedb_migration_create>` CLI
-command, followed by :ref:`gel migrate <_ref_cli_edgedb_migrate>` in order to
+Let's use the :ref:`gel migration create ` CLI
+command, followed by :ref:`gel migrate ` in order to
migrate to our new schema and proceed to writing some queries.
-.. code-block:: sdl
+.. code-block:: bash
+
$ gel migration create
-.. code-block:: sdl
+.. code-block:: bash
+
$ gel migrate
Now that our schema is applied, let's quickly populate the database with some
@@ -513,6 +537,7 @@ writing queries in a bit, but for now you can just run the following command in
the shell:
.. code-block:: bash
+
$ mkdir app/sample_data && cat << 'EOF' > app/sample_data/inserts.edgeql
# Create users first
insert User {
@@ -595,6 +620,7 @@ This created an ``app/sample_data/inserts.edgeql`` file, which we can now execut
using the CLI like this:
.. code-block:: bash
+
$ gel query -f app/sample_data/inserts.edgeql
{"id": "862de904-de39-11ef-9713-4fab09220c4a"}
@@ -602,11 +628,12 @@ using the CLI like this:
{"id": "862de904-de39-11ef-9713-4fab09220c4a"}
{"id": "862e400c-de39-11ef-9713-2f81f2b67013"}
-The :ref:`gel query <_ref_cli_edgedb_query>` command is one of many ways we can
+The :ref:`gel query ` command is one of many ways we can
execute a query in Gel. Now that we've done it, there's stuff in the database.
Let's verify it by running:
.. code-block:: bash
+
$ gel query "select User { name };"
{"name": "alice"}
@@ -619,7 +646,7 @@ With schema in place, it's time to focus on getting the data in and out of the
database.
In this tutorial we're going to write queries using :ref:`EdgeQL
-<_ref_intro_edgeql>` and then use :ref:`codegen <_edgedb-python-codegen>` to
+` and then use :ref:`codegen ` to
generate typesafe function that we can plug directly into out Python code. If
you are completely unfamiliar with EdgeQL, now is a good time to check out the
basics before proceeding.
@@ -639,6 +666,7 @@ in there:
Now run the code generator from the shell:
.. code-block:: bash
+
$ gel-py
It's going to automatically locate the ``.edgeql`` file and generate types for
@@ -646,6 +674,7 @@ it. We can inspect generated code in ``app.queries/get_users_async_edgeql.py``.
Once that is done, let's use those types to create the endpoint in ``main.py``:
.. code-block:: python
+
from edgedb import create_async_client
from .queries.get_users_async_edgeql import get_users as get_users_query, GetUsersResult
@@ -659,6 +688,7 @@ Once that is done, let's use those types to create the endpoint in ``main.py``:
Let's verify it that works as expected:
.. code-block:: bash
+
$ curl -X 'GET' \
'http://127.0.0.1:8000/users' \
-H 'accept: application/json'
@@ -730,6 +760,7 @@ going to be ``None``, which will enable us to implement our conditional logic:
And once again, let's verify that everything works:
.. code-block:: bash
+
$ curl -X 'GET' \
'http://127.0.0.1:8000/users?username=alice' \
-H 'accept: application/json'
@@ -745,6 +776,7 @@ before, we'll create a new file ``app/queries/create_user.edgeql``, add a query
to it and run code generation.
.. code-block:: edgeql
+
select(
insert User {
name := $username
@@ -762,6 +794,7 @@ endpoint. Note that this one has the same name ``/users``, but is for the POST
HTTP method.
.. code-block:: python
+
from gel import ConstraintViolationError
from .queries.create_user_async_edgeql import (
create_user as create_user_query,
@@ -781,6 +814,7 @@ HTTP method.
Once more, let's verify that the new endpoint works as expected:
.. code-block:: bash
+
$ curl -X 'POST' \
'http://127.0.0.1:8000/users?username=charlie' \
-H 'accept: application/json' \
@@ -839,6 +873,7 @@ below if you are in rush.
.. code-block:: python
:caption: app/main.py
+
from .queries.get_chats_async_edgeql import get_chats as get_chats_query, GetChatsResult
from .queries.get_chat_by_id_async_edgeql import (
get_chat_by_id as get_chat_by_id_query,
@@ -897,6 +932,7 @@ messages, retrieving chat history, invoking web search and generating the
answer.
.. code-block:: python
+
@app.post("/messages", status_code=HTTPStatus.CREATED)
async def post_messages(
search_terms: SearchTerms,
@@ -939,6 +975,7 @@ Let's not forget to modify the ``generate_answer`` function, so it can also be
history-aware.
.. code-block:: python
+
async def generate_answer(
query: str,
chat_history: list[GetMessagesResult],
@@ -981,6 +1018,7 @@ Ok, this should be it for setting up the chat history. Let's test it. First, we
are going to start a new chat for our user:
.. code-block:: bash
+
$ curl -X 'POST' \
'http://127.0.0.1:8000/chats?username=charlie' \
-H 'accept: application/json' \
@@ -995,6 +1033,7 @@ are going to start a new chat for our user:
Next, let's add a couple messages and wait for the bot to respond:
.. code-block:: bash
+
$ curl -X 'POST' \
'http://127.0.0.1:8000/messages?username=charlie&chat_id=544ef3f2-ded8-11ef-ba16-f7f254b95e36' \
-H 'accept: application/json' \
@@ -1029,6 +1068,7 @@ Finally, let's check that the messages we saw are in fact stored in the chat
history:
.. code-block:: bash
+
$ curl -X 'GET' \
'http://127.0.0.1:8000/messages?username=charlie&chat_id=544ef3f2-ded8-11ef-ba16-f7f254b95e36' \
-H 'accept: application/json'
@@ -1114,6 +1154,7 @@ modifications to the ``main.py``:
.. code-block:: python
:caption: app/main.py
+
@app.post("/messages", status_code=HTTPStatus.CREATED)
async def post_messages(
search_terms: SearchTerms,
@@ -1210,12 +1251,14 @@ We begin by enabling the ``ai`` extension by adding the following like on top of
the ``dbschema/default.esdl``:
.. code-block:: sdl
+
using extension ai;
... and do the migration:
.. code-block:: bash
+
$ gel migration create
$ gel migrate
@@ -1223,7 +1266,8 @@ Next, we need to configure the API key in Gel for whatever embedding provider
we're going to be using. As per documentation, let's open up the CLI by typing
``gel`` and run the following command (assuming we're using OpenAI):
-.. code-block:: edgeql
+.. code-block:: edgeql-repl
+
searchbot:main> configure current database
insert ext::ai::OpenAIProviderConfig {
secret := 'sk-....',
@@ -1235,6 +1279,7 @@ In order to get Gel to automatically keep track of creating and updating message
embeddings, all we need to do is create a deferred index like this:
.. code-block:: sdl
+
type Message {
role: str;
body: str;
@@ -1254,6 +1299,7 @@ embedding vectors for our queries. To make sure nothing broke we can follow
Gel's AI documentation and take a look at instance logs:
.. code-block:: bash
+
$ gel instance logs -I searchbot | grep api.openai.com
INFO 50121 searchbot 2025-01-30T14:39:53.364 httpx: HTTP Request: POST https://api.openai.com/v1/embeddings "HTTP/1.1 200 OK"
@@ -1264,6 +1310,7 @@ similar to our current message. This can be a little difficult to visualize in
your head, so here's the query itself:
.. code-block:: edgeql
+
with
user := (select User filter .name = $username),
chats := (select Chat filter .