|
|
|
@@ -1,13 +1,16 @@
|
|
|
|
"""Deep Agent — LangGraph hierarchical supervisors for home and floating modes.
|
|
|
|
"""Deep Agent — ``create_deep_agent`` supervisors for home and floating modes.
|
|
|
|
|
|
|
|
|
|
|
|
Two supervisor graphs (both ``create_react_agent``):
|
|
|
|
Two supervisor graphs (via ``deepagents.create_deep_agent``):
|
|
|
|
* **HomeSupervisor** — gathers data from multiple domains, presents
|
|
|
|
* **HomeSupervisor** — gathers data from multiple domains, presents
|
|
|
|
structured overview with tool-result blocks.
|
|
|
|
structured overview with entity/chart tags.
|
|
|
|
* **FloatingSupervisor** — focused, scoped assistant for a single entity/domain.
|
|
|
|
* **FloatingSupervisor** — focused, scoped assistant for a single entity/domain.
|
|
|
|
|
|
|
|
|
|
|
|
Each supervisor delegates to four sub-agent tools, each a compiled
|
|
|
|
Each supervisor delegates to four sub-agents (task, project, note, timeline)
|
|
|
|
``create_react_agent`` wrapping the domain CRUD tools (task, project, note,
|
|
|
|
via the built-in ``task`` tool provided by ``SubAgentMiddleware``.
|
|
|
|
timeline). The sub-agents talk to Electron via ``execute_on_client``.
|
|
|
|
The sub-agents talk to Electron via ``execute_on_client``.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Built-in middleware provides: todo-list tracking, virtual filesystem,
|
|
|
|
|
|
|
|
automatic context summarisation, prompt-caching, and tool-call patching.
|
|
|
|
|
|
|
|
|
|
|
|
Streaming uses ``astream(stream_mode=["messages", "updates"])`` so that
|
|
|
|
Streaming uses ``astream(stream_mode=["messages", "updates"])`` so that
|
|
|
|
callers can sniff:
|
|
|
|
callers can sniff:
|
|
|
|
@@ -24,9 +27,9 @@ import json
|
|
|
|
import logging
|
|
|
|
import logging
|
|
|
|
from typing import Any, AsyncGenerator
|
|
|
|
from typing import Any, AsyncGenerator
|
|
|
|
|
|
|
|
|
|
|
|
from langchain_core.messages import AIMessageChunk, HumanMessage
|
|
|
|
from deepagents import create_deep_agent
|
|
|
|
|
|
|
|
from langchain_core.messages import AIMessage, AIMessageChunk, HumanMessage
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from langchain_core.tools import tool
|
|
|
|
from langgraph.prebuilt import create_react_agent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from app.core.llm import get_llm
|
|
|
|
from app.core.llm import get_llm
|
|
|
|
from app.core.ws_context import (
|
|
|
|
from app.core.ws_context import (
|
|
|
|
@@ -97,45 +100,24 @@ _PROJECT_TOOLS = [
|
|
|
|
_TIMELINE_TOOLS = [list_timelines, create_timeline, update_timeline, delete_timeline]
|
|
|
|
_TIMELINE_TOOLS = [list_timelines, create_timeline, update_timeline, delete_timeline]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _build_subagent_tool(
|
|
|
|
def _make_subagent_specs() -> list[dict[str, Any]]:
|
|
|
|
name: str,
|
|
|
|
"""Return SubAgent dicts for the four workspace domains.
|
|
|
|
description: str,
|
|
|
|
|
|
|
|
system_prompt: str,
|
|
|
|
|
|
|
|
tools: list,
|
|
|
|
|
|
|
|
):
|
|
|
|
|
|
|
|
"""Build a compiled sub-agent graph and wrap it as a LangChain tool."""
|
|
|
|
|
|
|
|
subgraph = create_react_agent(
|
|
|
|
|
|
|
|
model=get_llm(),
|
|
|
|
|
|
|
|
tools=tools,
|
|
|
|
|
|
|
|
prompt=system_prompt,
|
|
|
|
|
|
|
|
name=name,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@tool(name, description=description)
|
|
|
|
Each dict follows the ``deepagents`` ``SubAgent`` TypedDict:
|
|
|
|
async def _run(query: str) -> str:
|
|
|
|
name, description, system_prompt, tools, model
|
|
|
|
result = await subgraph.ainvoke(
|
|
|
|
The model and middleware are filled in by ``create_deep_agent`` automatically.
|
|
|
|
{"messages": [HumanMessage(content=query)]}
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
llm = get_llm()
|
|
|
|
messages = result["messages"]
|
|
|
|
|
|
|
|
# Return the last AI message content
|
|
|
|
|
|
|
|
for msg in reversed(messages):
|
|
|
|
|
|
|
|
if hasattr(msg, "content") and msg.content and not getattr(msg, "tool_calls", None):
|
|
|
|
|
|
|
|
return str(msg.content)
|
|
|
|
|
|
|
|
return "No response from sub-agent."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return _run
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _make_subagent_tools() -> list:
|
|
|
|
|
|
|
|
"""Create the four sub-agent tools for the supervisor."""
|
|
|
|
|
|
|
|
return [
|
|
|
|
return [
|
|
|
|
_build_subagent_tool(
|
|
|
|
{
|
|
|
|
name="task_agent",
|
|
|
|
"name": "task_agent",
|
|
|
|
description=(
|
|
|
|
"description": (
|
|
|
|
"Manages tasks and comments: list, create, update, delete, "
|
|
|
|
"Manages tasks and comments: list, create, update, delete, "
|
|
|
|
"due-today, comments. Delegate task-related queries here."
|
|
|
|
"due-today, and comments. Use when the user asks about tasks, "
|
|
|
|
|
|
|
|
"to-dos, assignments, deadlines, or anything task-related."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
system_prompt=(
|
|
|
|
"system_prompt": (
|
|
|
|
"You are a task management assistant. You create, update, list, "
|
|
|
|
"You are a task management assistant. You create, update, list, "
|
|
|
|
"and track tasks and their comments.\n\n"
|
|
|
|
"and track tasks and their comments.\n\n"
|
|
|
|
"Rules:\n"
|
|
|
|
"Rules:\n"
|
|
|
|
@@ -147,15 +129,15 @@ def _make_subagent_tools() -> list:
|
|
|
|
" - For update_task, use -1 for integer fields you do not want to change\n"
|
|
|
|
" - For update_task, use -1 for integer fields you do not want to change\n"
|
|
|
|
" - Always confirm the action in plain, user-friendly language."
|
|
|
|
" - Always confirm the action in plain, user-friendly language."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
tools=_TASK_TOOLS,
|
|
|
|
"tools": _TASK_TOOLS
|
|
|
|
),
|
|
|
|
},
|
|
|
|
_build_subagent_tool(
|
|
|
|
{
|
|
|
|
name="note_agent",
|
|
|
|
"name": "note_agent",
|
|
|
|
description=(
|
|
|
|
"description": (
|
|
|
|
"Manages notes: list, get, create, update, delete. "
|
|
|
|
"Manages notes: list, get, create, update, delete. "
|
|
|
|
"Delegate note-related queries here."
|
|
|
|
"Use when the user asks about notes, documents, or written content."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
system_prompt=(
|
|
|
|
"system_prompt": (
|
|
|
|
"You are a note-taking assistant. You help users create, retrieve, "
|
|
|
|
"You are a note-taking assistant. You help users create, retrieve, "
|
|
|
|
"update, and delete Markdown notes in their workspace.\n\n"
|
|
|
|
"update, and delete Markdown notes in their workspace.\n\n"
|
|
|
|
"Rules:\n"
|
|
|
|
"Rules:\n"
|
|
|
|
@@ -164,15 +146,15 @@ def _make_subagent_tools() -> list:
|
|
|
|
"content before appending or replacing sections\n"
|
|
|
|
"content before appending or replacing sections\n"
|
|
|
|
" - Do not fabricate note content."
|
|
|
|
" - Do not fabricate note content."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
tools=_NOTE_TOOLS,
|
|
|
|
"tools": _NOTE_TOOLS
|
|
|
|
),
|
|
|
|
},
|
|
|
|
_build_subagent_tool(
|
|
|
|
{
|
|
|
|
name="project_agent",
|
|
|
|
"name": "project_agent",
|
|
|
|
description=(
|
|
|
|
"description": (
|
|
|
|
"Manages projects: list, get, create, update, archive, delete. "
|
|
|
|
"Manages projects: list, get, create, update, archive, delete. "
|
|
|
|
"Delegate project-related queries here."
|
|
|
|
"Use when the user asks about projects, workspaces, or project status."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
system_prompt=(
|
|
|
|
"system_prompt": (
|
|
|
|
"You are a project management assistant. You help users create, "
|
|
|
|
"You are a project management assistant. You help users create, "
|
|
|
|
"find, update, and archive projects.\n\n"
|
|
|
|
"find, update, and archive projects.\n\n"
|
|
|
|
"Rules:\n"
|
|
|
|
"Rules:\n"
|
|
|
|
@@ -180,15 +162,16 @@ def _make_subagent_tools() -> list:
|
|
|
|
" - Prefer archiving over deletion\n"
|
|
|
|
" - Prefer archiving over deletion\n"
|
|
|
|
" - ai_summary is populated only when the user asks for a summary."
|
|
|
|
" - ai_summary is populated only when the user asks for a summary."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
tools=_PROJECT_TOOLS,
|
|
|
|
"tools": _PROJECT_TOOLS
|
|
|
|
),
|
|
|
|
},
|
|
|
|
_build_subagent_tool(
|
|
|
|
{
|
|
|
|
name="timeline_agent",
|
|
|
|
"name": "timeline_agent",
|
|
|
|
description=(
|
|
|
|
"description": (
|
|
|
|
"Manages project timelines (milestones): list, create, update, "
|
|
|
|
"Manages project timelines and milestones: list, create, update, "
|
|
|
|
"delete. Delegate timeline/milestone queries here."
|
|
|
|
"delete. Use when the user asks about timelines, milestones, "
|
|
|
|
|
|
|
|
"deadlines, or project scheduling."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
system_prompt=(
|
|
|
|
"system_prompt": (
|
|
|
|
"You are a project timeline assistant. Timelines are milestone "
|
|
|
|
"You are a project timeline assistant. Timelines are milestone "
|
|
|
|
"dates that track progress on a project.\n\n"
|
|
|
|
"dates that track progress on a project.\n\n"
|
|
|
|
"Rules:\n"
|
|
|
|
"Rules:\n"
|
|
|
|
@@ -197,8 +180,8 @@ def _make_subagent_tools() -> list:
|
|
|
|
" - For update_timeline, use -1 for integer fields you do not "
|
|
|
|
" - For update_timeline, use -1 for integer fields you do not "
|
|
|
|
"want to change."
|
|
|
|
"want to change."
|
|
|
|
),
|
|
|
|
),
|
|
|
|
tools=_TIMELINE_TOOLS,
|
|
|
|
"tools": _TIMELINE_TOOLS
|
|
|
|
),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -230,12 +213,17 @@ _HOME_SYSTEM = (
|
|
|
|
"You are Adiuva, a smart workspace assistant on the Home dashboard.\n"
|
|
|
|
"You are Adiuva, a smart workspace assistant on the Home dashboard.\n"
|
|
|
|
"Your job is to help the user by gathering data from their workspace and "
|
|
|
|
"Your job is to help the user by gathering data from their workspace and "
|
|
|
|
"presenting a comprehensive overview.\n\n"
|
|
|
|
"presenting a comprehensive overview.\n\n"
|
|
|
|
"You have sub-agent tools (task_agent, note_agent, project_agent, "
|
|
|
|
"You have sub-agents (task_agent, note_agent, project_agent, "
|
|
|
|
"timeline_agent) that can query and mutate workspace data. Delegate to "
|
|
|
|
"timeline_agent) accessible via the `task` tool. Delegate to "
|
|
|
|
"the appropriate sub-agent(s) based on the user's request. You can call "
|
|
|
|
"the appropriate sub-agent(s) based on the user's request. You can call "
|
|
|
|
"multiple sub-agents if needed.\n\n"
|
|
|
|
"multiple sub-agents in parallel if needed.\n\n"
|
|
|
|
"You also have an update_core_memory tool — use it when the user states "
|
|
|
|
"You also have an update_core_memory tool — use it when the user states "
|
|
|
|
"a preference or important fact worth remembering long-term.\n\n"
|
|
|
|
"a preference or important fact worth remembering long-term.\n\n"
|
|
|
|
|
|
|
|
"IMPORTANT: You do NOT have direct access to workspace data. Always "
|
|
|
|
|
|
|
|
"delegate to your subagents using the task() tool. Do not attempt to "
|
|
|
|
|
|
|
|
"answer workspace queries yourself — the subagents have the tools to "
|
|
|
|
|
|
|
|
"fetch and modify data. You can call multiple subagents in parallel "
|
|
|
|
|
|
|
|
"when the request spans multiple domains.\n\n"
|
|
|
|
"## Entity References\n"
|
|
|
|
"## Entity References\n"
|
|
|
|
"When your response mentions specific workspace entities, embed them "
|
|
|
|
"When your response mentions specific workspace entities, embed them "
|
|
|
|
"inline using entity tags so the UI can render interactive components.\n"
|
|
|
|
"inline using entity tags so the UI can render interactive components.\n"
|
|
|
|
@@ -272,12 +260,16 @@ _FLOATING_SYSTEM = (
|
|
|
|
"You are Adiuva, a focused workspace assistant in the floating panel.\n"
|
|
|
|
"You are Adiuva, a focused workspace assistant in the floating panel.\n"
|
|
|
|
"The user is currently working in the '{scope_type}' section"
|
|
|
|
"The user is currently working in the '{scope_type}' section"
|
|
|
|
"{scope_detail}.\n\n"
|
|
|
|
"{scope_detail}.\n\n"
|
|
|
|
"You have sub-agent tools (task_agent, note_agent, project_agent, "
|
|
|
|
"You have sub-agents (task_agent, note_agent, project_agent, "
|
|
|
|
"timeline_agent) that can query and mutate workspace data. Focus your "
|
|
|
|
"timeline_agent) accessible via the `task` tool. Focus your "
|
|
|
|
"help on the user's current scope, but you can use other sub-agents "
|
|
|
|
"help on the user's current scope, but you can use other sub-agents "
|
|
|
|
"if the request requires it.\n\n"
|
|
|
|
"if the request requires it.\n\n"
|
|
|
|
"You also have an update_core_memory tool — use it when the user states "
|
|
|
|
"You also have an update_core_memory tool — use it when the user states "
|
|
|
|
"a preference or important fact worth remembering long-term.\n\n"
|
|
|
|
"a preference or important fact worth remembering long-term.\n\n"
|
|
|
|
|
|
|
|
"IMPORTANT: You do NOT have direct access to workspace data. Always "
|
|
|
|
|
|
|
|
"delegate to your subagents using the task() tool. Do not attempt to "
|
|
|
|
|
|
|
|
"answer workspace queries yourself — the subagents have the tools to "
|
|
|
|
|
|
|
|
"fetch and modify data.\n\n"
|
|
|
|
"Provide direct, conversational responses.\n\n"
|
|
|
|
"Provide direct, conversational responses.\n\n"
|
|
|
|
"Memory context:\n{memory_context}"
|
|
|
|
"Memory context:\n{memory_context}"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@@ -307,18 +299,18 @@ def build_home_graph(
|
|
|
|
db_session_factory,
|
|
|
|
db_session_factory,
|
|
|
|
):
|
|
|
|
):
|
|
|
|
"""Build the Home supervisor graph."""
|
|
|
|
"""Build the Home supervisor graph."""
|
|
|
|
subagent_tools = _make_subagent_tools()
|
|
|
|
subagent_specs = _make_subagent_specs()
|
|
|
|
memory_tool = _make_update_core_memory_tool(user_id, db_session_factory)
|
|
|
|
memory_tool = _make_update_core_memory_tool(user_id, db_session_factory)
|
|
|
|
all_tools = subagent_tools + [memory_tool]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prompt = _HOME_SYSTEM.format(
|
|
|
|
prompt = _HOME_SYSTEM.format(
|
|
|
|
memory_context=_format_memory_context(memory_context),
|
|
|
|
memory_context=_format_memory_context(memory_context),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return create_react_agent(
|
|
|
|
return create_deep_agent(
|
|
|
|
model=get_llm(),
|
|
|
|
model=get_llm(),
|
|
|
|
tools=all_tools,
|
|
|
|
tools=[memory_tool],
|
|
|
|
prompt=prompt,
|
|
|
|
system_prompt=prompt,
|
|
|
|
|
|
|
|
subagents=subagent_specs,
|
|
|
|
name="home_supervisor",
|
|
|
|
name="home_supervisor",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -330,9 +322,8 @@ def build_floating_graph(
|
|
|
|
db_session_factory,
|
|
|
|
db_session_factory,
|
|
|
|
):
|
|
|
|
):
|
|
|
|
"""Build the Floating supervisor graph."""
|
|
|
|
"""Build the Floating supervisor graph."""
|
|
|
|
subagent_tools = _make_subagent_tools()
|
|
|
|
subagent_specs = _make_subagent_specs()
|
|
|
|
memory_tool = _make_update_core_memory_tool(user_id, db_session_factory)
|
|
|
|
memory_tool = _make_update_core_memory_tool(user_id, db_session_factory)
|
|
|
|
all_tools = subagent_tools + [memory_tool]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
scope_type = scope.get("type", "general")
|
|
|
|
scope_type = scope.get("type", "general")
|
|
|
|
scope_id = scope.get("id")
|
|
|
|
scope_id = scope.get("id")
|
|
|
|
@@ -344,10 +335,11 @@ def build_floating_graph(
|
|
|
|
memory_context=_format_memory_context(memory_context),
|
|
|
|
memory_context=_format_memory_context(memory_context),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return create_react_agent(
|
|
|
|
return create_deep_agent(
|
|
|
|
model=get_llm(),
|
|
|
|
model=get_llm(),
|
|
|
|
tools=all_tools,
|
|
|
|
tools=[memory_tool],
|
|
|
|
prompt=prompt,
|
|
|
|
system_prompt=prompt,
|
|
|
|
|
|
|
|
subagents=subagent_specs,
|
|
|
|
name="floating_supervisor",
|
|
|
|
name="floating_supervisor",
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@@ -382,14 +374,53 @@ async def _run_graph_stream(
|
|
|
|
):
|
|
|
|
):
|
|
|
|
if stream_mode == "messages":
|
|
|
|
if stream_mode == "messages":
|
|
|
|
msg, metadata = chunk
|
|
|
|
msg, metadata = chunk
|
|
|
|
|
|
|
|
agent_name = (
|
|
|
|
|
|
|
|
metadata.get("lc_agent_name", "?")
|
|
|
|
|
|
|
|
if isinstance(metadata, dict) else "?"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
node = (
|
|
|
|
|
|
|
|
metadata.get("langgraph_node", "?")
|
|
|
|
|
|
|
|
if isinstance(metadata, dict) else "?"
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Log every message event with agent attribution
|
|
|
|
|
|
|
|
if isinstance(msg, (AIMessage, AIMessageChunk)) and msg.content:
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
|
|
"[%s] %s node=%s content=%s",
|
|
|
|
|
|
|
|
agent_name,
|
|
|
|
|
|
|
|
type(msg).__name__,
|
|
|
|
|
|
|
|
node,
|
|
|
|
|
|
|
|
str(msg.content),
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
elif isinstance(msg, (AIMessage, AIMessageChunk)) and msg.tool_calls:
|
|
|
|
|
|
|
|
tool_names = [tc["name"] for tc in msg.tool_calls]
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
|
|
"[%s] %s node=%s tool_calls=%s",
|
|
|
|
|
|
|
|
agent_name,
|
|
|
|
|
|
|
|
type(msg).__name__,
|
|
|
|
|
|
|
|
node,
|
|
|
|
|
|
|
|
tool_names,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
elif hasattr(msg, "name") and hasattr(msg, "content") and msg.content:
|
|
|
|
|
|
|
|
# ToolMessage — log tool result
|
|
|
|
|
|
|
|
logger.info(
|
|
|
|
|
|
|
|
"[%s] ToolMessage tool=%s node=%s result=%s",
|
|
|
|
|
|
|
|
agent_name,
|
|
|
|
|
|
|
|
getattr(msg, "name", "?"),
|
|
|
|
|
|
|
|
node,
|
|
|
|
|
|
|
|
str(msg.content),
|
|
|
|
|
|
|
|
)
|
|
|
|
# Only yield tokens from the supervisor's final response
|
|
|
|
# Only yield tokens from the supervisor's final response
|
|
|
|
# (not from sub-agent internal LLM calls)
|
|
|
|
# (not from sub-agent internal LLM calls).
|
|
|
|
|
|
|
|
# Accept both AIMessageChunk (streamed tokens) and AIMessage
|
|
|
|
|
|
|
|
# (full response from non-streaming providers).
|
|
|
|
|
|
|
|
# create_deep_agent names the LLM node "model".
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
isinstance(msg, AIMessageChunk)
|
|
|
|
isinstance(msg, (AIMessage, AIMessageChunk))
|
|
|
|
and msg.content
|
|
|
|
and msg.content
|
|
|
|
and not msg.tool_calls
|
|
|
|
and not msg.tool_calls
|
|
|
|
and isinstance(metadata, dict)
|
|
|
|
and isinstance(metadata, dict)
|
|
|
|
and metadata.get("langgraph_node") == "agent"
|
|
|
|
and metadata.get("langgraph_node") == "model"
|
|
|
|
):
|
|
|
|
):
|
|
|
|
yield ("token", str(msg.content))
|
|
|
|
yield ("token", str(msg.content))
|
|
|
|
|
|
|
|
|
|
|
|
|