mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-08 00:18:12 +08:00
feat(agent): ship the Go agent canvas port — eino interrupt/resume + Redis check-pointing (#16035)
Replaces the Python agent canvas runtime with a Go implementation that runs inside `cmd/server_main`. The canvas compiles into an eino Workflow that pauses on wait-for-user via native Interrupt/Resume (no sentinel flag) and resumes from a Redis-backed CheckPointStore. All 21 Python agent components and ~35 tools are ported with functional parity. Sandbox providers now read their JSON config from the admin-panel system_settings table with env fallback. 234 files / +35,413 / -6,111. All Go files are gofmt-clean (CI gate added); drops the v2 DSL E2E step and the gap-analysis plan (both redundant after the port ships). ## Type of change - [x] Refactoring - [x] New feature - [x] Bug fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -139,8 +139,11 @@ def test_agents_crud_validation_contract(rest_client, create_agent_resource):
|
||||
invalid_delete = rest_client.delete("/agents/invalid-agent-id")
|
||||
assert invalid_delete.status_code == 200
|
||||
invalid_delete_payload = invalid_delete.json()
|
||||
# code=103 = permission denied (Python: "Only the owner of the agent..."; Go: "Make sure you have permission...")
|
||||
assert invalid_delete_payload["code"] == 103, invalid_delete_payload
|
||||
assert "Only the owner of the agent is authorized for this operation." in invalid_delete_payload["message"], invalid_delete_payload
|
||||
msg = invalid_delete_payload["message"]
|
||||
assert ("Only the owner of the agent is authorized" in msg
|
||||
or "Make sure you have permission" in msg), invalid_delete_payload
|
||||
|
||||
delete_res = rest_client.delete(f"/agents/{agent_id}")
|
||||
assert delete_res.status_code == 200
|
||||
|
||||
62
test/testcases/restful_api/test_agents_go_mode.py
Normal file
62
test/testcases/restful_api/test_agents_go_mode.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import os
|
||||
import uuid
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
os.environ.get("API_PROXY_SCHEME", "python") != "go",
|
||||
reason="Go mode only (set API_PROXY_SCHEME=go + bin/ragflow_server on 9384)",
|
||||
)
|
||||
|
||||
V2_DSL = {
|
||||
"graph": {
|
||||
"nodes": [
|
||||
{"id": "begin", "type": "beginNode", "position": {"x": 50, "y": 200},
|
||||
"data": {"label": "Begin", "name": "begin"}},
|
||||
{"id": "answer:0", "type": "messageNode", "position": {"x": 400, "y": 200},
|
||||
"data": {"label": "Answer", "name": "answer"}},
|
||||
],
|
||||
"edges": [
|
||||
{"id": "xy-edge__begin-answer:0", "source": "begin", "target": "answer:0",
|
||||
"sourceHandle": "end", "targetHandle": "start"},
|
||||
],
|
||||
},
|
||||
"components": {
|
||||
"begin": {"obj": {"component_name": "Begin", "params": {}}, "downstream": ["answer:0"], "upstream": []},
|
||||
"answer:0": {"obj": {"component_name": "Answer", "params": {}}, "downstream": [], "upstream": ["begin"]},
|
||||
},
|
||||
"retrieval": [], "history": [], "path": [], "variables": [], "globals": {"sys.query": ""},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.p2
|
||||
def test_v2_dsl_round_trip_position_preserved(rest_client):
|
||||
# Unique title per run so the test is idempotent (Go returns 102 on duplicate).
|
||||
title = f"go_v2_e2e_{uuid.uuid4().hex[:8]}"
|
||||
|
||||
# 1. POST v2 DSL
|
||||
r = rest_client.post("/agents", json={"title": title, "dsl": V2_DSL})
|
||||
assert r.status_code == 200 and r.json()["code"] == 0, r.text
|
||||
agent_id = r.json()["data"]["id"]
|
||||
|
||||
# 2. GET — verify v2 shape (graph + components, NO _layout)
|
||||
dsl = rest_client.get(f"/agents/{agent_id}").json()["data"]["dsl"]
|
||||
assert "graph" in dsl, f"v2 must have 'graph', got keys={sorted(dsl.keys())}"
|
||||
assert "_layout" not in dsl, f"v2 must NOT emit '_layout', got keys={sorted(dsl.keys())}"
|
||||
assert "components" in dsl
|
||||
assert len(dsl["graph"]["nodes"]) == 2
|
||||
assert len(dsl["graph"]["edges"]) == 1
|
||||
|
||||
# 3. PUT — move begin node to (777, 888)
|
||||
new_dsl = {**V2_DSL, "graph": {**V2_DSL["graph"]}}
|
||||
new_dsl["graph"]["nodes"] = [
|
||||
{**n, "position": {"x": 777, "y": 888}} if n["id"] == "begin" else n
|
||||
for n in V2_DSL["graph"]["nodes"]
|
||||
]
|
||||
r = rest_client.put(f"/agents/{agent_id}", json={"title": title, "dsl": new_dsl})
|
||||
assert r.status_code == 200 and r.json()["code"] == 0, r.text
|
||||
|
||||
# 4. Re-GET — position preserved
|
||||
pos = next(n["position"] for n in
|
||||
rest_client.get(f"/agents/{agent_id}").json()["data"]["dsl"]["graph"]["nodes"]
|
||||
if n["id"] == "begin")
|
||||
assert pos == {"x": 777, "y": 888}, f"position lost: {pos}"
|
||||
Reference in New Issue
Block a user