Skip to content

Commit 7a92c4d

Browse files
authored
Add Database Management Tools to AI Assistant (baserow#4081)
* Add database tools * Address feedback --------- Authored-by: Davide Silvestri <davide@baserow.io>
1 parent 320d89d commit 7a92c4d

File tree

44 files changed

+5294
-331
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+5294
-331
lines changed

backend/src/baserow/contrib/database/locale/en/LC_MESSAGES/django.po

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2025-09-29 14:05+0000\n"
11+
"POT-Creation-Date: 2025-10-13 19:58+0000\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -335,39 +335,39 @@ msgstr ""
335335
msgid "Table \"%(table_name)s\" (%(table_id)s) created"
336336
msgstr ""
337337

338-
#: src/baserow/contrib/database/table/actions.py:104
338+
#: src/baserow/contrib/database/table/actions.py:107
339339
msgid "Delete table"
340340
msgstr ""
341341

342-
#: src/baserow/contrib/database/table/actions.py:105
342+
#: src/baserow/contrib/database/table/actions.py:108
343343
#, python-format
344344
msgid "Table \"%(table_name)s\" (%(table_id)s) deleted"
345345
msgstr ""
346346

347-
#: src/baserow/contrib/database/table/actions.py:160
347+
#: src/baserow/contrib/database/table/actions.py:163
348348
msgid "Order tables"
349349
msgstr ""
350350

351-
#: src/baserow/contrib/database/table/actions.py:161
351+
#: src/baserow/contrib/database/table/actions.py:164
352352
msgid "Tables order changed"
353353
msgstr ""
354354

355-
#: src/baserow/contrib/database/table/actions.py:224
355+
#: src/baserow/contrib/database/table/actions.py:227
356356
msgid "Update table"
357357
msgstr ""
358358

359-
#: src/baserow/contrib/database/table/actions.py:226
359+
#: src/baserow/contrib/database/table/actions.py:229
360360
#, python-format
361361
msgid ""
362362
"Table (%(table_id)s) name changed from \"%(original_table_name)s\" to "
363363
"\"%(table_name)s\""
364364
msgstr ""
365365

366-
#: src/baserow/contrib/database/table/actions.py:296
366+
#: src/baserow/contrib/database/table/actions.py:299
367367
msgid "Duplicate table"
368368
msgstr ""
369369

370-
#: src/baserow/contrib/database/table/actions.py:298
370+
#: src/baserow/contrib/database/table/actions.py:301
371371
#, python-format
372372
msgid ""
373373
"Table \"%(table_name)s\" (%(table_id)s) duplicated from "

backend/src/baserow/contrib/database/table/actions.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dataclasses
2-
from typing import Any, List, Optional
2+
from typing import Any, Dict, List, Optional, Tuple
33

44
from django.contrib.auth.models import AbstractUser
55
from django.utils.translation import gettext_lazy as _
@@ -47,8 +47,9 @@ def do(
4747
name: str,
4848
data: Optional[List[List[Any]]] = None,
4949
first_row_header: bool = True,
50+
fill_example: bool = True,
5051
progress: Optional[Progress] = None,
51-
) -> Table:
52+
) -> Tuple[Table, Dict[str, Dict[str, Any]]]:
5253
"""
5354
Create a table in the specified database.
5455
Undoing this action trashes the table and redoing restores it.
@@ -61,6 +62,8 @@ def do(
6162
:param first_row_header: Indicates if the first row are the fields. The names
6263
of these rows are going to be used as fields. If `fields` is provided,
6364
this options is ignored.
65+
:param fill_example: Whether or not to fill the table with example data if
66+
no data is provided.
6467
:param progress: An optional progress instance if you want to track the progress
6568
of the task.
6669
:return: The created table and the error report.
@@ -72,7 +75,7 @@ def do(
7275
name,
7376
data=data,
7477
first_row_header=first_row_header,
75-
fill_example=True,
78+
fill_example=fill_example,
7679
progress=progress,
7780
)
7881

backend/src/baserow/contrib/database/views/handler.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -578,12 +578,12 @@ def list_views(
578578
self,
579579
user: AbstractUser,
580580
table: Table,
581-
_type: str,
582-
filters: bool,
583-
sortings: bool,
584-
decorations: bool,
585-
group_bys: bool,
586-
limit: int,
581+
_type: str | None = None,
582+
filters: bool = True,
583+
sortings: bool = True,
584+
decorations: bool = True,
585+
group_bys: bool = True,
586+
limit: int | None = None,
587587
) -> Iterable[View]:
588588
"""
589589
Lists available views for a user/table combination.

backend/src/baserow/core/locale/en/LC_MESSAGES/django.po

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2025-09-29 14:05+0000\n"
11+
"POT-Creation-Date: 2025-10-13 19:58+0000\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -242,7 +242,7 @@ msgstr ""
242242
msgid "Decimal number"
243243
msgstr ""
244244

245-
#: src/baserow/core/handler.py:2185 src/baserow/core/user/handler.py:267
245+
#: src/baserow/core/handler.py:2187 src/baserow/core/user/handler.py:267
246246
#, python-format
247247
msgid "%(name)s's workspace"
248248
msgstr ""

backend/src/baserow/locale/en/LC_MESSAGES/django.po

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgid ""
88
msgstr ""
99
"Project-Id-Version: PACKAGE VERSION\n"
1010
"Report-Msgid-Bugs-To: \n"
11-
"POT-Creation-Date: 2025-09-30 08:04+0000\n"
11+
"POT-Creation-Date: 2025-10-13 19:58+0000\n"
1212
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1313
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1414
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -35,15 +35,15 @@ msgstr ""
3535
#: src/baserow/contrib/automation/action_scopes.py:14
3636
#, python-format
3737
msgid ""
38-
"of type (%(node_type)s) in automation "
39-
"\"%(automation_name)s\" (%(automation_id)s)."
38+
"of type (%(node_type)s) in automation \"%(automation_name)s\" "
39+
"(%(automation_id)s)."
4040
msgstr ""
4141

4242
#: src/baserow/contrib/automation/actions.py:8
4343
#, python-format
4444
msgid ""
45-
"in workflow (%(workflow_id)s) in automation "
46-
"\"%(automation_name)s\" (%(automation_id)s)."
45+
"in workflow (%(workflow_id)s) in automation \"%(automation_name)s\" "
46+
"(%(automation_id)s)."
4747
msgstr ""
4848

4949
#: src/baserow/contrib/automation/automation_init_application.py:29
@@ -54,62 +54,71 @@ msgstr ""
5454
msgid "Local Baserow"
5555
msgstr ""
5656

57-
#: src/baserow/contrib/automation/nodes/actions.py:28
57+
#: src/baserow/contrib/automation/nodes/actions.py:32
5858
msgid "Create automation node"
5959
msgstr ""
6060

61-
#: src/baserow/contrib/automation/nodes/actions.py:29
61+
#: src/baserow/contrib/automation/nodes/actions.py:33
6262
#, python-format
6363
msgid "Node (%(node_id)s) created"
6464
msgstr ""
6565

66-
#: src/baserow/contrib/automation/nodes/actions.py:100
66+
#: src/baserow/contrib/automation/nodes/actions.py:104
6767
msgid "Update automation node"
6868
msgstr ""
6969

70-
#: src/baserow/contrib/automation/nodes/actions.py:101
70+
#: src/baserow/contrib/automation/nodes/actions.py:105
7171
#, python-format
7272
msgid "Node (%(node_id)s) updated"
7373
msgstr ""
7474

75-
#: src/baserow/contrib/automation/nodes/actions.py:169
75+
#: src/baserow/contrib/automation/nodes/actions.py:173
7676
msgid "Delete automation node"
7777
msgstr ""
7878

79-
#: src/baserow/contrib/automation/nodes/actions.py:170
79+
#: src/baserow/contrib/automation/nodes/actions.py:174
8080
#, python-format
8181
msgid "Node (%(node_id)s) deleted"
8282
msgstr ""
8383

84-
#: src/baserow/contrib/automation/nodes/actions.py:227
84+
#: src/baserow/contrib/automation/nodes/actions.py:231
8585
msgid "Order nodes"
8686
msgstr ""
8787

88-
#: src/baserow/contrib/automation/nodes/actions.py:228
88+
#: src/baserow/contrib/automation/nodes/actions.py:232
8989
msgid "Node order changed"
9090
msgstr ""
9191

92-
#: src/baserow/contrib/automation/nodes/actions.py:296
92+
#: src/baserow/contrib/automation/nodes/actions.py:300
9393
msgid "Duplicate automation node"
9494
msgstr ""
9595

96-
#: src/baserow/contrib/automation/nodes/actions.py:297
96+
#: src/baserow/contrib/automation/nodes/actions.py:301
9797
#, python-format
9898
msgid "Node (%(node_id)s) duplicated"
9999
msgstr ""
100100

101-
#: src/baserow/contrib/automation/nodes/actions.py:380
101+
#: src/baserow/contrib/automation/nodes/actions.py:384
102102
msgid "Replace automation node"
103103
msgstr ""
104104

105-
#: src/baserow/contrib/automation/nodes/actions.py:382
105+
#: src/baserow/contrib/automation/nodes/actions.py:386
106106
#, python-format
107107
msgid ""
108108
"Node (%(node_id)s) changed from a type of %(original_node_type)s to "
109109
"%(node_type)s"
110110
msgstr ""
111111

112-
#: src/baserow/contrib/automation/nodes/node_types.py:195
112+
#: src/baserow/contrib/automation/nodes/actions.py:491
113+
msgid "Moved automation node"
114+
msgstr ""
115+
116+
#: src/baserow/contrib/automation/nodes/actions.py:492
117+
#, python-format
118+
msgid "Node (%(node_id)s) moved"
119+
msgstr ""
120+
121+
#: src/baserow/contrib/automation/nodes/node_types.py:176
113122
msgid "Branch"
114123
msgstr ""
115124

@@ -204,18 +213,18 @@ msgstr ""
204213
msgid "Widget \"%(widget_title)s\" (%(widget_id)s) deleted"
205214
msgstr ""
206215

207-
#: src/baserow/contrib/integrations/core/service_types.py:1083
216+
#: src/baserow/contrib/integrations/core/service_types.py:1103
208217
msgid "Branch taken"
209218
msgstr ""
210219

211-
#: src/baserow/contrib/integrations/core/service_types.py:1088
220+
#: src/baserow/contrib/integrations/core/service_types.py:1108
212221
msgid "Label"
213222
msgstr ""
214223

215-
#: src/baserow/contrib/integrations/core/service_types.py:1090
224+
#: src/baserow/contrib/integrations/core/service_types.py:1110
216225
msgid "The label of the branch that matched the condition."
217226
msgstr ""
218227

219-
#: src/baserow/contrib/integrations/core/service_types.py:1374
228+
#: src/baserow/contrib/integrations/core/service_types.py:1402
220229
msgid "Triggered at"
221230
msgstr ""

enterprise/backend/src/baserow_enterprise/api/assistant/serializers.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -142,22 +142,17 @@ class AiMessageSerializer(serializers.Serializer):
142142

143143
class AiThinkingSerializer(serializers.Serializer):
144144
type = serializers.CharField(default=AssistantMessageType.AI_THINKING)
145-
code = serializers.CharField(
146-
help_text=(
147-
"Thinking code. If empty, signals end of thinking. This is used to provide recurring "
148-
"messages that have a translation in the frontend (i.e. 'thinking', 'answering', etc.)"
149-
)
150-
)
151145
content = serializers.CharField(
152-
default="",
153-
allow_blank=True,
154-
help_text=(
155-
"A short description of what the AI is thinking about. It can be used to "
156-
"provide a dynamic message that don't have a translation in the frontend."
157-
),
146+
default="The AI is thinking...",
147+
help_text=("The message to show while the AI is thinking"),
158148
)
159149

160150

151+
class AiNavigationSerializer(serializers.Serializer):
152+
type = serializers.CharField(default=AssistantMessageType.AI_NAVIGATION)
153+
location = serializers.DictField(help_text=("The location to navigate to."))
154+
155+
161156
class AiErrorMessageSerializer(serializers.Serializer):
162157
type = serializers.CharField(default=AssistantMessageType.AI_ERROR)
163158
code = serializers.CharField(
@@ -185,6 +180,7 @@ class HumanMessageSerializer(serializers.Serializer):
185180
AssistantMessageType.HUMAN: HumanMessageSerializer,
186181
AssistantMessageType.AI_MESSAGE: AiMessageSerializer,
187182
AssistantMessageType.AI_THINKING: AiThinkingSerializer,
183+
AssistantMessageType.AI_NAVIGATION: AiNavigationSerializer,
188184
AssistantMessageType.AI_ERROR: AiErrorMessageSerializer,
189185
}
190186

enterprise/backend/src/baserow_enterprise/api/assistant/views.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ def post(self, request: Request, chat_uuid: str, data) -> StreamingHttpResponse:
161161

162162
handler = AssistantHandler()
163163
chat, _ = handler.get_or_create_chat(request.user, workspace, chat_uuid)
164+
165+
# Clearing the user websocket_id will make sure real-time updates are sent
166+
chat.user.web_socket_id = None
167+
# FIXME: As long as we don't allow users to change it, temporarily set the
168+
# timezone to the one provided in the UI context
169+
chat.user.profile.timezone = ui_context.timezone
170+
164171
assistant = handler.get_assistant(chat)
165172
human_message = HumanMessage(content=data["content"], ui_context=ui_context)
166173

enterprise/backend/src/baserow_enterprise/apps.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,14 +302,42 @@ def ready(self):
302302
notification_type_registry.register(TwoWaySyncUpdateFailedNotificationType())
303303
notification_type_registry.register(TwoWaySyncDeactivatedNotificationType())
304304

305+
from baserow_enterprise.assistant.tools import (
306+
CreateDatabaseToolType,
307+
CreateFieldsToolType,
308+
CreateTablesToolType,
309+
CreateViewFiltersToolType,
310+
CreateViewsToolType,
311+
GetRowsToolsToolType,
312+
GetTablesSchemaToolType,
313+
ListDatabasesToolType,
314+
ListRowsToolType,
315+
ListTablesToolType,
316+
ListViewsToolType,
317+
NavigationToolType,
318+
SearchDocsToolType,
319+
)
305320
from baserow_enterprise.assistant.tools.registries import (
306321
assistant_tool_registry,
307322
)
308-
from baserow_enterprise.assistant.tools.search_docs.tools import (
309-
SearchDocsToolType,
310-
)
311323

312324
assistant_tool_registry.register(SearchDocsToolType())
325+
assistant_tool_registry.register(NavigationToolType())
326+
327+
assistant_tool_registry.register(ListDatabasesToolType())
328+
assistant_tool_registry.register(CreateDatabaseToolType())
329+
assistant_tool_registry.register(ListTablesToolType())
330+
assistant_tool_registry.register(CreateTablesToolType())
331+
assistant_tool_registry.register(GetTablesSchemaToolType())
332+
assistant_tool_registry.register(CreateFieldsToolType())
333+
334+
assistant_tool_registry.register(ListRowsToolType())
335+
assistant_tool_registry.register(GetRowsToolsToolType())
336+
337+
assistant_tool_registry.register(ListViewsToolType())
338+
assistant_tool_registry.register(CreateViewsToolType())
339+
340+
assistant_tool_registry.register(CreateViewFiltersToolType())
313341

314342
# The signals must always be imported last because they use the registries
315343
# which need to be filled first.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import dspy
2+
3+
from .prompts import ASSISTANT_SYSTEM_PROMPT
4+
5+
6+
class ChatAdapter(dspy.ChatAdapter):
7+
def format_field_description(self, signature: type[dspy.Signature]) -> str:
8+
"""
9+
This is the first part of the prompt the LLM sees, so we prepend our custom
10+
system prompt to it to give it the personality and context of Baserow.
11+
"""
12+
13+
field_description = super().format_field_description(signature)
14+
return ASSISTANT_SYSTEM_PROMPT + "## TASK INSTRUCTIONS:\n\n" + field_description

0 commit comments

Comments
 (0)