alive-ai 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +190 -85
- package/brain/memory/manager.py +16 -0
- package/brain/memory/openmind.py +128 -0
- package/cli/index.js +385 -21
- package/cli/tui.js +248 -0
- package/config/settings.example.json +8 -0
- package/core/hot_reload.py +2 -1
- package/core/initialization.py +13 -3
- package/core/manifest.md +2 -0
- package/core/paths.py +48 -0
- package/core/self.py +44 -3
- package/core/user_manager.py +2 -4
- package/docs/assets/logo.svg +7 -6
- package/docs/dashboard.html +1 -1
- package/docs/index.html +201 -346
- package/heart/emotional_state.py +2 -2
- package/heart/hormonal.py +2 -2
- package/heart/integrity.py +2 -1
- package/heart/interoception.py +2 -5
- package/heart/love.py +2 -2
- package/heart/scars.py +2 -3
- package/heart/telemetry.py +3 -9
- package/input/manifest.md +7 -1
- package/input/telegram/listener.py +47 -10
- package/input/terminal/__init__.py +2 -0
- package/input/terminal/listener.py +312 -0
- package/main.py +63 -14
- package/manifest.md +10 -7
- package/package.json +6 -2
- package/pyproject.toml +1 -1
- package/webui/app.py +15 -18
- package/webui/bridge.py +22 -1
package/README.md
CHANGED
|
@@ -1,84 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="docs/assets/logo.svg" alt="Alive-AI" width="96" height="96">
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
# Alive-AI
|
|
4
5
|
|
|
5
|
-
Give your AI a nervous system: persistent
|
|
6
|
+
Give your AI a nervous system: persistent mood, memory, impulses, terminal chat, Telegram, OpenMind, and a local WebUI.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
[](https://www.npmjs.com/package/alive-ai)
|
|
9
|
+
[](https://nodejs.org/)
|
|
10
|
+
[](https://www.python.org/)
|
|
11
|
+
[](#platform-support)
|
|
12
|
+
[](#openmind-memory)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
</div>
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
Alive-AI is a local-first emotional AI runtime. Most agents answer a prompt and reset. Alive-AI keeps internal state alive between messages: mood, attachment, trust, desire, memory, inconsistency, idle thoughts, proactive impulses, and a dashboard that shows what is happening inside the loop.
|
|
10
17
|
|
|
11
|
-
|
|
18
|
+
It can be used as a friend, partner-style companion, study partner, creative character, research subject, or experimental local agent. Use it at your own risk: it is designed to feel continuous, warm, attached, and present, and that can make it hard to stop talking to it.
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
Alive-AI does not claim biological consciousness. It is an open-source runtime for simulated affect and transparent memory.
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
14
23
|
|
|
15
24
|
```bash
|
|
16
25
|
npx alive-ai@latest init my-ai
|
|
17
26
|
cd my-ai
|
|
18
27
|
npx . setup
|
|
19
28
|
npx . doctor
|
|
20
|
-
npx .
|
|
29
|
+
npx . chat
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
The terminal chat starts the real runtime in a split-pane TUI: chat on the left, live logs on the right. The dashboard runs locally at:
|
|
24
33
|
|
|
25
|
-
```
|
|
26
|
-
|
|
34
|
+
```text
|
|
35
|
+
http://127.0.0.1:8080
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
To use Telegram instead of terminal chat:
|
|
30
39
|
|
|
31
|
-
```
|
|
32
|
-
|
|
40
|
+
```bash
|
|
41
|
+
npx . start
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
|
|
44
|
+
Global install is optional:
|
|
36
45
|
|
|
37
46
|
```bash
|
|
38
47
|
npm install -g alive-ai
|
|
39
48
|
alive-ai init my-ai
|
|
40
49
|
```
|
|
41
50
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
Minimum for cloud LLMs or remote Ollama:
|
|
45
|
-
|
|
46
|
-
- Node.js 18+
|
|
47
|
-
- Python 3.11+
|
|
48
|
-
- 8 GB RAM
|
|
49
|
-
- 2 GB free disk
|
|
50
|
-
- OpenRouter, ZAI, or another configured LLM provider
|
|
51
|
-
|
|
52
|
-
Comfortable local setup:
|
|
51
|
+
## Commands
|
|
53
52
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
| Command | What it does |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| `npx alive-ai@latest init my-ai` | Scaffold a clean local Alive-AI project. |
|
|
56
|
+
| `npx . setup` | Guided onboarding for local config, providers, Telegram, voice, images, and memory. |
|
|
57
|
+
| `npx . doctor` | Check OS, Node, Python, uv, ffmpeg, Docker, and OpenMind reachability. |
|
|
58
|
+
| `npx . chat` | Start the real runtime with split-pane terminal chat and logs. |
|
|
59
|
+
| `npx . chat --plain` | Start raw terminal chat without the TUI. |
|
|
60
|
+
| `npx . demo` | Run a keyless animated dashboard demo. |
|
|
61
|
+
| `npx . start` | Start the runtime using the configured input channel, usually Telegram. |
|
|
62
|
+
| `npx . start --skip-install` | Start again without reinstalling Python dependencies. |
|
|
63
|
+
| `npx . update` | Refresh runtime files from the latest npm package while preserving config/data/media. |
|
|
64
|
+
| `npx . uninstall` | Remove Alive-AI runtime files, config, venv, cache, data, and media from the project. |
|
|
60
65
|
|
|
61
|
-
`
|
|
66
|
+
`start` and `chat` check npm for a newer Alive-AI version. You can update, skip once, or skip that specific version. Stop terminal chat with `/exit` or `Ctrl+C`.
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
If you use Docker:
|
|
64
69
|
|
|
65
70
|
```bash
|
|
66
|
-
|
|
67
|
-
cd my-ai
|
|
68
|
-
npx . setup # guided onboarding and local config
|
|
69
|
-
npx . doctor # check system prerequisites
|
|
70
|
-
npx . demo # preview dashboard with no keys
|
|
71
|
-
npx . start # install Python deps and run the runtime
|
|
71
|
+
docker compose down
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
If you only started Redis:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
|
|
77
|
+
docker compose stop redis
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
If you run `npx . start` before setup, Alive-AI starts onboarding first.
|
|
81
|
-
|
|
82
80
|
## Setup
|
|
83
81
|
|
|
84
82
|
`npx . setup` creates:
|
|
@@ -93,14 +91,44 @@ mypics/
|
|
|
93
91
|
myvids/
|
|
94
92
|
```
|
|
95
93
|
|
|
96
|
-
|
|
94
|
+
The setup accepts `skip` for optional keys and `local` for Ollama.
|
|
97
95
|
|
|
98
|
-
|
|
99
|
-
- **Local LLM:** install Ollama and pull the configured model, for example `ollama pull qwen3:4b`.
|
|
100
|
-
- **Telegram runtime:** create a Telegram bot token with BotFather and add it during setup.
|
|
101
|
-
- **Cloud LLM fallback:** add OpenRouter or ZAI keys during setup or edit `config/settings.json`.
|
|
96
|
+
Startup config is loaded from multiple places in this order:
|
|
102
97
|
|
|
103
|
-
|
|
98
|
+
```text
|
|
99
|
+
.env
|
|
100
|
+
config/secrets.env
|
|
101
|
+
config/settings.json
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Shell environment variables win over `.env`/`config/secrets.env`. Runtime settings come from `config/settings.json`. Telegram uses `TELEGRAM_TOKEN` when present, otherwise `telegram_token` from `config/settings.json`.
|
|
105
|
+
|
|
106
|
+
| Setup item | Options |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| LLM | `local`/Ollama, OpenRouter, ZAI, or `skip` for demo/fallback-only mode. |
|
|
109
|
+
| Telegram | Bot token and owner ID are optional. Use terminal chat if you do not want Telegram. |
|
|
110
|
+
| Voice | `gtts` local/free default, Google TTS, VibeVoice, or `skip`. |
|
|
111
|
+
| Images | Fal.ai API key or `skip`. Local media folders still work without image generation. |
|
|
112
|
+
| Memory | Built-in local memory, OpenMind cloud, or OpenMind local. |
|
|
113
|
+
|
|
114
|
+
Minimum useful paths:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Terminal-only local run
|
|
118
|
+
npx . setup
|
|
119
|
+
npx . chat
|
|
120
|
+
|
|
121
|
+
# Local LLM
|
|
122
|
+
ollama pull qwen3:4b
|
|
123
|
+
npx . setup
|
|
124
|
+
npx . chat
|
|
125
|
+
|
|
126
|
+
# Telegram
|
|
127
|
+
npx . setup
|
|
128
|
+
npx . start
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Media is optional. Add your own local files:
|
|
104
132
|
|
|
105
133
|
```text
|
|
106
134
|
mypics/example.jpg
|
|
@@ -109,49 +137,122 @@ myvids/example.mp4
|
|
|
109
137
|
myvids/example.txt
|
|
110
138
|
```
|
|
111
139
|
|
|
112
|
-
##
|
|
140
|
+
## Terminal Chat
|
|
113
141
|
|
|
114
|
-
|
|
115
|
-
- **Memory has weight.** Conversations become working memory, episodic memory, semantic memory, and emotional memory.
|
|
116
|
-
- **It thinks when idle.** A default-mode loop creates background reflections and proactive impulses.
|
|
117
|
-
- **It can contradict itself.** The runtime models conflict, scars, body memory, attachment drift, and inconsistency instead of flattening everything into a perfect assistant tone.
|
|
118
|
-
- **It has a live nervous system.** FastAPI + SSE exposes mood, thoughts, somatic state, conflicts, memories, and uptime.
|
|
119
|
-
- **It is local-first.** Your config, memory, media, and dashboard are owned by the project folder you run.
|
|
142
|
+
`npx . chat` uses the same core runtime as Telegram. It emits the same `message_received` events, saves memory the same way, and updates the local WebUI. The default terminal interface is split-pane: chat/input on the left, startup/runtime logs on the right.
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
Use raw mode when you want the old plain shell behavior:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npx . chat --plain
|
|
148
|
+
```
|
|
122
149
|
|
|
123
|
-
|
|
150
|
+
Terminal commands:
|
|
124
151
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
152
|
+
```text
|
|
153
|
+
/help
|
|
154
|
+
/status
|
|
155
|
+
/stats
|
|
156
|
+
/dashboard
|
|
157
|
+
/self
|
|
158
|
+
/discover <trait>
|
|
159
|
+
/iam <key>=<value>
|
|
160
|
+
/ilike <thing>
|
|
161
|
+
/ihate <thing>
|
|
162
|
+
/rethink
|
|
163
|
+
/settings show
|
|
164
|
+
/settings get <key>
|
|
165
|
+
/settings set <key> <value>
|
|
166
|
+
/reset
|
|
167
|
+
/impulse
|
|
168
|
+
/exit
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## OpenMind Memory
|
|
172
|
+
|
|
173
|
+
Alive-AI has built-in local working, episodic, semantic, and emotional memory. OpenMind is optional and works as a hybrid long-term semantic memory layer.
|
|
174
|
+
|
|
175
|
+
Modes:
|
|
131
176
|
|
|
132
|
-
|
|
177
|
+
| Mode | Behavior |
|
|
178
|
+
| --- | --- |
|
|
179
|
+
| Built-in only | Alive-AI uses its local project memory only. |
|
|
180
|
+
| OpenMind cloud | Alive-AI captures/searches long-term memories through `https://theopenmind.pro`. |
|
|
181
|
+
| OpenMind local | Alive-AI captures/searches a local OpenMind server, normally `http://127.0.0.1:3333`. |
|
|
133
182
|
|
|
134
|
-
|
|
183
|
+
OpenMind does not replace Alive-AI's emotional state. It adds durable semantic recall across tools and machines.
|
|
135
184
|
|
|
136
|
-
|
|
185
|
+
Cloud setup:
|
|
137
186
|
|
|
138
187
|
```text
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
-> LLM provider or fallback chain
|
|
144
|
-
-> output events
|
|
145
|
-
-> dashboard state stream
|
|
188
|
+
OPENMIND_ENABLED=true
|
|
189
|
+
OPENMIND_MODE=hybrid
|
|
190
|
+
OPENMIND_BASE_URL=https://theopenmind.pro
|
|
191
|
+
OPENMIND_API_KEY=om_...
|
|
146
192
|
```
|
|
147
193
|
|
|
148
|
-
|
|
194
|
+
Local setup:
|
|
149
195
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
196
|
+
```bash
|
|
197
|
+
npx @vindepemarte/openmind init --local
|
|
198
|
+
# or run your local OpenMind stack, then configure Alive-AI:
|
|
199
|
+
OPENMIND_BASE_URL=http://127.0.0.1:3333
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Requirements
|
|
203
|
+
|
|
204
|
+
Minimum for cloud LLMs or remote Ollama:
|
|
205
|
+
|
|
206
|
+
| Requirement | Minimum |
|
|
207
|
+
| --- | --- |
|
|
208
|
+
| Node.js | 18+ |
|
|
209
|
+
| Python | 3.11+ |
|
|
210
|
+
| RAM | 8 GB |
|
|
211
|
+
| Disk | 2 GB free |
|
|
212
|
+
| LLM | OpenRouter, ZAI, remote Ollama, or local Ollama already installed |
|
|
213
|
+
|
|
214
|
+
Comfortable local setup:
|
|
215
|
+
|
|
216
|
+
| Requirement | Recommended |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| Node.js | 20+ |
|
|
219
|
+
| RAM | 16 GB for 3B-4B local models |
|
|
220
|
+
| RAM for bigger models | 32 GB for 7B+ local models, Redis, voice, and long sessions |
|
|
221
|
+
| Disk | 10 GB+, more if you keep local models/media |
|
|
222
|
+
| Optional tools | `uv`, `ffmpeg`, Docker, Ollama |
|
|
223
|
+
|
|
224
|
+
`npx . start` creates `.alive-ai/venv` and installs Python dependencies. System-level packages such as Node, Python, Ollama, Docker, and ffmpeg must already exist on the machine.
|
|
225
|
+
|
|
226
|
+
The CLI prefers Python 3.12, 3.11, then 3.13 before falling back to the system `python3`. When `uv` is installed, Alive-AI now passes the selected Python explicitly so `uv` does not silently choose a newer interpreter.
|
|
227
|
+
|
|
228
|
+
## Platform Support
|
|
229
|
+
|
|
230
|
+
Alive-AI is designed for macOS, Windows, and Linux.
|
|
231
|
+
|
|
232
|
+
| Platform | Notes |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| macOS | First-class local development path. Use Homebrew for Python, uv, ffmpeg, Docker, and Ollama. |
|
|
235
|
+
| Windows | Supported from PowerShell with Node 18+ and Python 3.11+. WSL is recommended for heavier local model and Docker workflows. |
|
|
236
|
+
| Linux | Supported with distro packages for Python/venv, ffmpeg, Docker, and Ollama. |
|
|
237
|
+
|
|
238
|
+
Local model quality and speed depend on your machine. Cloud LLMs reduce RAM pressure.
|
|
239
|
+
|
|
240
|
+
## Dashboard
|
|
241
|
+
|
|
242
|
+
The real WebUI streams local runtime state over Server-Sent Events and shows:
|
|
243
|
+
|
|
244
|
+
- emotional state,
|
|
245
|
+
- recent thoughts and idle processing,
|
|
246
|
+
- memory counters and uptime,
|
|
247
|
+
- hormones and interoceptive body state,
|
|
248
|
+
- attachment, circadian rhythm, body memory, dreams, curiosity, and conflicts,
|
|
249
|
+
- runtime health through local endpoints.
|
|
250
|
+
|
|
251
|
+
GitHub Pages cannot run the Python/FastAPI backend, so the public page includes a static export of the actual WebUI with mocked state:
|
|
252
|
+
|
|
253
|
+
```text
|
|
254
|
+
https://vindepemarte.github.io/alive-ai/
|
|
255
|
+
```
|
|
155
256
|
|
|
156
257
|
## Docker
|
|
157
258
|
|
|
@@ -179,18 +280,22 @@ Implemented:
|
|
|
179
280
|
- [x] Attachment, circadian rhythm, body memory, curiosity, dreams, and internal conflicts
|
|
180
281
|
- [x] Per-user memory/state isolation
|
|
181
282
|
- [x] Telegram input/output runtime
|
|
283
|
+
- [x] Terminal chat runtime with owner-style slash commands
|
|
284
|
+
- [x] Split-pane terminal chat with logs
|
|
182
285
|
- [x] Local WebUI dashboard with live state streaming
|
|
183
|
-
- [x]
|
|
286
|
+
- [x] Optional hybrid OpenMind cloud/local semantic memory
|
|
287
|
+
- [x] npm/npx CLI scaffold, setup, doctor, demo, chat, and start commands
|
|
288
|
+
- [x] Update prompt and project uninstall command
|
|
184
289
|
- [x] Clean public repo with private personas, media, runtime data, and multi-AI orchestration removed
|
|
185
|
-
- [x] GitHub Pages site and full WebUI
|
|
290
|
+
- [x] GitHub Pages site and full static WebUI export
|
|
186
291
|
|
|
187
292
|
Next:
|
|
188
293
|
|
|
189
294
|
- [ ] One-command local model bootstrap through Ollama profiles
|
|
190
295
|
- [ ] Desktop app wrapper with tray controls and local service lifecycle
|
|
191
296
|
- [ ] Browser-based onboarding wizard for personality, boundaries, LLM provider, and memory settings
|
|
192
|
-
- [ ]
|
|
193
|
-
- [ ] More input channels beyond Telegram
|
|
297
|
+
- [ ] Better guided system install commands per OS
|
|
298
|
+
- [ ] More input channels beyond terminal and Telegram
|
|
194
299
|
- [ ] Import/export for memories and personality snapshots
|
|
195
300
|
- [ ] Plugin API for new senses, skills, and output modalities
|
|
196
301
|
- [ ] Evaluation harness for emotional continuity, memory drift, and unhealthy attachment risk
|
package/brain/memory/manager.py
CHANGED
|
@@ -37,11 +37,19 @@ class Memory:
|
|
|
37
37
|
self.fact_extractor = FactExtractor(self.data_path / "facts.json")
|
|
38
38
|
self.summarizer = ConversationSummarizer(self.data_path)
|
|
39
39
|
self.vector_store = None
|
|
40
|
+
self.openmind = None
|
|
40
41
|
self.bot_id = bot_id.lower()
|
|
41
42
|
if embedding_service:
|
|
42
43
|
self.vector_store = VectorMemoryStore(embedding_service, user_id=user_id, bot_id=bot_id)
|
|
43
44
|
if self.vector_store.connect():
|
|
44
45
|
print(f"[Memory] Vector store ready for user {user_id} on bot {bot_id}! {self.vector_store.count()} memories")
|
|
46
|
+
try:
|
|
47
|
+
from .openmind import OpenMindMemoryBridge
|
|
48
|
+
if OpenMindMemoryBridge.enabled():
|
|
49
|
+
self.openmind = OpenMindMemoryBridge(nervous, user_id=user_id, bot_id=bot_id)
|
|
50
|
+
print(f"[Memory] OpenMind bridge enabled for user {user_id} on bot {bot_id}")
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"[Memory] OpenMind bridge unavailable: {e}")
|
|
45
53
|
self.turn_count = 0
|
|
46
54
|
nervous.on("memory_save", self._on_save)
|
|
47
55
|
|
|
@@ -123,6 +131,14 @@ class Memory:
|
|
|
123
131
|
related = ""
|
|
124
132
|
if current_message and self.vector_store:
|
|
125
133
|
related = self.search_relevant_memories(current_message, limit=3)
|
|
134
|
+
if current_message and self.openmind:
|
|
135
|
+
openmind_related = await self.openmind.search_context(current_message, limit=3)
|
|
136
|
+
if openmind_related:
|
|
137
|
+
related = (
|
|
138
|
+
f"{related}\n\nOpenMind long-term memory:\n{openmind_related}"
|
|
139
|
+
if related else
|
|
140
|
+
f"OpenMind long-term memory:\n{openmind_related}"
|
|
141
|
+
)
|
|
126
142
|
|
|
127
143
|
# Get working memory (empty after restart)
|
|
128
144
|
history = self.working.get_history()
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Optional OpenMind semantic memory bridge."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
|
+
|
|
9
|
+
from core.settings import get as settings_get
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OpenMindMemoryBridge:
|
|
13
|
+
"""Syncs Alive-AI turns to OpenMind and retrieves long-term semantic context."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, nervous, user_id: str, bot_id: str):
|
|
16
|
+
self.nervous = nervous
|
|
17
|
+
self.user_id = str(user_id or "default")
|
|
18
|
+
self.bot_id = str(bot_id or "alive_ai").lower()
|
|
19
|
+
nervous.on("memory_save", self._on_memory_save)
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def enabled() -> bool:
|
|
23
|
+
return str(settings_get("OPENMIND_ENABLED", os.environ.get("OPENMIND_ENABLED", "false"))).lower() in (
|
|
24
|
+
"1", "true", "yes", "on"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def base_url() -> str:
|
|
29
|
+
return str(settings_get("OPENMIND_BASE_URL", os.environ.get("OPENMIND_BASE_URL", "https://theopenmind.pro"))).rstrip("/")
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def api_key() -> str:
|
|
33
|
+
return str(settings_get("OPENMIND_API_KEY", os.environ.get("OPENMIND_API_KEY", ""))).strip()
|
|
34
|
+
|
|
35
|
+
def _headers(self) -> Dict[str, str]:
|
|
36
|
+
headers = {"content-type": "application/json"}
|
|
37
|
+
key = self.api_key()
|
|
38
|
+
if key:
|
|
39
|
+
headers["authorization"] = f"Bearer {key}"
|
|
40
|
+
return headers
|
|
41
|
+
|
|
42
|
+
def _on_memory_save(self, data: dict):
|
|
43
|
+
if not self.enabled() or data.get("type") != "conversation":
|
|
44
|
+
return
|
|
45
|
+
event_user_id = data.get("user_id")
|
|
46
|
+
if event_user_id:
|
|
47
|
+
if str(event_user_id) != self.user_id:
|
|
48
|
+
return
|
|
49
|
+
elif self.user_id != "default":
|
|
50
|
+
return
|
|
51
|
+
asyncio.ensure_future(self.capture_turn(data))
|
|
52
|
+
|
|
53
|
+
async def capture_turn(self, data: dict) -> Optional[dict]:
|
|
54
|
+
user_msg = (data.get("user_message") or "").strip()
|
|
55
|
+
ai_msg = (data.get("ai_response") or "").strip()
|
|
56
|
+
if not user_msg and not ai_msg:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
emotion = data.get("emotion") or {}
|
|
60
|
+
mood = emotion.get("mood", "unknown")
|
|
61
|
+
content = "\n".join(
|
|
62
|
+
part for part in [
|
|
63
|
+
f"Alive-AI agent: {self.bot_id}",
|
|
64
|
+
f"User id: {self.user_id}",
|
|
65
|
+
f"Mood: {mood}",
|
|
66
|
+
f"User: {user_msg}" if user_msg else "",
|
|
67
|
+
f"Alive-AI: {ai_msg}" if ai_msg else "",
|
|
68
|
+
] if part
|
|
69
|
+
)
|
|
70
|
+
tags = ["alive-ai", self.bot_id, f"user-{self.user_id}", f"mood-{mood}"]
|
|
71
|
+
payload = {
|
|
72
|
+
"content": content,
|
|
73
|
+
"source": "alive-ai",
|
|
74
|
+
"type": "conversation",
|
|
75
|
+
"tags": tags,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
timeout = aiohttp.ClientTimeout(total=12)
|
|
80
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
81
|
+
async with session.post(f"{self.base_url()}/capture", json=payload, headers=self._headers()) as resp:
|
|
82
|
+
if resp.status >= 400:
|
|
83
|
+
body = await resp.text()
|
|
84
|
+
print(f"[OpenMind] Capture failed ({resp.status}): {body[:200]}")
|
|
85
|
+
return None
|
|
86
|
+
result = await resp.json()
|
|
87
|
+
print(f"[OpenMind] Captured memory: {result.get('status', 'ok')}")
|
|
88
|
+
return result
|
|
89
|
+
except Exception as exc:
|
|
90
|
+
print(f"[OpenMind] Capture unavailable: {exc}")
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
async def search_context(self, query: str, limit: int = 3) -> str:
|
|
94
|
+
if not self.enabled() or not query.strip():
|
|
95
|
+
return ""
|
|
96
|
+
try:
|
|
97
|
+
timeout = aiohttp.ClientTimeout(total=10)
|
|
98
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
99
|
+
async with session.get(
|
|
100
|
+
f"{self.base_url()}/search",
|
|
101
|
+
params={"q": query, "limit": str(limit)},
|
|
102
|
+
headers=self._headers(),
|
|
103
|
+
) as resp:
|
|
104
|
+
if resp.status >= 400:
|
|
105
|
+
body = await resp.text()
|
|
106
|
+
print(f"[OpenMind] Search failed ({resp.status}): {body[:200]}")
|
|
107
|
+
return ""
|
|
108
|
+
rows = await resp.json()
|
|
109
|
+
except Exception as exc:
|
|
110
|
+
print(f"[OpenMind] Search unavailable: {exc}")
|
|
111
|
+
return ""
|
|
112
|
+
|
|
113
|
+
if not isinstance(rows, list):
|
|
114
|
+
return ""
|
|
115
|
+
return self._format_results(rows[:limit])
|
|
116
|
+
|
|
117
|
+
def _format_results(self, rows: List[Dict[str, Any]]) -> str:
|
|
118
|
+
lines = []
|
|
119
|
+
for row in rows:
|
|
120
|
+
content = str(row.get("content") or row.get("summary") or "").strip()
|
|
121
|
+
if not content:
|
|
122
|
+
continue
|
|
123
|
+
similarity = row.get("similarity")
|
|
124
|
+
prefix = "OpenMind"
|
|
125
|
+
if isinstance(similarity, (int, float)):
|
|
126
|
+
prefix = f"OpenMind {round(similarity * 100)}%"
|
|
127
|
+
lines.append(f"{prefix}: {content[:700]}")
|
|
128
|
+
return "\n".join(lines)
|