mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-12 20:03:40 +08:00
feat(agent): Go port — canvas engine, 22 components, DSL v2, 13 endpoints (#15952)
Ports the agent canvas subsystem from Python to Go.
## What's included
### Canvas Engine (Phase 0/1)
- State engine, scheduler, variable resolver, Redis checkpoint store,
cancel protocol
- **209 tests** across canvas / component / io packages
### 22 Components (P0–P4)
| Tier | Components |
|---|---|
| P0 T1+T2+T3 | LLM, Agent, ExitLoop, Switch, Categorize, Begin,
Message, Invoke |
| P1 T3 | VariableAggregator, VariableAssigner, StringTransform,
ListOperations, DataOperations |
| P2 T3 | Iteration, IterationItem, Loop, LoopItem |
| P3 T3 | UserFillUp, Fillup |
| P4 T5 | Browser, ExcelProcessor, DocsGenerator |
### DSL v2 Schema (Phase 2.5)
- Typed v2 in-memory model with v1-to-v2 auto-detect converter
- v1 legacy field stripping per plan §2.11.7
### HTTP Endpoints & Bug Fixes (Plans PR1–PR3)
- **DELETE SQL bug fix**: gorm v2 `Where("id = ?", id).Delete(...)`
pattern
- **CreateAgent validation**: title/DSL required, duplicate check, 103
envelope
- **13 new endpoints**: templates, prompts, tags, sessions CRUD,
chat/completions (SSE + non-stream stubs), rerun, test_db_connection,
logs, webhook/logs
- **756 Go unit tests** (745 → 756, +18)
- **17 → 0 Python integration test failures** (test_agents.py +
test_session_management/)
### Tools
21 eino tools: HTTPHelper, search tools, financial/data tools, mandatory
stubs
### Infrastructure
OTel observability, NATS message queue, DeepDoc gRPC client, SSRF
guards, IDOR mitigation
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// joinModelNames extracts model names from a ListModelResponse slice and
|
||||
// joins them with sep, for use in test assertions.
|
||||
func joinModelNames(models []ListModelResponse, sep string) string {
|
||||
@@ -32,8 +33,6 @@ func joinModelNames(models []ListModelResponse, sep string) string {
|
||||
return strings.Join(names, sep)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func readProviderConfig(t *testing.T, fileName string) []byte {
|
||||
t.Helper()
|
||||
|
||||
@@ -56,14 +55,53 @@ func readPPIOProviderConfig(t *testing.T) []byte {
|
||||
return readProviderConfig(t, "ppio.json")
|
||||
}
|
||||
|
||||
func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, fileName := range []string{"mineru.json", "paddleocr.json"} {
|
||||
// setupProviderTestDir creates a temporary directory populated with provider
|
||||
// config files and conf/all_models.json, then changes the working directory to
|
||||
// it. InitProviderManager hardcodes a read of conf/all_models.json relative to
|
||||
// CWD, so the test must run from a directory that contains conf/all_models.json.
|
||||
//
|
||||
// Provider configs MUST be copied before the chdir because readProviderConfig
|
||||
// resolves file paths relative to the test binary's original CWD.
|
||||
//
|
||||
// Caller must defer the returned restore function.
|
||||
func setupProviderTestDir(t *testing.T, configFileNames ...string) (dir string, restore func()) {
|
||||
t.Helper()
|
||||
dir = t.TempDir()
|
||||
|
||||
// Copy provider configs first — readProviderConfig uses relative paths
|
||||
// that are only valid from the original CWD.
|
||||
for _, fileName := range configFileNames {
|
||||
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
|
||||
t.Fatalf("write %s config: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
|
||||
confDir := filepath.Join(dir, "conf")
|
||||
if err := os.MkdirAll(confDir, 0o755); err != nil {
|
||||
t.Fatalf("create conf dir: %v", err)
|
||||
}
|
||||
|
||||
allModelsSrc := filepath.Join("..", "..", "..", "conf", "all_models.json")
|
||||
data, err := os.ReadFile(allModelsSrc)
|
||||
if err != nil {
|
||||
t.Fatalf("read all_models.json: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(confDir, "all_models.json"), data, 0o600); err != nil {
|
||||
t.Fatalf("write all_models.json: %v", err)
|
||||
}
|
||||
|
||||
orig, _ := os.Getwd()
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
t.Fatalf("chdir: %v", err)
|
||||
}
|
||||
|
||||
return dir, func() { os.Chdir(orig) }
|
||||
}
|
||||
|
||||
func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
|
||||
dir, restore := setupProviderTestDir(t, "mineru.json", "paddleocr.json")
|
||||
defer restore()
|
||||
|
||||
err := InitProviderManager(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("InitProviderManager: %v", err)
|
||||
@@ -101,12 +139,8 @@ func TestHostedProviderConfigsLoadSharedDrivers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLocalOCRProviderConfigsLoadLocalDrivers(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, fileName := range []string{"mineru_local.json", "paddleocr_local.json"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
|
||||
t.Fatalf("write %s config: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
dir, restore := setupProviderTestDir(t, "mineru_local.json", "paddleocr_local.json")
|
||||
defer restore()
|
||||
|
||||
err := InitProviderManager(dir)
|
||||
if err != nil {
|
||||
@@ -139,12 +173,8 @@ func TestLocalOCRProviderConfigsLoadLocalDrivers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestProviderConfigsLoadURLSuffixKeys(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
for _, fileName := range []string{"cohere.json", "xai.json"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, fileName), readProviderConfig(t, fileName), 0o600); err != nil {
|
||||
t.Fatalf("write %s config: %v", fileName, err)
|
||||
}
|
||||
}
|
||||
dir, restore := setupProviderTestDir(t, "cohere.json", "xai.json")
|
||||
defer restore()
|
||||
|
||||
err := InitProviderManager(dir)
|
||||
if err != nil {
|
||||
@@ -152,7 +182,6 @@ func TestProviderConfigsLoadURLSuffixKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
pm := GetProviderManager()
|
||||
|
||||
cohere := pm.FindProvider("CoHere")
|
||||
if cohere == nil {
|
||||
t.Fatal("CoHere provider not found")
|
||||
@@ -206,10 +235,8 @@ func TestProviderConfigRejectsUnknownURLSuffixKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "ppio.json"), readPPIOProviderConfig(t), 0o600); err != nil {
|
||||
t.Fatalf("write ppio config: %v", err)
|
||||
}
|
||||
dir, restore := setupProviderTestDir(t, "ppio.json")
|
||||
defer restore()
|
||||
|
||||
err := InitProviderManager(dir)
|
||||
if err != nil {
|
||||
@@ -217,7 +244,6 @@ func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
|
||||
}
|
||||
|
||||
pm := GetProviderManager()
|
||||
|
||||
provider := pm.FindProvider("ppio")
|
||||
if provider == nil {
|
||||
t.Fatal("PPIO provider not found")
|
||||
@@ -295,10 +321,8 @@ func TestPPIOProviderConfigLoadsIntoProviderManager(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSiliconFlowProviderConfigLoadsLatestProModels(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.WriteFile(filepath.Join(dir, "siliconflow.json"), readProviderConfig(t, "siliconflow.json"), 0o600); err != nil {
|
||||
t.Fatalf("write siliconflow config: %v", err)
|
||||
}
|
||||
dir, restore := setupProviderTestDir(t, "siliconflow.json")
|
||||
defer restore()
|
||||
|
||||
err := InitProviderManager(dir)
|
||||
if err != nil {
|
||||
@@ -306,7 +330,6 @@ func TestSiliconFlowProviderConfigLoadsLatestProModels(t *testing.T) {
|
||||
}
|
||||
|
||||
pm := GetProviderManager()
|
||||
|
||||
provider := pm.FindProvider("SiliconFlow")
|
||||
if provider == nil {
|
||||
t.Fatal("SiliconFlow provider not found")
|
||||
|
||||
Reference in New Issue
Block a user