Unai is a chat application that builds responses from Python Skills instead of using an LLM.
When a message arrives, Unai tries each Skill in order (defined by priority.json), calls the first Skill whose match() returns True, and then runs that Skill's respond() function. If nothing matches, Unai returns a fixed fallback message.
- Features
- Demo
- Quick Start
- Installation
- How It Works
- Built-in Skills
- Writing a Skill
- API Reference
- Configuration
- Development
- License
- 🔧 Skill-based response routing - Modular Python Skills handle different query types
- 💬 Flask-based web chat UI - Clean, responsive interface
- 🗄️ SQLite-backed session storage - Persistent conversation history
- 🌳 Branching conversation history
- Regenerate a turn to add a new branch
- Edit a user message to truncate later turns and create a new branch
- Switch between branches
- ⚡ Real-time progress - SSE-based progress updates while Skills are being selected
- 🎯 Early response selection - Select responses as soon as they're ready
- 📊 Response metadata - Token count, elapsed time, and tokens/sec
- 🔌 Skills management - Reorder, enable/disable, import/export Skills as ZIP files
- ⌨️ Slash commands -
/helpand/help <skill>for quick assistance
Windows (recommended):
git clone https://github.com/PixelNest256/Unai.git
cd Unai
start.batmacOS / Linux:
git clone https://github.com/PixelNest256/Unai.git
cd Unai
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.pyThen open http://localhost:5000 in your browser.
- Python 3.10 or newer
pip
git clone https://github.com/PixelNest256/Unai.git
cd unai
start.batstart.bat creates the virtual environment (if needed), installs dependencies, and starts the app in one step. It automatically detects pyenv Python 3.12.0 or falls back to the system Python.
git clone https://github.com/PixelNest256/Unai.git
cd unai
python -m venv .venvActivate the virtual environment:
# PowerShell
.\.venv\Scripts\Activate.ps1:: Command Prompt
.venv\Scripts\activate.batInstall dependencies and start:
pip install -r requirements.txt
python app.pygit clone https://github.com/PixelNest256/Unai.git
cd unai
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python app.pyUnai's processing flow is straightforward:
- Check whether the input is a slash command
- Try each Skill in
skills/priority.jsonorder - Call
respond()on the first Skill whosematch()returnsTrue - Normalize the response into a shared result format and return it to the UI
In the web UI, /api/chat/sse streams Skill-selection progress so the frontend can show matching and responding states. The response text can be animated token by token on the client side.
unai/
├── app.py # Flask web server
├── unai_core.py # Core processing logic
├── requirements.txt # Python dependencies
├── sessions.db # SQLite database (auto-created)
├── settings.json # App settings (auto-created)
├── static/ # CSS, JavaScript, assets
├── templates/ # HTML templates
└── skills/ # Skill modules
├── priority.json # Skill execution order
├── greeting/ # Built-in skills...
├── calc/
├── ddgs/
└── ...
| Skill | Purpose | Implementation |
|---|---|---|
greeting |
Greetings and small talk | Rule-based matching with Levenshtein distance |
calc |
Mathematical calculations | Safe AST-based evaluation; uses SymPy for expand, factor, solve |
wikipedia |
Wikipedia summaries | Uses English Wikipedia summary API |
ddgs |
Search summaries | Summarizes the first DuckDuckGo search result |
ddgs_chatbot |
Conversational search | Interactive chat-based search with DuckDuckGo |
joke |
Random jokes | Returns a random joke from a predefined list |
valves_test |
Development sample | Displays saved valves values for Skill |
The /skills page supports:
- 🔍 Searching Skills
↔️ Drag-and-drop reordering- ✅ Enabling and disabling Skills
- 📥 Importing Skills from ZIP files
- 📤 Exporting Skills to ZIP files
- 🗑️ Deleting Skills
- ⚙️ Editing per-Skill
valves - ❓ Viewing
help.txt
ZIP import format: The ZIP file must contain exactly one top-level folder with at least skill.py and meta.json. If requirements.txt is present, Unai runs pip install -r during import.
Add a new Skill under skills/<skill_id>/.
skills/
└── my_skill/
├── skill.py # Main implementation
└── meta.json # Metadata
| File | Purpose |
|---|---|
help.txt |
Help text shown by /help <skill> |
requirements.txt |
Python dependencies for the Skill |
valves.json |
Saved settings written from the UI |
Implement these two functions:
def match(text: str) -> bool:
return "hello" in text.lower()
def respond(text: str) -> str | None:
return "Hi there!"match()decides whether the Skill should handle the inputrespond()returns the response text- If
respond()returnsNone, the Skill is skipped
Using Valves (Optional):
If your Skill needs user-configurable settings, you can use the load_valves() and get_valve_definitions() functions. These are automatically injected into your Skill module - no imports needed:
def respond(text: str) -> str:
# Load valve values and definitions
values = load_valves()
definitions = get_valve_definitions()
# Access a specific valve value
api_key = values.get("api_key", "")
return f"Using API key: {api_key}"{
"name": "My Skill",
"description": "What this Skill does",
"author": "your-name",
"version": "1.0.0"
}Add valves for editable settings in the Skills page:
{
"name": "My Skill",
"description": "What this Skill does",
"author": "your-name",
"version": "1.0.0",
"valves": [
{
"key": "api_key",
"label": "API Key",
"type": "password",
"description": "Optional setting shown in the UI",
"default": ""
}
]
}Valves field types:
| Field | Meaning |
|---|---|
key |
Storage key |
label |
UI label |
type |
text, password, or number |
description |
Extra help text |
default |
Default value |
placeholder |
Input placeholder |
| Method | Path | Description |
|---|---|---|
GET |
/ |
Chat UI |
POST |
/api/chat |
Send message. Body: { message, session_id } |
POST |
/api/chat/sse |
Send with SSE progress updates |
POST |
/api/chat/commit |
Commit selected candidate |
POST |
/api/chat/regenerate |
Regenerate a turn. Body: { session_id, turn_id } |
POST |
/api/chat/edit |
Edit message and resend. Body: { session_id, turn_id, message } |
POST |
/api/chat/switch_branch |
Switch branch. Body: { session_id, turn_id, branch_index } |
| Method | Path | Description |
|---|---|---|
GET |
/api/sessions |
List sessions |
POST |
/api/sessions |
Create session |
GET |
/api/sessions/<id> |
Get session |
DELETE |
/api/sessions/<id> |
Delete session |
POST |
/api/sessions/<id>/rename |
Rename session |
| Method | Path | Description |
|---|---|---|
GET |
/skills |
Skills management page |
GET |
/api/skills |
List Skills |
GET |
/api/skills/<id>/export |
Export Skill as ZIP |
POST |
/api/skills/import |
Import Skill ZIP |
DELETE |
/api/skills/<id> |
Delete Skill |
POST |
/api/skills/toggle |
Enable/disable Skill |
POST |
/api/skills/reorder |
Update Skill order |
GET |
/api/skills/<id>/help |
Fetch help.txt |
GET |
/api/skills/<id>/valves |
Fetch valves definitions/values |
POST |
/api/skills/<id>/valves |
Update valves values |
| Method | Path | Description |
|---|---|---|
GET |
/api/settings |
Get app settings |
POST |
/api/settings |
Update app settings |
/help- Shows list of enabled Skills and summaries/help <skill_id>- Shows the target Skill'shelp.txt
Stores Skill execution order and disabled Skills:
{
"order": ["greeting", "wikipedia", "ddgs", "ddgs_chatbot", "calc", "joke"],
"disabled": []
}Stores app-wide settings:
{
"preload_skills": true
}When preload_skills is true, Unai loads all enabled Skills at startup to reduce the delay before the first response.
- Backend: Flask, Python 3.10+
- Database: SQLite (sessions, conversation history)
- Frontend: Vanilla JavaScript, Server-Sent Events (SSE)
- Architecture: Modular Skill-based routing
sessions.dbis SQLite (auto-created)priority.jsonis updated from the Skills page- A Skill works even without
help.txt - If
requirements.txtis present during ZIP import, Unai installs its dependencies - The current implementation does not enforce external-host restrictions via
request_urls.txt
MIT © PixelNest256