Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions Autotests/test_openai_runtime_embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
MEMORY_METTA_PATH = REPO_ROOT / "src" / "memory.metta"


def load_rag_module(monkeypatch):
def load_rag_module(monkeypatch, config=None, expected_model="text-embedding-3-large"):
created_clients = []
settings = {"GATEWAY_URL": "http://gateway:8080", **(config or {})}

class FakeEmbeddings:
def create(self, *, model, input):
assert model == "text-embedding-3-large"
assert model == expected_model
assert input == ["runtime probe"]
return types.SimpleNamespace(
data=[types.SimpleNamespace(embedding=[0.1, 0.2, 0.3])]
Expand All @@ -32,7 +33,7 @@ def __init__(self, *, base_url=None, api_key=None):
chromadb_module = types.ModuleType("chromadb")
config_module = types.ModuleType("config")
config_module.config_get_by_key = (
lambda key, default=None: "http://gateway:8080" if key == "GATEWAY_URL" else default
lambda key, default=None: settings.get(key, default)
)
llm_module = types.ModuleType("lib_llm_ext")
llm_module.initLocalEmbedding = lambda: None
Expand All @@ -59,6 +60,18 @@ def test_runtime_openai_embedding_uses_proxy_and_returns_single_vector(monkeypat
assert clients[0].api_key == "unused"


def test_runtime_embedding_uses_the_configured_provider_and_model(monkeypatch):
rag, clients = load_rag_module(
monkeypatch,
config={"embeddingprovider": "ASICloud",
"embedding_model": "WhereIsAI/UAE-Large-V1"},
expected_model="WhereIsAI/UAE-Large-V1",
)

assert rag.openai_embed("runtime probe") == [0.1, 0.2, 0.3]
assert clients[0].base_url == "http://gateway:8080/asicloud/"


def test_memory_metta_routes_openai_embeddings_to_rag_wrapper():
memory_metta = MEMORY_METTA_PATH.read_text(encoding="utf-8")

Expand Down
4 changes: 3 additions & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ maxRecallItems: 20
maxEpisodeRecallLines: 20
# Tail of `memory/history.metta` included in the prompt (chars)
maxHistory: 30000
# `Local` (Python-side model) or `OpenAI` (requires `OPENAI_API_KEY`)
# `Local` (Python-side model) or the id of a provider serving OpenAI-compatible embeddings: `OpenAI`, `ASICloud`
embeddingprovider: Local
# Model asked of a non-`Local` embeddingprovider
embedding_model: "text-embedding-3-large"

# Policy

Expand Down
3 changes: 2 additions & 1 deletion docs/reference-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ This reads a command-line override via `argk` (`name=value` on the MeTTa command
| `maxRecallItems` | 20 | Items returned by `query`. |
| `maxEpisodeRecallLines` | 20 | Lines returned by `episodes`. |
| `maxHistory` | 30000 (chars) | Tail of `memory/history.metta` included in the prompt. |
| `embeddingprovider` | `Local` | `Local` (Python-side model) or `OpenAI`. |
| `embeddingprovider` | `Local` | `Local` (Python-side model), or the id of a provider that serves an OpenAI-compatible `/embeddings` endpoint — `OpenAI` and `ASICloud` are known to. The gateway supplies that provider's key. |
| `embedding_model` | `text-embedding-3-large` | Model asked of a non-`Local` `embeddingprovider`. |

## Channels (`src/channels.metta`, `initChannels`)

Expand Down
8 changes: 6 additions & 2 deletions docs/reference-internals-extension-points.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ In `src/memory.metta`, the `embed` function dispatches on `embeddingprovider`:
(= (embed $str)
(if (== (embeddingprovider) Local)
(py-call (lib_llm_ext.useLocalEmbedding (string-safe $str)))
(useGPTEmbedding (string-safe $str))))
(py-call (rag.openai_embed (string-safe $str)))))
```

To add a new backend, add a branch and implement the Python function.
Any value other than `Local` is a provider id: the remote branch posts
`embedding_model` to `<GATEWAY_URL>/<embeddingprovider lowercased>/`, the
location that already injects that provider's key. Switching vendor is
configuration, not code — provided the vendor serves embeddings at all. It
changes the vector space, so reset the ChromaDB store when you do.

## Change the reasoning library

Expand Down
2 changes: 1 addition & 1 deletion docs/reference-skills-memory.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The result of the ChromaDB write (internally). The agent treats a successful cal

### Notes / Limits
- Text is passed through `string-safe` before embedding, which escapes newlines, quotes, and apostrophes.
- Embedding provider is selected by `embeddingprovider` (`Local` or `OpenAI`).
- Embedding provider is selected by `embeddingprovider`, the model by `embedding_model`.
- Nothing deduplicates automatically — repeated `remember` calls store multiple items.

---
Expand Down
12 changes: 12 additions & 0 deletions scripts/omegaclaw
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ help() {
echo -e "\t-l <logging config> set Python logging config file"
echo -e "\t--version, -v show the OmegaClaw version"
echo -e "\t--help, -h show this help"
echo
echo -e "Environment:"
echo -e "\tEMBEDDING_PROVIDER override the embedding backend (default depends on provider)"
echo -e "\tEMBEDDING_MODEL set the model asked of a non-Local EMBEDDING_PROVIDER"
Comment on lines +500 to +503

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this is passed via environment? Does ASI:Create start instances via ./scripts/omegaclaw script? I would recommend starting using Docker command line instead and don't touch omegaclaw script at all because it will probably be removed in nearest future.

}

options() {
Expand Down Expand Up @@ -586,6 +590,10 @@ options() {
}

start() {
# Each provider above picks a default embedding backend. EMBEDDING_PROVIDER
# overrides it, so a remote provider can serve embeddings too.
embeddingprovider="${EMBEDDING_PROVIDER:-${embeddingprovider}}"

docker rm -f omegaclaw 2>/dev/null || true
docker pull "${image}" 2>/dev/null || true

Expand Down Expand Up @@ -656,6 +664,10 @@ start() {
docker_cmd+=("openaiapi_url=${openaiapi_url}")
fi

if [ -n "${EMBEDDING_MODEL:-}" ]; then
docker_cmd+=("embedding_model=${EMBEDDING_MODEL}")
fi

if [ -n "${openclaw_url:-}" ]; then
docker_cmd+=("openclaw_url=${openclaw_url}")
docker_cmd+=("openClawEnabled=enabled")
Expand Down
6 changes: 3 additions & 3 deletions src/loop.metta
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@

(= (initKnowledge)
(progn (log INFO "loop" "Initializing knowledge base")
(if (== (embeddingprovider) OpenAI)
(log INFO "loop" (py-call (rag.init_knowledge "OpenAI")))
(log INFO "loop" (py-call (rag.init_knowledge "Local"))))))
(if (== (embeddingprovider) Local)
(log INFO "loop" (py-call (rag.init_knowledge "Local")))
(log INFO "loop" (py-call (rag.init_knowledge "OpenAI"))))))

(= (getContext)
(string-safe (py-str ("PROMPT: " (getPrompt (provider)) " SKILLS: " (getSkills)
Expand Down
8 changes: 5 additions & 3 deletions src/rag.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,16 @@ def _chunk_markdown(text, filename):
# --- Embedding -----------------------------------------------------------

def openai_embed_batch(texts):
"""Embed a list of texts via OpenAI. Returns list of float vectors."""
"""Embed a list of texts via an OpenAI-compatible API. Returns list of float vectors."""
model = config_get_by_key("embedding_model", EMBEDDING_MODEL)
proxy_url = config_get_by_key("GATEWAY_URL")
if proxy_url:
client = openai.OpenAI(base_url=f"{proxy_url.rstrip('/')}/openai/", api_key="unused")
prefix = str(config_get_by_key("embeddingprovider", "OpenAI")).lower()
client = openai.OpenAI(base_url=f"{proxy_url.rstrip('/')}/{prefix}/", api_key="unused")
else:
client = openai.OpenAI()
try:
resp = client.embeddings.create(model=EMBEDDING_MODEL, input=texts)
resp = client.embeddings.create(model=model, input=texts)
except Exception as e:
raise RuntimeError(f"Embedding request failed: {e}") from e
return [item.embedding for item in resp.data]
Expand Down
Loading