- run_task_brief_research() runner with brief-specific tool set and max_steps=12 - New agents: client_agent (list_clients, get_client) and relations_agent (query_relations) - search_associative tool wrapping MemoryMiddleware semantic search - BRIEF_RESEARCH_TOOLS constant: read-only task/project/note/timeline + memory + client/relations - canvas block extraction in output_formatter (splits visible text from <canvas> draft) - device_ws.py: task_brief_research request type; emits canvas_draft mutation on stream_end - Stage 2 briefMode: briefing_context injected into floating system prompt when present - briefingContext kwarg wired through compile_prompt call chain Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.3 KiB
Python
64 lines
2.3 KiB
Python
"""Relations agent — read-only tool wrapping MemoryMiddleware.query_relations."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from app.core.memory_middleware import MemoryMiddleware
|
|
from app.db import async_session
|
|
|
|
# Injected at tool-factory time by _brief_research_tools(); not a module-level global.
|
|
# Each tool closure captures the user_id bound at factory time.
|
|
|
|
|
|
def make_query_relations_tool(user_id: str, trace_id: str | None = None) -> Any:
|
|
"""Return a query_relations tool bound to *user_id*."""
|
|
|
|
@tool
|
|
async def query_relations(
|
|
subject_label: str = "",
|
|
predicate: str = "",
|
|
object_label: str = "",
|
|
limit: int = 10,
|
|
) -> str:
|
|
"""Query the relational memory graph for entity relationships.
|
|
|
|
Returns rows where subject ↔ predicate ↔ object match the given filters.
|
|
All parameters are optional — omit to retrieve all relations up to limit.
|
|
|
|
subject_label: entity label on the left side (e.g. a client name, "Acme Corp").
|
|
predicate: relationship type (e.g. "mentioned_in", "works_at", "related_to").
|
|
object_label: entity label on the right side (e.g. a project name, "Website Redesign").
|
|
limit: max rows to return (default 10).
|
|
"""
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
logger.info(
|
|
"relations_agent: query_relations trace=%s user=%s subject=%r predicate=%r object=%r",
|
|
trace_id or "-", user_id, subject_label, predicate, object_label,
|
|
)
|
|
|
|
async with async_session() as db:
|
|
memory = MemoryMiddleware(db)
|
|
rows = await memory.query_relations(
|
|
user_id=user_id,
|
|
subject=subject_label or None,
|
|
predicate=predicate or None,
|
|
object_=object_label or None,
|
|
limit=limit,
|
|
)
|
|
|
|
if not rows:
|
|
return "No relational memory entries found for the given filters."
|
|
|
|
lines = [
|
|
f"- {r.subject_label} —[{r.predicate}]→ {r.object_label}"
|
|
+ (f" (confidence: {r.confidence:.2f})" if r.confidence is not None else "")
|
|
for r in rows
|
|
]
|
|
return f"Found {len(rows)} relation(s):\n" + "\n".join(lines)
|
|
|
|
return query_relations
|