Hi there! I'm Shrijith Venkatrama, the founder of Hexmos. Right now, I’m building LiveAPI, a super-convenient tool that simplifies engineering workflows by generating awesome API docs from your code in minutes.
In this tutorial series, I am on a journey to build for myself DBChat - a simple tool for using AI chat to explore and evolve databases.
See previous posts to get more context:
- Building DBChat - Explore and Evolve Your DB with Simple Chat (Part 1)
- DBChat: Getting a Toy REPL Going in Golang (Part 2)
- DBChat Part 3 - Configure , Connect & Dump Databases
- Chat With Your DB via DBChat & Gemini (Part 4)
- The Language Server Protocol - Building DBChat (Part 5)
- Making DBChat VSCode Extension - Ping Pong With LSP Backend (Part 6)
- Starting a VSCode Extension UI For DBChat (Part 7)
- Manage TOML Configuration From VSCode Extension - DBChat Part 8
Some screenshots of the working version
The default view for DBChat will look like this:
After selecting a database - we get a chat view:
Now, we will try to get "gmail users" vs "non-gmail users" with the simplest of prompts/requests:
The query is perfect. When I do exec 1
I get exactly the result expected in a table format.
Similar logic works for "get all non-gmail users" query as well.
A More Detailed View Into the DBChat Updates
This code handles communication with an LSP client, likely a language server protocol client in a code editor. Here's a breakdown:
1. Setting the Stage: Configuration and Initialization
- Loading the Config: The code starts by reading the configuration file. This file probably holds settings like database connection strings.
-
Creating the Query Handler: It then sets up the
queryHandler
, which is likely responsible for understanding and executing user requests. -
Building the Handler: Finally, it creates the
DBChatHandler
, a core component that manages the configuration, query handler, and potentially the database connection.
config, err := utils.LoadConfig()
if err != nil {
log.Printf("Warning: Could not load config: %v", err)
config = &utils.Config{Connections: make(map[string]string)}
}
queryHandler, err := query.NewHandler(config.LLM.GeminiKey)
if err != nil {
log.Printf("Warning: Could not create query handler: %v", err)
}
handler := &DBChatHandler{
config: config,
queryHandler: queryHandler,
}
2. LSP Communication: The Heartbeat
- The LSP Loop: The program enters a continuous loop, constantly listening for messages from the LSP client.
-
Reading Messages: It carefully reads messages from the client, paying close attention to the
Content-Length
header to know how much data to expect. - Parsing Messages: Once received, the raw data is transformed into a structured JSON-RPC message, making it easier to understand.
for {
// Read Content-Length header
// ... (code for reading header) ...
// Read message body
// ... (code for reading message body) ...
// Parse JSON-RPC message
var msg JSONRPCMessage
if err := json.Unmarshal(body, &msg); err != nil {
// ... (handle parsing error) ...
}
}
3. Handling Requests: What to Do?
- "ping" Requests: Simple! The program responds with a "pong" to acknowledge the client's ping.
-
"chat" Requests: This is where the action happens.
- The code extracts the user's chat message.
- It hands off this message to the
Eval
method within theDBChatHandler
. This method likely interacts with the database and thequeryHandler
to process the request. - The result of the processing is then packaged into a response and sent back to the client.
switch msg.Method {
case "ping":
response.Result = "pong"
case "chat":
var params struct {
Message string `json:"message"`
}
if err := json.Unmarshal(msg.Params, ¶ms); err != nil {
// ... (handle invalid params) ...
}
log.Printf("Processing chat message: %q", params.Message)
result := handler.Eval(params.Message)
log.Printf("Eval result: %q", result)
response.Result = map[string]interface{}{
"message": result,
}
// ... (handle other methods) ...
}
4. Error Handling and Responses
- Catching Errors: If anything goes wrong – like problems parsing the message or issues with the database – the program gracefully handles the error and sends an appropriate error message back to the client.
- Sending Responses: Finally, the program crafts the response message in the correct JSON-RPC format and sends it back to the client.
if err := writeResponse(response); err != nil {
log.Printf("Error writing response: %v", err)
}
Conclusion
DBchat now is able to manage database connections, focus on any database, allow making queries in natural languages and finally get good responses in table format based on database contents.
Top comments (0)