Skip to content

Commit 4d5b285

Browse files
gaodan-fangvisahak
andauthored
refactor(mcp): extract shared persistence helper for entity writes (#254)
Collapse the duplicated update_entities + NamespaceNotFoundException retry pattern in store_user_facts, save_trajectory, and create_entity into a single _persist_entities helper. New entity-writing tools can now reuse the helper for consistent error handling. Closes #241 Co-authored-by: Vatche Isahagian <visahak@users.noreply.github.com>
1 parent 92b52f9 commit 4d5b285

1 file changed

Lines changed: 47 additions & 42 deletions

File tree

altk_evolve/frontend/mcp/mcp_server.py

Lines changed: 47 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
extract_facts_from_messages,
2727
)
2828
from altk_evolve.llm.guidelines.guidelines import generate_guidelines
29+
from altk_evolve.schema.conflict_resolution import EntityUpdate
2930
from altk_evolve.schema.core import Entity, RecordedEntity
3031
from altk_evolve.schema.exceptions import EvolveException, NamespaceNotFoundException
3132

@@ -236,6 +237,36 @@ def _parse_metadata(metadata: str | None) -> dict[str, Any]:
236237
return parsed
237238

238239

240+
def _persist_entities(
241+
namespace_id: str | None,
242+
entities: list[Entity],
243+
enable_conflict_resolution: bool = False,
244+
) -> tuple[list[EntityUpdate], str]:
245+
"""Persist entities with a single retry if the namespace cache is stale.
246+
247+
Resolves ``namespace_id`` (falling back to the configured default), writes
248+
via ``update_entities``, and on ``NamespaceNotFoundException`` evicts the
249+
cached entry, re-resolves, and retries once. Returns the update records
250+
and the namespace actually written to.
251+
"""
252+
resolved_ns = _resolve_namespace(namespace_id)
253+
try:
254+
updates = get_client().update_entities(
255+
namespace_id=resolved_ns,
256+
entities=entities,
257+
enable_conflict_resolution=enable_conflict_resolution,
258+
)
259+
except NamespaceNotFoundException:
260+
_evict_namespace(resolved_ns)
261+
resolved_ns = _resolve_namespace(namespace_id)
262+
updates = get_client().update_entities(
263+
namespace_id=resolved_ns,
264+
entities=entities,
265+
enable_conflict_resolution=enable_conflict_resolution,
266+
)
267+
return updates, resolved_ns
268+
269+
239270
@mcp.tool()
240271
def get_entities(
241272
task: str,
@@ -310,8 +341,6 @@ def store_user_facts(
310341
if not trimmed_message:
311342
return _empty_store_user_facts_response(user_id)
312343

313-
resolved_ns = _resolve_namespace(None)
314-
315344
base_metadata: dict[str, Any] = dict(metadata_dict)
316345
base_metadata["user_id"] = user_id
317346

@@ -330,20 +359,11 @@ def store_user_facts(
330359
if not entities:
331360
return _empty_store_user_facts_response(user_id)
332361

333-
try:
334-
updates = get_client().update_entities(
335-
namespace_id=resolved_ns,
336-
entities=entities,
337-
enable_conflict_resolution=enable_conflict_resolution,
338-
)
339-
except NamespaceNotFoundException:
340-
_evict_namespace(resolved_ns)
341-
resolved_ns = _resolve_namespace(None)
342-
updates = get_client().update_entities(
343-
namespace_id=resolved_ns,
344-
entities=entities,
345-
enable_conflict_resolution=enable_conflict_resolution,
346-
)
362+
updates, _ = _persist_entities(
363+
namespace_id=None,
364+
entities=entities,
365+
enable_conflict_resolution=enable_conflict_resolution,
366+
)
347367

348368
serialized_updates = [
349369
{
@@ -487,20 +507,11 @@ def save_trajectory(
487507
)
488508
)
489509

490-
try:
491-
get_client().update_entities(
492-
namespace_id=resolved_ns,
493-
entities=entities,
494-
enable_conflict_resolution=False,
495-
)
496-
except NamespaceNotFoundException:
497-
_evict_namespace(resolved_ns)
498-
resolved_ns = _resolve_namespace(namespace_id)
499-
get_client().update_entities(
500-
namespace_id=resolved_ns,
501-
entities=entities,
502-
enable_conflict_resolution=False,
503-
)
510+
_, resolved_ns = _persist_entities(
511+
namespace_id=namespace_id,
512+
entities=entities,
513+
enable_conflict_resolution=False,
514+
)
504515
results = generate_guidelines(messages)
505516

506517
guideline_metadata_base: dict = {
@@ -574,8 +585,7 @@ def create_entity(
574585
Returns:
575586
JSON string with the entity update details (ADD/UPDATE/DELETE/NONE) and entity ID
576587
"""
577-
resolved_ns = _resolve_namespace(namespace_id)
578-
logger.info(f"Creating entity of type: {entity_type} in namespace: {resolved_ns}")
588+
logger.info(f"Creating entity of type: {entity_type} (namespace override: {namespace_id})")
579589
try:
580590
if visibility not in ("private", "public"):
581591
return json.dumps({"error": f"Invalid visibility '{visibility}': must be 'private' or 'public'"})
@@ -611,16 +621,11 @@ def create_entity(
611621

612622
entity = Entity(type=entity_type, content=content, metadata=metadata_dict)
613623

614-
try:
615-
updates = get_client().update_entities(
616-
namespace_id=resolved_ns, entities=[entity], enable_conflict_resolution=enable_conflict_resolution
617-
)
618-
except NamespaceNotFoundException:
619-
_evict_namespace(resolved_ns)
620-
resolved_ns = _resolve_namespace(namespace_id)
621-
updates = get_client().update_entities(
622-
namespace_id=resolved_ns, entities=[entity], enable_conflict_resolution=enable_conflict_resolution
623-
)
624+
updates, _ = _persist_entities(
625+
namespace_id=namespace_id,
626+
entities=[entity],
627+
enable_conflict_resolution=enable_conflict_resolution,
628+
)
624629

625630
if updates:
626631
update = updates[0]

0 commit comments

Comments
 (0)