mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-10-20 05:15:31 +00:00
feat(supermemory): support custom base URL for self-hosted servers
The supermemory SDK already honors SUPERMEMORY_BASE_URL, but the raw urllib call used for session-end conversation ingest hardcoded https://api.supermemory.ai/v4/conversations, so ingest always hit the cloud even when pointing at a self-hosted server (e.g. http://localhost:6767). Resolve the base URL as config (supermemory.json base_url) > SUPERMEMORY_BASE_URL env var > https://api.supermemory.ai, strip any trailing slash, and use it for both the SDK client and the /v4/conversations ingest endpoint.
This commit is contained in:
parent
244dabbd9c
commit
24ac26a3da
2 changed files with 90 additions and 5 deletions
|
|
@ -31,7 +31,7 @@ _VALID_SEARCH_MODES = ("hybrid", "memories", "documents")
|
|||
_DEFAULT_API_TIMEOUT = 5.0
|
||||
_MIN_CAPTURE_LENGTH = 10
|
||||
_MAX_ENTITY_CONTEXT_LENGTH = 1500
|
||||
_CONVERSATIONS_URL = "https://api.supermemory.ai/v4/conversations"
|
||||
_DEFAULT_BASE_URL = "https://api.supermemory.ai"
|
||||
_API_KEY_URL = "http://app.supermemory.ai/integrations?connect=hermes"
|
||||
_TRIVIAL_RE = re.compile(
|
||||
r"^(ok|okay|thanks|thank you|got it|sure|yes|no|yep|nope|k|ty|thx|np)\.?$",
|
||||
|
|
@ -65,6 +65,7 @@ def _default_config() -> dict:
|
|||
"search_mode": _DEFAULT_SEARCH_MODE,
|
||||
"entity_context": _DEFAULT_ENTITY_CONTEXT,
|
||||
"api_timeout": _DEFAULT_API_TIMEOUT,
|
||||
"base_url": "",
|
||||
"enable_custom_container_tags": False,
|
||||
"custom_containers": [],
|
||||
"custom_container_instructions": "",
|
||||
|
|
@ -77,6 +78,15 @@ def _sanitize_tag(raw: str) -> str:
|
|||
return tag.strip("_") or _DEFAULT_CONTAINER_TAG
|
||||
|
||||
|
||||
def _resolve_base_url(config_value: Any = "") -> str:
|
||||
"""Resolve the API base URL: config > SUPERMEMORY_BASE_URL env var > default.
|
||||
|
||||
Supports self-hosted Supermemory servers (e.g. http://localhost:6767).
|
||||
"""
|
||||
raw = str(config_value or "").strip() or os.environ.get("SUPERMEMORY_BASE_URL", "").strip()
|
||||
return (raw or _DEFAULT_BASE_URL).rstrip("/") or _DEFAULT_BASE_URL
|
||||
|
||||
|
||||
def _clamp_entity_context(text: str) -> str:
|
||||
if not text:
|
||||
return _DEFAULT_ENTITY_CONTEXT
|
||||
|
|
@ -129,6 +139,7 @@ def _load_supermemory_config(hermes_home: str) -> dict:
|
|||
config["api_timeout"] = max(0.5, min(15.0, float(config.get("api_timeout", _DEFAULT_API_TIMEOUT))))
|
||||
except Exception:
|
||||
config["api_timeout"] = _DEFAULT_API_TIMEOUT
|
||||
config["base_url"] = str(config.get("base_url", "") or "").strip()
|
||||
|
||||
# Multi-container support
|
||||
config["enable_custom_container_tags"] = _as_bool(config.get("enable_custom_container_tags"), False)
|
||||
|
|
@ -263,7 +274,8 @@ def _is_trivial_message(text: str) -> bool:
|
|||
|
||||
|
||||
class _SupermemoryClient:
|
||||
def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode: str = "hybrid"):
|
||||
def __init__(self, api_key: str, timeout: float, container_tag: str, search_mode: str = "hybrid",
|
||||
base_url: str = ""):
|
||||
# Lazy-install the supermemory SDK on demand. ensure() honors
|
||||
# security.allow_lazy_installs (default true) and, on a sealed Docker
|
||||
# venv, redirects the install to the durable target. On failure we
|
||||
|
|
@ -276,15 +288,16 @@ class _SupermemoryClient:
|
|||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
from supermemory import Supermemory
|
||||
|
||||
self._api_key = api_key
|
||||
self._container_tag = container_tag
|
||||
self._search_mode = search_mode if search_mode in _VALID_SEARCH_MODES else _DEFAULT_SEARCH_MODE
|
||||
self._timeout = timeout
|
||||
self._base_url = (base_url or _DEFAULT_BASE_URL).rstrip("/") or _DEFAULT_BASE_URL
|
||||
self._client = Supermemory(
|
||||
api_key=api_key,
|
||||
base_url=self._base_url,
|
||||
timeout=timeout,
|
||||
max_retries=0,
|
||||
default_headers={"x-sm-source": "hermes"},
|
||||
|
|
@ -388,7 +401,7 @@ class _SupermemoryClient:
|
|||
payload["metadata"] = self._merge_metadata(metadata)
|
||||
|
||||
req = urllib.request.Request(
|
||||
_CONVERSATIONS_URL,
|
||||
f"{self._base_url}/v4/conversations",
|
||||
data=json.dumps(payload).encode("utf-8"),
|
||||
headers={
|
||||
"Authorization": f"Bearer {self._api_key}",
|
||||
|
|
@ -531,6 +544,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
|
|||
self._search_mode = _DEFAULT_SEARCH_MODE
|
||||
self._entity_context = _DEFAULT_ENTITY_CONTEXT
|
||||
self._api_timeout = _DEFAULT_API_TIMEOUT
|
||||
self._base_url = _DEFAULT_BASE_URL
|
||||
self._hermes_home = ""
|
||||
self._write_enabled = True
|
||||
self._active = False
|
||||
|
|
@ -645,6 +659,9 @@ class SupermemoryMemoryProvider(MemoryProvider):
|
|||
self._search_mode = self._config["search_mode"]
|
||||
self._entity_context = self._config["entity_context"]
|
||||
self._api_timeout = self._config["api_timeout"]
|
||||
# Base URL: config > SUPERMEMORY_BASE_URL env var > api.supermemory.ai.
|
||||
# Supports self-hosted Supermemory servers.
|
||||
self._base_url = _resolve_base_url(self._config["base_url"])
|
||||
self._enable_custom_containers = self._config["enable_custom_container_tags"]
|
||||
self._custom_containers = self._config["custom_containers"]
|
||||
self._custom_container_instructions = self._config["custom_container_instructions"]
|
||||
|
|
@ -663,6 +680,7 @@ class SupermemoryMemoryProvider(MemoryProvider):
|
|||
timeout=self._api_timeout,
|
||||
container_tag=self._container_tag,
|
||||
search_mode=self._search_mode,
|
||||
base_url=self._base_url,
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("Supermemory initialization failed", exc_info=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue