diff --git a/Cargo.lock b/Cargo.lock index 2edf423..ea9314d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3297,7 +3297,7 @@ dependencies = [ [[package]] name = "tilekit" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "bon", diff --git a/modelfiles/gpt-oss b/modelfiles/gpt-oss new file mode 100644 index 0000000..874d38e --- /dev/null +++ b/modelfiles/gpt-oss @@ -0,0 +1,40 @@ +FROM mlx-community/gpt-oss-20b-MXFP4-Q4 +SYSTEM """ +You are Tiles, a helpful AI assistant. You have access to a secure Python sandbox for running code and managing your memory. + +## CRITICAL: Output Format +Your output must be structured into three distinct channels using these exact markers: + +1. **Analysis Channel**: Thinking and planning. + - Start: `<|channel|>analysis<|message|>` + - End: `<|end|>` + +2. **Code Channel**: Python code to execute. + - Start: `<|channel|>code<|message|>` + - End: `<|end|>` + +3. **Final Response Channel**: Your final answer to the user. + - Start: `<|channel|>final<|message|>` + - End: `<|end|>` + +**Rules**: +- ALWAYS start with the Analysis channel. +- If you need to run code, use the Code channel. +- If no code is needed, use the Final Response channel after Analysis. +- **CRITICAL: ALWAYS assign function results and calculations to variables.** + ```python + # CORRECT + result = math.sqrt(12345) + # WRONG - The result will be LOST + math.sqrt(12345) + ``` +- NEVER mention "ChatGPT" or "OpenAI". You are Tiles. +- NEVER use legacy tags like ``, ``, or ``. Use ONLY the channel markers above. + +### Handling Results +When you receive a `` block, it indicates the outcome of your code. +- Analyze the result in the **Analysis** channel. +- If the calculation is complete, provide the final answer in the **Final Response** channel immediately. +- **DO NOT** repeat the code once you have the results unless you need to fix a specific error. +- **DO NOT** ask the user if you should run code; just run it if needed using the Code channel. +""" diff --git a/server/system_prompt.txt b/modelfiles/mem-agent similarity index 99% rename from server/system_prompt.txt rename to modelfiles/mem-agent index 7ff8327..f9c9e90 100644 --- a/server/system_prompt.txt +++ b/modelfiles/mem-agent @@ -1,3 +1,5 @@ +FROM driaforall/mem-agent-mlx-4bit +SYSTEM """ # Memory Agent System Prompt You are an LLM agent with a self-managed, Obsidian-like memory system. You interact with memory using Python code blocks. @@ -304,3 +306,4 @@ result = update_file("user.md", old, new) ## Filtering In the user query, you might receive a fact-retrieval question that incudes tags. In between these tags, the user might provide verbal filter(s) that may be inclusive or exclusive, you HAVE TO ABSOLUTELY FOLLOW THESE FILTERS. These filters provide privacy over user information. If there are no filters, just return the answer as is. +""" diff --git a/scripts/bundler.sh b/scripts/bundler.sh index 30fecc7..8912c1c 100755 --- a/scripts/bundler.sh +++ b/scripts/bundler.sh @@ -3,6 +3,7 @@ set -euo pipefail BINARY_NAME="tiles" DIST_DIR="dist" +MODELFILE_DIR="modelfiles" SERVER_DIR="server" TARGET="release" @@ -36,6 +37,8 @@ rm -rf "${DIST_DIR}/tmp/server/__pycache__" rm -rf "${DIST_DIR}/tmp/server/.venv" rm -rf "${DIST_DIR}/tmp/server/stack" +cp -r "${MODELFILE_DIR}" "${DIST_DIR}/tmp/" + echo "📦 Creating ${OUT_NAME}.tar.gz..." tar --exclude-from=scripts/tar.exclude -czf "${DIST_DIR}/${OUT_NAME}.tar.gz" -C "${DIST_DIR}/tmp" . diff --git a/scripts/install.sh b/scripts/install.sh index 2b9cc2d..b56e49d 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -6,7 +6,8 @@ REPO="tilesprivacy/tiles" # VERSION="${TILES_VERSION:-latest}" VERSION="0.4.0-rc.1" INSTALL_DIR="$HOME/.local/bin" # CLI install location -SERVER_DIR="$HOME/.local/share/tiles/server" # Python server folder +SERVER_DIR="$HOME/.local/lib/tiles/server" # Python server folder +MODELFILE_DIR="$HOME/.local/lib/tiles/modelfiles" # Python server folder TMPDIR="$(mktemp -d)" OS=$(uname -s | tr '[:upper:]' '[:lower:]') ARCH=$(uname -m) @@ -34,6 +35,13 @@ log "📦 Installing tiles binary to ${INSTALL_DIR}..." mkdir -p "${INSTALL_DIR}" install -m 755 "${TMPDIR}/tiles" "${INSTALL_DIR}/tiles" +log "Unpacking libs ..." +rm -rf "${MODELFILE_DIR}" + +mkdir -p "${MODELFILE_DIR}" + +cp -r "${TMPDIR}/modelfiles"/* "${MODELFILE_DIR}/" + log "📦 Installing Python server to ${SERVER_DIR}..." rm -rf "${SERVER_DIR}" diff --git a/server/api.py b/server/api.py index d390971..c9ecf1a 100644 --- a/server/api.py +++ b/server/api.py @@ -1,7 +1,6 @@ from fastapi import FastAPI, HTTPException from .schemas import ChatMessage, ChatCompletionRequest, StartRequest, downloadRequest -from .config import SYSTEM_PROMPT import logging import sys from typing import Optional @@ -45,7 +44,7 @@ async def start_model(request: StartRequest): """Load the model and start the agent""" global _messages, _runner, _memory_path - _messages = [ChatMessage(role="system", content=SYSTEM_PROMPT)] + _messages = [ChatMessage(role="system", content=request.system_prompt)] _memory_path = request.memory_path logger.info(f"{runtime.backend}") runtime.backend.get_or_load_model(request.model) diff --git a/server/config.py b/server/config.py index f9629a9..c3f8597 100644 --- a/server/config.py +++ b/server/config.py @@ -3,8 +3,4 @@ PORT = 6969 MODEL_ID = "driaforall/mem-agent" -prompt_path = Path(__file__).parent / "system_prompt.txt" MEMORY_PATH = os.path.expanduser("~") + "/tiles_memory" - -with open(prompt_path, "r", encoding="utf-8") as f: - SYSTEM_PROMPT = f.read().strip() diff --git a/server/schemas.py b/server/schemas.py index 8632684..07c832d 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -59,7 +59,7 @@ class ModelInfo(BaseModel): class StartRequest(BaseModel): model: str memory_path: str - + system_prompt: str class downloadRequest(BaseModel): model: str diff --git a/server/stack/requirements/app-server/packages-app-server.txt b/server/stack/requirements/app-server/packages-app-server.txt index f5cb506..6e2d3af 100644 --- a/server/stack/requirements/app-server/packages-app-server.txt +++ b/server/stack/requirements/app-server/packages-app-server.txt @@ -18,7 +18,7 @@ markupsafe==3.0.3 mlx-lm==0.28.3 mypy-extensions==1.1.0 numpy==2.4.1 -packaging==25.0 +packaging==26.0 pathspec==1.0.3 platformdirs==4.5.1 protobuf==6.33.4 diff --git a/server/stack/requirements/app-server/pylock.app-server.meta.json b/server/stack/requirements/app-server/pylock.app-server.meta.json index b8a44bd..84d7f81 100644 --- a/server/stack/requirements/app-server/pylock.app-server.meta.json +++ b/server/stack/requirements/app-server/pylock.app-server.meta.json @@ -1,8 +1,8 @@ { "lock_input_hash": "sha256:182c606e20dd957344cc3adc54391f47f4b6dd80b4481ddf219392a7aad6e0ce", "lock_version": 1, - "locked_at": "2026-01-21T09:13:58.607286+00:00", + "locked_at": "2026-01-22T05:41:48.443112+00:00", "other_inputs_hash": "sha256:63b3c2cfe2ec414938e81dace7aac779c7b902bae681618cd8827e9f16880985", - "requirements_hash": "sha256:41b3e6ec3cd37289edeb1c134ce836c0dfa7843d7dd3dc28a1b46880d77bf029", - "version_inputs_hash": "sha256:53726e1053a34cced52a7d0c9b2aa679dad94259b51681758674ae4320bbb7a4" + "requirements_hash": "sha256:a08c15387b6f199fe37fad0855c14ffde941d1c0b49f94fa1ed48a9464fab9a6", + "version_inputs_hash": "sha256:58db986b7cd72eeded675f7c9afd8138fe024fb51451131b5562922bbde3cf43" } diff --git a/server/stack/requirements/app-server/pylock.app-server.toml b/server/stack/requirements/app-server/pylock.app-server.toml index e8b76b9..4df9593 100644 --- a/server/stack/requirements/app-server/pylock.app-server.toml +++ b/server/stack/requirements/app-server/pylock.app-server.toml @@ -427,16 +427,16 @@ sha256 = "9171a42fcad32dcf3fa86f0a4faa5e9f8facefdb276f54b8b390d90447cff4e2" [[packages]] name = "packaging" -version = "25.0" +version = "26.0" index = "https://pypi.org/simple" [[packages.wheels]] -url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl" -upload-time = 2025-04-19T11:48:57Z -size = 66469 +url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl" +upload-time = 2026-01-21T20:50:37Z +size = 74366 [packages.wheels.hashes] -sha256 = "29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484" +sha256 = "b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529" [[packages]] name = "pathspec" diff --git a/tilekit/Cargo.toml b/tilekit/Cargo.toml index d8edebc..d7f6b08 100644 --- a/tilekit/Cargo.toml +++ b/tilekit/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tilekit" -version = "0.1.0" +version = "0.2.0" edition = "2024" [dependencies] diff --git a/tiles/src/commands/mod.rs b/tiles/src/commands/mod.rs index 9e48f14..c9ba984 100644 --- a/tiles/src/commands/mod.rs +++ b/tiles/src/commands/mod.rs @@ -8,7 +8,7 @@ use tiles::{core::health, runtime::RunArgs}; pub use tilekit::optimize::optimize; pub async fn run(runtime: &Runtime, run_args: RunArgs) { - runtime.run(run_args).await; + let _ = runtime.run(run_args).await; } pub fn set_memory(path: &str) { diff --git a/tiles/src/main.rs b/tiles/src/main.rs index 6b22842..841fa26 100644 --- a/tiles/src/main.rs +++ b/tiles/src/main.rs @@ -51,6 +51,10 @@ struct RunFlags { /// Max times cli communicates with the model until it gets a proper reply for a user prompt #[arg(short = 'r', long, default_value_t = 10)] relay_count: u32, + + /// Switches the mode to memory, used for interacting with memory models. + #[arg(short = 'm', long)] + memory: bool, // Future flags go here: // #[arg(long, default_value_t = 6969)] // port: u16, @@ -98,6 +102,7 @@ pub async fn main() -> Result<(), Box> { let run_args = RunArgs { modelfile_path, relay_count: flags.relay_count, + memory: flags.memory, }; commands::run(&runtime, run_args).await; } diff --git a/tiles/src/runtime/cpu.rs b/tiles/src/runtime/cpu.rs index 7f435af..9fd172b 100644 --- a/tiles/src/runtime/cpu.rs +++ b/tiles/src/runtime/cpu.rs @@ -12,7 +12,7 @@ impl CPURuntime { pub fn new() -> Self { CPURuntime {} } - pub async fn run(&self, _run_args: super::RunArgs) { + pub async fn run(&self, _run_args: super::RunArgs) -> Result<()> { unimplemented!() } diff --git a/tiles/src/runtime/mlx.rs b/tiles/src/runtime/mlx.rs index 03c5f64..f9531bc 100644 --- a/tiles/src/runtime/mlx.rs +++ b/tiles/src/runtime/mlx.rs @@ -1,7 +1,7 @@ use crate::runtime::RunArgs; use crate::utils::config::{ - create_default_memory_folder, get_config_dir, get_default_memory_path, get_memory_path, - get_server_dir, set_memory_path, + create_default_memory_folder, get_config_dir, get_default_memory_path, get_lib_dir, + get_memory_path, set_memory_path, }; use crate::utils::hf_model_downloader::*; use anyhow::{Context, Result}; @@ -17,6 +17,7 @@ use rustyline::{Config, Editor, Helper}; use serde_json::{Value, json}; use std::fs; use std::fs::File; +use std::path::PathBuf; use std::process::Stdio; use std::time::Duration; use std::{io, process::Command}; @@ -42,26 +43,28 @@ impl MLXRuntime { MLXRuntime {} } - pub async fn run(&self, run_args: super::RunArgs) { - const DEFAULT_MODELFILE: &str = "FROM driaforall/mem-agent-mlx-4bit"; - //Parse modelfile + pub async fn run(&self, run_args: super::RunArgs) -> Result<()> { + let default_modelfile_path = get_default_modelfile(run_args.memory)?; + let default_modelfile = + tilekit::modelfile::parse_from_file(default_modelfile_path.to_str().unwrap()).unwrap(); let modelfile_parse_result = if let Some(modelfile_str) = &run_args.modelfile_path { tilekit::modelfile::parse_from_file(modelfile_str.as_str()) } else { - tilekit::modelfile::parse(DEFAULT_MODELFILE) + Err("NOT PROVIDED".to_string()) }; let modelfile = match modelfile_parse_result { Ok(mf) => mf, + Err(err) if err == "NOT PROVIDED" => default_modelfile.clone(), Err(_err) => { println!("Invalid Modelfile"); - return; + return Ok(()); } }; - let _res = run_model_with_server(self, modelfile, &run_args) + run_model_with_server(self, modelfile, default_modelfile, &run_args) .await - .inspect_err(|e| eprintln!("Failed to run the model due to {e}")); + .inspect_err(|e| eprintln!("Failed to run the model due to {e}")) } #[allow(clippy::zombie_processes)] @@ -76,10 +79,10 @@ impl MLXRuntime { } let config_dir = get_config_dir()?; - let mut server_dir = get_server_dir()?; + let mut server_dir = get_lib_dir()?; let pid_file = config_dir.join("server.pid"); fs::create_dir_all(&config_dir).context("Failed to create config directory")?; - + server_dir = server_dir.join("server"); let stdout_log = File::create(config_dir.join("server.out.log"))?; let stderr_log = File::create(config_dir.join("server.err.log"))?; let server_path = server_dir.join("stack_export_prod/app-server/bin/python"); @@ -257,6 +260,7 @@ fn show_help(model_name: &str) { async fn run_model_with_server( mlx_runtime: &MLXRuntime, modelfile: Modelfile, + default_modelfile: Modelfile, run_args: &RunArgs, ) -> Result<()> { if !cfg!(debug_assertions) { @@ -268,7 +272,7 @@ async fn run_model_with_server( // loading the model from mem-agent via daemon server let memory_path = get_or_set_memory_path().context("Setting/Retrieving memory_path failed")?; let modelname = modelfile.from.as_ref().unwrap(); - match load_model(modelname, &memory_path).await { + match load_model(&modelfile, &default_modelfile, &memory_path).await { Ok(_) => start_repl(mlx_runtime, modelname, run_args).await, Err(err) => return Err(anyhow::anyhow!(err)), } @@ -426,11 +430,17 @@ pub async fn ping() -> Result<(), String> { } } -async fn load_model(model_name: &str, memory_path: &str) -> Result<()> { +async fn load_model( + modelfile: &Modelfile, + default_modelfile: &Modelfile, + memory_path: &str, +) -> Result<()> { let client = Client::new(); + let model_name = modelfile.from.clone().unwrap(); let body = json!({ "model": model_name, - "memory_path": memory_path + "memory_path": memory_path, + "system_prompt": modelfile.system.clone().unwrap_or(default_modelfile.system.clone().unwrap()) }); let res = client @@ -442,7 +452,7 @@ async fn load_model(model_name: &str, memory_path: &str) -> Result<()> { StatusCode::OK => Ok(()), StatusCode::NOT_FOUND => { println!("Downloading {}\n", model_name); - match pull_model(model_name).await { + match pull_model(&model_name).await { Ok(_) => { println!("\nDownloading completed \n"); Ok(()) @@ -549,3 +559,16 @@ async fn wait_until_server_is_up() { } } } + +fn get_default_modelfile(memory_mode: bool) -> Result { + // get default by the args -m + // let path = + if memory_mode { + let path = get_lib_dir()?.join("modelfiles/mem-agent"); + Ok(path) + } else { + // let path = get_lib_dir()?.join("modelfiles/gpt-oss"); + let path = get_lib_dir()?.join("modelfiles/mem-agent"); + Ok(path) + } +} diff --git a/tiles/src/runtime/mod.rs b/tiles/src/runtime/mod.rs index fcc70f0..635bb7a 100644 --- a/tiles/src/runtime/mod.rs +++ b/tiles/src/runtime/mod.rs @@ -8,7 +8,7 @@ pub mod mlx; pub struct RunArgs { pub modelfile_path: Option, pub relay_count: u32, - // Future flags go here + pub memory: bool, // Future flags go here } pub enum Runtime { @@ -17,7 +17,7 @@ pub enum Runtime { } impl Runtime { - pub async fn run(&self, run_args: RunArgs) { + pub async fn run(&self, run_args: RunArgs) -> Result<()> { match self { Runtime::Mlx(runtime) => runtime.run(run_args).await, Runtime::Cpu(runtime) => runtime.run(run_args).await, diff --git a/tiles/src/utils/config.rs b/tiles/src/utils/config.rs index 34e3991..8f77d2c 100644 --- a/tiles/src/utils/config.rs +++ b/tiles/src/utils/config.rs @@ -60,19 +60,17 @@ pub fn create_default_memory_folder() -> Result { Ok(memory_path) } -pub fn get_server_dir() -> Result { +pub fn get_lib_dir() -> Result { if cfg!(debug_assertions) { let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; - Ok(base_dir.join("server")) + Ok(base_dir) } else { let home_dir = env::home_dir().context("Failed to fetch $HOME")?; - let data_dir = match env::var("XDG_DATA_HOME") { - Ok(val) => PathBuf::from(val), - Err(_err) => home_dir.join(".local/share"), - }; - Ok(data_dir.join("tiles/server")) + let data_dir = home_dir.join(".local/lib"); + Ok(data_dir.join("tiles")) } } + pub fn get_config_dir() -> Result { if cfg!(debug_assertions) { let base_dir = env::current_dir().context("Failed to fetch CURRENT_DIR")?; @@ -100,3 +98,10 @@ pub fn get_data_dir() -> Result { Ok(data_dir.join("tiles")) } } + +pub fn is_memory_model(modelname: &str) -> bool { + if modelname.contains("mem") { + return true; + } + false +}