feat: deep-agent canvas, live observability, and multi-environment tooling
Self-hosted platform for building, testing, and shipping LangChain/LangGraph agents. Deep-agent sub-agents on the canvas, a live tracing/observability timeline, auto-provisioned built-in tools with import/export, per-environment tool variables, streamed evaluations, and per-user auth token forwarding.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""Live-agent handoff: a channel run pauses at a handoff node, opens a queue item,
|
||||
and an agent reply resumes the run."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from langgraph.checkpoint.memory import InMemorySaver
|
||||
|
||||
from forge.db.base import SessionLocal
|
||||
from forge.models import HandoffRequest, Workflow
|
||||
from forge.services.channels import ChannelService
|
||||
from forge.services.dispatch import dispatch_message
|
||||
from forge.services.handoff import HandoffService
|
||||
from forge.services.runs import RunService
|
||||
|
||||
_WF = {
|
||||
"id": "wf_h", "version": 1,
|
||||
"state": {"messages": {"type": "list[message]", "reducer": "add_messages"}},
|
||||
"entry_node": "handoff",
|
||||
"nodes": [
|
||||
{"id": "handoff", "type": "handoff", "config": {"reason": "needs a human", "ack_message": "Hold on, connecting you."}},
|
||||
{"id": "end", "type": "end", "config": {}},
|
||||
],
|
||||
"edges": [{"source": "handoff", "target": "end"}],
|
||||
}
|
||||
|
||||
|
||||
async def test_handoff_node_interrupts_then_resumes():
|
||||
async with SessionLocal() as s:
|
||||
wf = Workflow(tenant_id="t_h", project_id="p_h", name="H", executable=_WF, status="active")
|
||||
s.add(wf)
|
||||
await s.commit()
|
||||
await s.refresh(wf)
|
||||
ch = await ChannelService.create(s, "t_h", "p_h", type_="email", name="W", workflow_id=wf.id)
|
||||
|
||||
# shared checkpointer so the run can be resumed by id
|
||||
rs = RunService(checkpointer=InMemorySaver())
|
||||
result = await dispatch_message(rs, tenant_id="t_h", project_id="p_h", workflow_id=wf.id, text="I need help")
|
||||
assert result["interrupted"] is True
|
||||
# the interrupt carries our handoff marker + reason
|
||||
flat = [it for grp in result["interrupts"] for it in grp]
|
||||
assert any(isinstance(i.get("value"), dict) and i["value"].get("handoff") for i in flat)
|
||||
|
||||
# open a handoff queue item and have an agent reply
|
||||
async with SessionLocal() as s:
|
||||
h = await HandoffService.create(
|
||||
s, channel=ch, tenant_id="t_h", project_id="p_h", workflow_id=wf.id,
|
||||
run_id=result["run_id"], thread_id=result["thread_id"], customer="widget-user",
|
||||
customer_message="I need help", reason="needs a human", reply_context={},
|
||||
)
|
||||
out = await HandoffService.reply(s, rs, handoff=h, agent_id="agent1", message="Hi, this is Sam - happy to help!")
|
||||
assert out["ok"] is True
|
||||
refreshed = await s.get(HandoffRequest, h.id)
|
||||
assert refreshed.status == "answered" and refreshed.agent_id == "agent1"
|
||||
Reference in New Issue
Block a user