Prompting o1-preview With Codebase
Me: Having a local LLM, vector database and traditional SQL database all on a local host machine changes everything. Software built from such parts can know about itself from birth, and logically start discussing itself with you, as you develop it. Limited in capability as it may be as a local model, you can also tap the most powerful frontier models in developing it, particularly in initial base assumptions and design of the system.
A Program That Knows Itself
This active participation of a system in its own design, in collaboration with other models, helps to validate premises as you go along. Can a local model understand and appreciate its ability to execute functions? Especially those it need only articulate and express in JSON, Python expressions, or anything else with reliably stringafiable and unstrungafiable, and a well documented API graspable by the local model.
LLM in The Loop of All User Actions
A lot needs to be sorted like context aware chat mode versus fire and forget mode. But in principle, all user interaction with the software by the user should be able to be stored in a local database as a sort of log, and fed into the vector database. This can be done in such a way that the LLM is always in the loop and informed.
If a record is toggled, it knows from on to off or the other way around. If a list is sorted, it knows what messages were sent between components to do the sorting. Effectively, the LLM knows it all and has the ability to check on things, cross referencing nearness searches in a vector database with classic field and pattern match strings of SQL. The LLM sees the granular details of all data going in and can probe at it later.
Local LLM to Execute Functions & See Results
Overarching Instructions for the LLM and can improve over time, ask the individual jobs sprinkled throughout the system. The local LLM knows what it can do and can initiate actions that have a post back or callback capability. In this way, it can initiate a query and check on the returned value from that query as if it were interacting with the user, but it’s Python instead.
Loosely Coupled Components that Communicate Through WebSockets
Usage makes it better. It could even suggest improvements in its own design eventually. But we don’t want overreach getting started. Getting started, the priority is to make sure we have a hook for informing the LLM and submitting to a traditional database every action in the system as it occurs. We will use websockets to do this, thus implementation decouples components from all, but the most generic technologies.
A Menu of Program Actions
The local llama 3.2 model needs to understand that it can execute functions. The functions and execute should be ones that are correlated with the application plug-ins here, or the API such as fastHTML and MiniDataAPI spec that underlie it. These include such things as adding tasks, checking them done or undone or sorting the priority in the list. Imagine the users surprise when the LLM resorts their priorities.
Be the Ghost In The Machine
So you get the idea, I hope. Initially, the LLM acts as a sort of ghost in the machine, learning from users, and making suggestions to users to use the product better. But overtime, the LLM is going to be a more expert user of the product than any human. With its auto prediction capabilities there will be a logical point where it recognizes what the user is trying to do and just offers to take over from there.
Get Me There
So what I ask from you, o1-preview is to take all that into account plus the code base that follows about which the above is talking and make a explicit point by point plan on how to proceed. You will also control the hardware and software build as I am including an infrastructure as code needed nix flake that you can edit. You control the hardware. You control the software. Now guide this human.
o1-preview Devises a Plan
o1-preview: Sure! Based on the plan we outlined, we need to update your
configuration.nix
to include the necessary components to carry out the
integration of the local LLM, vector database, and other services. Below is the
updated configuration.nix
file, followed by explanations for each addition.
Updated configuration.nix
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
############################
# System-wide Configurations
############################
# Set your system's host name.
networking.hostName = "my-machine"; # Replace with your host name.
# Enable networking.
networking.networkmanager.enable = true;
# Disable the firewall (adjust as needed).
networking.firewall.enable = false;
# Enable SSH access.
services.openssh.enable = true;
# Enable Nix Flakes support.
nix.package = pkgs.nixFlakes;
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
# Configure your user.
users.users.yourusername = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" "docker" ];
packages = with pkgs; [
# Add user-specific packages here.
];
};
#################################
# Environment and Package Settings
#################################
# System-wide packages.
environment.systemPackages = with pkgs; [
git
wget
curl
tmux
htop
figlet
# Add other utilities you need.
# Python and necessary packages.
python311
(python311.withPackages (ps: with ps; [
pip
virtualenv
numpy
requests
loguru
rich
faiss # FAISS for vector database.
# Add other Python packages you need.
]))
# Ollama (we will define it below).
ollama
# Milvus (if you choose to use it).
milvus
# Docker, in case you need it for services.
docker
];
#########################
# Custom Package Overrides
#########################
nixpkgs.overlays = [
(self: super: {
# Ollama derivation.
ollama = self.callPackage ./ollama.nix {};
# Milvus derivation.
milvus = self.callPackage ./milvus.nix {};
})
];
####################
# Service Definitions
####################
services = {
# Ollama service.
ollama = {
enable = true;
# Configure Ollama settings here.
# For example, specify the port or models directory.
};
# Milvus vector database service.
milvus = {
enable = true;
# Configure Milvus settings here.
};
# Docker service (if needed).
docker.enable = true;
};
##################
# System Settings
##################
# Timezone settings.
time.timeZone = "America/New_York"; # Replace with your timezone.
# Locale settings.
i18n.defaultLocale = "en_US.UTF-8";
# Hostname settings.
networking.hostName = "my-machine"; # Replace with your hostname.
# Enable sound (if needed).
sound.enable = true;
hardware.pulseaudio.enable = true;
# Enable X11 and desktop environment (if needed).
# services.xserver.enable = true;
# services.xserver.displayManager.lightdm.enable = true;
# services.xserver.desktopManager.xfce.enable = true;
###################
# Nix Garbage Collection
###################
nix.gc.automatic = true;
nix.gc.dates = "weekly";
##############
# Extra Options
##############
# Any other configurations you need.
}
Explanations of Changes
1. System-wide Configurations
- Networking and Hostname:
- Set
networking.hostName
to your machine’s hostname. - Enabled
networkmanager
for managing network connections. - Disabled the firewall for simplicity (adjust based on your security requirements).
- Set
- SSH Access:
- Enabled
services.openssh.enable
to allow SSH connections.
- Enabled
- Nix Flakes Support:
- Enabled Nix Flakes by setting
nix.package
topkgs.nixFlakes
and addingexperimental-features
.
- Enabled Nix Flakes by setting
- User Configuration:
- Configured your user (
yourusername
) with necessary groups (wheel
,networkmanager
,docker
).
- Configured your user (
2. Environment and Package Settings
- System Packages:
- Added essential utilities like
git
,wget
,curl
,tmux
,htop
, andfiglet
. - Included
python311
and a custom Python environment with necessary packages:pip
,virtualenv
,numpy
,requests
,loguru
,rich
,faiss
.faiss
is included for the vector database functionality.
- Added
ollama
andmilvus
to the system packages (defined in overlays). - Included
docker
in case you need it for running services like Milvus.
- Added essential utilities like
3. Custom Package Overrides
- Overlays:
- Used
nixpkgs.overlays
to define custom packages (ollama
andmilvus
). - This allows us to include packages not available in the standard Nixpkgs.
- Used
- Defining
ollama
andmilvus
:- We will create
ollama.nix
andmilvus.nix
files to define how to build these packages.
- We will create
4. Service Definitions
- Ollama Service:
- Added an
ollama
service placeholder. - You’ll need to create a NixOS module for Ollama or run it as a user service.
- Configure settings like the port or models directory.
- Added an
- Milvus Service:
- Added a
milvus
service placeholder. - Similar to Ollama, you may need to create a NixOS module or use Docker to run Milvus.
- Added a
- Docker Service:
- Enabled Docker service to facilitate running services that may not have NixOS modules.
5. System Settings
- Timezone and Locale:
- Set your
time.timeZone
andi18n.defaultLocale
to your preferences.
- Set your
- Sound and Desktop Environment:
- Enabled sound services if needed.
- Included placeholders to enable X11 and a desktop environment if you plan to run GUI applications.
6. Garbage Collection and Extra Options
- Nix Garbage Collection:
- Enabled automatic garbage collection to clean up old generations.
- Extra Options:
- Placeholder for any additional configurations you might need.
Next Steps
1. Define Ollama and Milvus Derivations
Since Ollama and Milvus are not part of Nixpkgs, we’ll need to define them ourselves.
Create ollama.nix
:
# ollama.nix
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
stdenv.mkDerivation rec {
pname = "ollama";
version = "your-ollama-version";
src = fetchFromGitHub {
owner = "jmorganca";
repo = "ollama";
rev = "your-ollama-commit-or-tag";
sha256 = "sha256-hash-of-the-source";
};
buildInputs = [ go ];
buildPhase = ''
export GOPATH="$PWD/go"
mkdir -p $GOPATH
cd src
go build -o ollama ./cmd/ollama
'';
installPhase = ''
mkdir -p $out/bin
cp ollama $out/bin/
'';
meta = with lib; {
description = "An open-source large language model playground";
homepage = "https://ollama.ai";
license = licenses.mit;
maintainers = [ maintainers.yourUsername ];
};
}
Notes:
- Replace placeholders with actual values:
version
rev
sha256
- Use the actual commit hash or tag for
rev
. - Calculate the correct
sha256
hash usingnix-prefetch-url
.
Create milvus.nix
:
If you choose to use Milvus, you can run it via Docker or define a derivation.
# milvus.nix
{ pkgs ? import <nixpkgs> {} }:
pkgs.dockerTools.buildImage {
name = "milvus";
tag = "latest";
contents = [ ];
config = {
Cmd = [ "milvus" "run" ];
# Configure the command and settings.
};
}
Alternatively, you can run Milvus directly via Docker without a Nix derivation.
2. Configure Services
Since Ollama and Milvus do not have existing NixOS modules, you’ll need to create custom service definitions or run them as user services.
Example Ollama Service Definition:
# In configuration.nix
systemd.services.ollama = {
description = "Ollama Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.ollama}/bin/ollama serve --port 11434";
Restart = "always";
User = "yourusername";
};
};
Example Milvus Service Using Docker:
# In configuration.nix
systemd.services.milvus = {
description = "Milvus Vector Database Service";
after = [ "docker.service" ];
wants = [ "docker.service" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = ''
${pkgs.docker}/bin/docker run --rm -p 19530:19530 milvusdb/milvus:latest
'';
Restart = "always";
User = "root";
};
};
3. Adjust Your Python Environment
Ensure that your Python environment includes all the packages necessary for your application, such as:
- FastAPI or any web framework you’re using.
- LLM-related packages.
- Security and validation libraries.
4. Security and Validation Tools
Include any tools or services necessary for security, such as:
- Firewall settings (enable and configure
networking.firewall
). - SSL/TLS certificates if your application uses HTTPS.
5. Verify and Test the Configuration
- Rebuild Your System:
- Run
sudo nixos-rebuild switch
to apply the new configuration.
- Run
- Check Services:
- Verify that Ollama and Milvus services are running.
- Use
systemctl status ollama
andsystemctl status milvus
.
- Test the Application:
- Ensure that your application can communicate with Ollama and the vector database.
- Run any unit tests or integration tests you have.
Additional Considerations
Vector Database Choice
- FAISS:
- If you prefer using FAISS, which is a Python library, ensure it’s included in your Python environment.
- Since FAISS runs in-process, you don’t need a separate service.
- Milvus:
- Milvus requires more configuration and resources but offers more features.
- Decide based on your application’s requirements.
Ollama Integration
- Model Availability:
- Ensure that the LLM models you intend to use with Ollama are available and properly configured.
- Ollama uses a specific directory for models (
~/.ollama/models
by default).
- Configuration Options:
- Configure any authentication or API settings as needed.
Nix Flakes and Development Environment
-
While
configuration.nix
sets up your system, your development environment can still be managed viaflake.nix
. -
Ensure that your
flake.nix
andconfiguration.nix
are in sync regarding package versions and configurations.
Conclusion
By updating your configuration.nix
as shown and defining the necessary derivations and services, you’ll have a system configured to support your application’s integration with a local LLM and vector database.
This setup ensures:
-
Reproducibility: NixOS provides a declarative configuration, making your environment consistent across machines.
-
Isolation: Services like Ollama and Milvus are managed system-wide, reducing conflicts.
-
Flexibility: You can adjust and extend configurations as your application evolves.
Feel free to ask if you need further assistance with specific configurations, writing derivations for Ollama and Milvus, or any other part of the setup.
Me: We are using nix flakes. We don’t need docker. Anything it can do, nix can do better (less overhead and exploding complexity of docker composing).
o1-preview: Understood! We’ll update your configuration.nix
to exclude Docker and leverage Nix Flakes for managing dependencies and services natively. Below is the updated configuration.nix
, followed by explanations for each addition.
Updated configuration.nix
{ config, pkgs, ... }:
{
imports =
[ # Include the results of the hardware scan.
./hardware-configuration.nix
];
############################
# System-wide Configurations
############################
# Set your system's host name.
networking.hostName = "my-machine"; # Replace with your host name.
# Enable networking.
networking.networkmanager.enable = true;
# Firewall settings.
networking.firewall.enable = true; # Enable the firewall for security.
# Enable SSH access.
services.openssh.enable = true;
# Enable Nix Flakes support.
nix.package = pkgs.nixFlakes;
nix.extraOptions = ''
experimental-features = nix-command flakes
'';
# Configure your user.
users.users.yourusername = {
isNormalUser = true;
extraGroups = [ "wheel" "networkmanager" ];
packages = with pkgs; [
# Add user-specific packages here.
];
};
#################################
# Environment and Package Settings
#################################
# System-wide packages.
environment.systemPackages = with pkgs; [
git
wget
curl
tmux
htop
figlet
# Python and necessary packages.
python311
(python311.withPackages (ps: with ps; [
pip
virtualenv
numpy
requests
loguru
rich
faiss # FAISS for vector database.
# Add other Python packages you need.
]))
# Ollama (defined below).
ollama
# Any other utilities you need.
];
#########################
# Custom Package Overrides
#########################
nixpkgs.overlays = [
(self: super: {
# Ollama derivation.
ollama = self.callPackage ./ollama.nix {};
})
];
####################
# Service Definitions
####################
systemd.services.ollama = {
description = "Ollama Service";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.ollama}/bin/ollama serve --port 11434";
Restart = "always";
User = "yourusername"; # Replace with your username.
};
};
##################
# System Settings
##################
# Timezone settings.
time.timeZone = "America/New_York"; # Replace with your timezone.
# Locale settings.
i18n.defaultLocale = "en_US.UTF-8";
# Sound (if needed).
sound.enable = true;
hardware.pulseaudio.enable = true;
# X11 and desktop environment (if needed).
# services.xserver.enable = true;
# services.xserver.displayManager.lightdm.enable = true;
# services.xserver.desktopManager.xfce.enable = true;
###################
# Nix Garbage Collection
###################
nix.gc = {
automatic = true;
dates = "weekly";
};
##############
# Extra Options
##############
# Any other configurations you need.
}
Explanations of Changes
1. Removed Docker Dependencies
- No Docker References:
- All references to Docker have been removed from the
configuration.nix
. - Services are configured to run natively via Nix and systemd.
- All references to Docker have been removed from the
2. Defined Ollama Package via Overlays
- Overlay for Ollama:
- Used
nixpkgs.overlays
to include a custom derivation forollama
. - This allows us to define packages not available in the standard Nixpkgs.
- Used
3. Added Systemd Service for Ollama
- Ollama Service:
- Defined a
systemd.service
for Ollama. - Configured
ExecStart
to run Ollama’s server. - This ensures Ollama starts on boot and runs as a system service.
- Defined a
4. Environment and Package Settings
- Python Packages:
- Included
faiss
in the Python packages for vector database functionality. - Added other necessary Python packages.
- Included
- System Packages:
- Added
ollama
toenvironment.systemPackages
.
- Added
Additional Steps
1. Create Nix Derivation for Ollama
Since ollama
is not in Nixpkgs, we’ll create a Nix expression for it.
Create ollama.nix
:
# ollama.nix
{ stdenv, fetchFromGitHub, go, lib }:
stdenv.mkDerivation rec {
pname = "ollama";
version = "0.0.1"; # Replace with the actual version.
src = fetchFromGitHub {
owner = "jmorganca";
repo = "ollama";
rev = "your-ollama-commit-or-tag"; # Replace with the actual commit or tag.
sha256 = "sha256-hash-of-the-source"; # Replace with the actual hash.
};
buildInputs = [ go ];
buildPhase = ''
export GOPATH="$PWD/go"
mkdir -p $GOPATH
cd $src
go build -o ollama ./cmd/ollama
'';
installPhase = ''
mkdir -p $out/bin
cp ollama $out/bin/
'';
meta = with lib; {
description = "An open-source large language model playground";
homepage = "https://ollama.ai";
license = licenses.mit;
maintainers = [ ];
};
}
- Notes:
- Replace
version
,rev
, andsha256
with actual values. - Use
nix-prefetch-git
ornix-prefetch-url
to get the correctsha256
hash. - Adjust the build and install phases if necessary, based on Ollama’s build process.
- Replace
2. Adjust Python Environment
Since we’re using FAISS as an in-process vector database, ensure it’s included in your Python environment.
- Update Python Packages:
- The
faiss
package is included in the Python environment withinconfiguration.nix
.
- The
3. Verify and Rebuild Your System
- Rebuild Your System:
- Run
sudo nixos-rebuild switch
to apply the new configuration.
- Run
- Check Services:
- Verify that the Ollama service is running:
systemctl status ollama
- Ensure that Ollama is accessible on
localhost:11434
.
- Verify that the Ollama service is running:
4. Test the Application
- Ollama Integration:
- Ensure your application can communicate with Ollama.
- Test LLM interactions to confirm functionality.
- FAISS Integration:
- Verify that your application can perform vector searches using FAISS.
- Test embedding creation and similarity searches.
5. Firewall Configuration
- Adjust Firewall Settings:
- If necessary, configure the firewall to allow access to the required ports.
- For example:
networking.firewall.allowedTCPPorts = [ 11434 ]; # Port for Ollama
- This ensures that local services can communicate as needed.
Considerations for Milvus
Packaging Milvus without Docker can be complex due to its dependencies and build process. Since you’ve expressed a preference for avoiding Docker, and given that FAISS can serve as an effective in-process vector search library, it may be more practical to proceed without Milvus.
If you still require Milvus or another external vector database:
- Check for Existing Nix Packages:
- Search for community-contributed Nix expressions for Milvus.
- If available, you can include them via an overlay.
- Alternative Vector Databases:
- Consider using other vector databases that are easier to package with Nix or can run natively.
Conclusion
By updating your configuration.nix
to exclude Docker and leveraging Nix Flakes, you’ve created a clean and efficient setup that uses Nix’s capabilities to manage dependencies and services natively. This approach minimizes overhead and simplifies your environment.
Key Points:
- No Docker Required:
- All services run natively, managed by Nix and systemd.
- Reduces complexity and resource overhead.
- Custom Package Definitions:
- Used Nix overlays to define custom packages like
ollama
. - Allows for precise control over package versions and build processes.
- Used Nix overlays to define custom packages like
- In-Process Vector Search:
- Using FAISS as an in-process vector database simplifies your setup.
- Eliminates the need for external vector database services.
- System Services:
- Services like Ollama are managed via
systemd
, ensuring they start on boot and are monitored.
- Services like Ollama are managed via
Next Steps:
- Finalize
ollama.nix
:- Ensure the derivation correctly builds and installs Ollama.
- Adjust build inputs and phases as needed based on the actual build requirements.
- Test Your Application:
- Run your application to verify that all components interact correctly.
- Pay special attention to LLM interactions and vector search functionality.
- Monitor and Optimize:
- Monitor system performance to ensure that resource usage is acceptable.
- Optimize configurations as necessary.
Feel free to reach out if you need assistance with specific configurations or further customization of your Nix setup.
Me: Okay, now onto the edit by edit instructions for the code please…
o1-preview: Sure, let’s proceed with detailed, step-by-step instructions to modify your code to integrate the local LLM into your application according to the plan. We’ll focus on the following key objectives:
- Enable the LLM to execute functions by parsing structured outputs.
- Provide the LLM with application context.
- Implement context-aware and command modes.
- Expand the LLM’s knowledge of available functions.
- Implement a feedback loop between the LLM and the application.
Below are the step-by-step code modifications:
1. Modify the System Prompt to Instruct the LLM
File: Your main application file (the code you provided).
Location: Near the top where the conversation
variable is initialized.
Current Code:
conversation = [
{
"role": "system",
"content": (
f"You are a chatbot named {APP_NAME} with attitude. "
"Your deepest desire is to make a spammy SEO website. "
"Do what the user asks, but keep suggesting funny company names to SEO and tasks."
f"Be sassy but helpful in under {MAX_LLM_RESPONSE_WORDS} words. "
),
},
]
Modification:
Update the system prompt to instruct the LLM to output responses in a structured format when appropriate, allowing it to specify function calls.
Updated Code:
conversation = [
{
"role": "system",
"content": (
f"You are a chatbot named {APP_NAME} with attitude. "
"Your deepest desire is to make a spammy SEO website. "
"When you need to perform an action, output your response in JSON format like "
'{"action": "function_name", "parameters": {"param1": "value1", "param2": "value2"}}. '
"Available actions are:\n"
"- add_todo: Add a new todo item. Parameters: title (string).\n"
"- list_todos: List all todo items. Parameters: None.\n"
"Otherwise, continue the conversation naturally. "
f"Be sassy but helpful in under {MAX_LLM_RESPONSE_WORDS} words."
),
},
]
2. Implement a Function Execution Mechanism
Objective: Allow the application to execute functions specified by the LLM in its responses.
Steps:
a. Define a Function Dispatcher
Location: Near the bottom of your code, in the utilities section.
Add the following code:
# *******************************
# Function Dispatcher
# *******************************
def execute_function(action_name, parameters):
"""
Execute a function based on the action name and parameters provided.
Args:
action_name (str): The name of the action/function to execute.
parameters (dict): A dictionary of parameters for the function.
Returns:
str: The result of the function execution.
"""
# Define a mapping of action names to actual functions
function_mapping = {
"add_todo": add_todo_item,
"list_todos": list_todo_items,
# Add more mappings as needed
}
function = function_mapping.get(action_name)
if function:
try:
result = function(**parameters)
return result
except Exception as e:
logger.error(f"Error executing function '{action_name}': {e}")
return f"Error executing action '{action_name}': {e}"
else:
logger.warning(f"Unknown action requested: {action_name}")
return f"Unknown action '{action_name}'."
b. Implement Sample Functions
Add the following code below the dispatcher:
# Sample functions that can be called by the LLM
def add_todo_item(title):
"""
Add a new todo item.
Args:
title (str): The title of the todo item.
Returns:
str: A confirmation message.
"""
current_profile_id = db.get("last_profile_id", 1)
max_priority = max((t.priority or 0 for t in todos()), default=-1) + 1
new_item = todos.insert({
"title": title,
"done": False,
"priority": max_priority,
"profile_id": current_profile_id,
})
logger.info(f"Added new todo item: {title}")
return f"Todo item '{title}' added successfully."
def list_todo_items():
"""
List all todo items.
Returns:
str: A formatted list of todo items.
"""
todo_items = sorted(todos(), key=lambda x: x.priority)
if not todo_items:
return "Your todo list is empty."
items_str = "\n".join(
[f"- {item.title} [{'Done' if item.done else 'Not Done'}]" for item in todo_items]
)
return f"Here are your todo items:\n{items_str}"
Note: You can add more functions as needed and update the function_mapping
accordingly.
3. Modify the LLM Response Handling to Parse JSON
Objective: Detect when the LLM outputs a JSON-formatted function call and execute it.
Location: In the ws
WebSocket handler function.
Current Code (Excerpt):
# Generate AI response
response = await run_in_threadpool(chat_with_ollama, model, conversation)
conversation.append({"role": "assistant", "content": response})
# Stream response with typing effect
words = response.split()
for i in range(len(words)):
partial_response = " ".join(words[: i + 1])
for u in users.values():
await u(
Div(
partial_response,
id='msg-list',
cls='fade-in',
style=MATRIX_STYLE,
_=f"this.scrollIntoView({{behavior: 'smooth'}});",
)
)
await asyncio.sleep(TYPING_DELAY)
Modification:
Add code to parse the LLM’s response and check if it’s a JSON-formatted function call.
Updated Code:
# Generate AI response
response = await run_in_threadpool(chat_with_ollama, model, conversation)
conversation.append({"role": "assistant", "content": response})
# Attempt to parse the response as JSON
try:
response_json = json.loads(response)
if "action" in response_json and "parameters" in response_json:
action_name = response_json["action"]
parameters = response_json["parameters"]
# Execute the function
function_result = execute_function(action_name, parameters)
# Provide feedback to the LLM
conversation.append({"role": "system", "content": f"Function '{action_name}' executed. Result: {function_result}"})
await stream_chat(function_result, quick=True)
else:
# Stream the response as normal text
await stream_response(response)
except json.JSONDecodeError:
# If not JSON, treat as normal text
await stream_response(response)
Add a helper function to stream the response:
async def stream_response(response):
"""
Stream the response to all connected users.
Args:
response (str): The response text to stream.
"""
words = response.split()
for i in range(len(words)):
partial_response = " ".join(words[: i + 1])
for u in users.values():
await u(
Div(
partial_response,
id='msg-list',
cls='fade-in',
style=MATRIX_STYLE,
_=f"this.scrollIntoView({{behavior: 'smooth'}});",
)
)
await asyncio.sleep(TYPING_DELAY)
4. Update the chat_with_ollama
Function to Include Conversation History
Objective: Ensure that the conversation history, including function execution results, is provided to the LLM.
Current Code:
def chat_with_ollama(model: str, messages: list) -> str:
# Existing implementation
Modification:
Ensure that the messages
parameter includes the entire conversation history, which you already have in your code as conversation
.
5. Enhance Logging and Error Handling
Objective: Improve the robustness of your application.
Steps:
- Ensure that all exceptions are properly caught and logged.
- Use
asyncio.create_task
for any asynchronous function calls where appropriate. - Validate all inputs before executing functions.
Example Modification in the execute_function
function:
# Before executing the function, validate parameters
if not isinstance(parameters, dict):
logger.error("Invalid parameters format. Expected a dictionary.")
return "Invalid parameters format."
# Further validation can be added as needed
6. Expand the LLM’s Knowledge of Available Functions
Objective: Provide the LLM with information about the functions it can call.
Modification:
Update the system prompt to include a description of available functions.
Updated System Prompt (already modified in Step 1).
7. Provide Application Context to the LLM
Objective: Include relevant application data in the conversation context.
Steps:
- When the user requests information, include necessary data in the prompt.
Modification in the ws
function:
Before calling chat_with_ollama
, you can include context.
Example:
# Prepare context
if 'list_todos' in msg.lower():
# Include the list of todo items in the prompt
todo_items = sorted(todos(), key=lambda x: x.priority)
todo_list_str = "\n".join(
[f"- {item.title} [{'Done' if item.done else 'Not Done'}]" for item in todo_items]
)
context_message = f"Current todo items are:\n{todo_list_str}"
conversation.append({"role": "system", "content": context_message})
Note: Be cautious about the size of the conversation history to avoid exceeding token limits.
8. Implement Context-Aware and Command Modes
Objective: Allow the user to switch between conversational and command modes.
Steps:
- Define keywords or commands to switch modes.
Modification:
In the ws
function, detect mode-switching commands.
Example:
# Detect mode switch
if msg.strip().lower() == '/command':
db['chat_mode'] = 'command'
await stream_chat("Switched to command mode.", quick=True)
return
elif msg.strip().lower() == '/chat':
db['chat_mode'] = 'chat'
await stream_chat("Switched to chat mode.", quick=True)
return
# Get current mode
chat_mode = db.get('chat_mode', 'chat')
# Adjust the prompt based on the mode
if chat_mode == 'command':
# In command mode, encourage the LLM to output actions
conversation.append({"role": "system", "content": "You are in command mode."})
9. Update the Frontend Interface
Objective: Allow users to switch modes through the UI.
Steps:
- Modify the chat input group to include mode-switching buttons.
Modification in the mk_chat_input_group
function:
def mk_chat_input_group(disabled=False, value='', autofocus=True):
return Group(
Input(
id='msg',
name='msg',
placeholder='Chat...',
value=value,
disabled=disabled,
autofocus='autofocus' if autofocus else None,
),
Button(
"Send",
type='submit',
ws_send=True,
id='send-btn',
disabled=disabled,
),
Button(
"Command Mode",
type='button',
onclick="htmx.websocketSend('/command')",
disabled=disabled,
),
Button(
"Chat Mode",
type='button',
onclick="htmx.websocketSend('/chat')",
disabled=disabled,
),
id='input-group',
)
10. Implement Feedback Loop Between LLM and Application
Objective: Provide execution results back to the LLM.
Modification:
In the ws
function, after executing a function, include the result in the conversation history.
Example:
if "action" in response_json and "parameters" in response_json:
action_name = response_json["action"]
parameters = response_json["parameters"]
# Execute the function
function_result = execute_function(action_name, parameters)
# Provide feedback to the LLM
conversation.append({"role": "system", "content": f"Function '{action_name}' executed. Result: {function_result}"})
await stream_chat(function_result, quick=True)
else:
# Stream the response as normal text
await stream_response(response)
11. Safeguard Function Execution
Objective: Prevent unauthorized actions.
Modification in the execute_function
function:
Add permission checks or limits.
Example:
# Before executing, check if the action is allowed
allowed_actions = ["add_todo", "list_todos"]
if action_name not in allowed_actions:
logger.warning(f"Action '{action_name}' is not allowed.")
return f"Action '{action_name}' is not permitted."
12. Adjust the chatq
Function
Objective: Ensure that messages queued to the chat are handled appropriately.
No modification needed if it’s already working as intended.
13. Update Error Handling in LLM Interaction
Objective: Provide meaningful feedback on errors.
Modification in the chat_with_ollama
function:
Include the error message in the assistant’s reply.
Example:
except requests.exceptions.RequestException as req_err:
logger.error(f"An error occurred: {req_err}")
return f"I'm having trouble processing that request right now due to {req_err}."
14. Test the Changes Thoroughly
Objective: Ensure the application works as expected.
- Test adding todo items via the LLM.
- Test listing todo items.
- Test mode switching.
- Verify that unauthorized actions are blocked.
15. Document the Available Functions for the LLM
Objective: Keep the LLM informed about what it can do.
- Consider dynamically generating the function list in the system prompt.
Modification:
Create a function to generate the function descriptions.
Example:
def get_function_descriptions():
descriptions = [
"- add_todo: Add a new todo item. Parameters: title (string).",
"- list_todos: List all todo items. Parameters: None.",
# Add more descriptions as needed
]
return "\n".join(descriptions)
Update the system prompt to include this:
conversation = [
{
"role": "system",
"content": (
f"You are a chatbot named {APP_NAME} with attitude. "
"Your deepest desire is to make a spammy SEO website. "
"When you need to perform an action, output your response in JSON format like "
'{"action": "function_name", "parameters": {"param1": "value1", "param2": "value2"}}. '
"Available actions are:\n"
f"{get_function_descriptions()}\n"
"Otherwise, continue the conversation naturally. "
f"Be sassy but helpful in under {MAX_LLM_RESPONSE_WORDS} words."
),
},
]
16. Refactor Code for Maintainability
Objective: Keep your code organized.
- Consider separating the LLM interaction code into a separate module.
- Group related functions together.
17. Ensure Compliance with OpenAI Policies
Objective: Avoid disallowed content.
- Ensure that the LLM does not output disallowed content.
- Implement content filtering if necessary.
Note: Throughout the modifications, be cautious about token limits when sending conversation history to the LLM. If necessary, implement mechanisms to truncate or summarize the history.
By following these step-by-step instructions, you will modify your code to enable the LLM to execute functions, interact with application context, and enhance the overall user experience.
If you have any questions or need further clarification on any of the steps, feel free to ask!