#19751·ragflow

Go agent components misparse composite `llm_id` when the model name contains `@`

Author: coderdailyoneCreated Sep 16, 2026Updated Sep 17, 2026

Describe the bug

The canvas llm_id convention is model[@instance]@provider, and the model name itself may legitimately contain @ — e.g. LM Studio quant-suffixed ids like text-embedding-nomic-embed-text-v1.5@q8_0, which produce composite keys like text-embedding-nomic-embed-text-v1.5@q8_0@lmstudio@LM-Studio.

Python's split_model_name (api/db/joint_services/tenant_model_service.py:206) right-anchors the split with rsplit("@", 2): provider = last segment, instance = second-to-last, model name = everything to the left (with its embedded @ preserved). The Go service layer already does the same (parseModelName in internal/service/model_service.go, BaseModelName in internal/common/format.go).

The agent component port splits left-to-right instead:

  • splitCompositeLLMID (internal/agent/component/llm_id.go) returns hasDriver=false for 4+-segment ids. Callers in llm.go, agent.go, and resolveChatModelRef then leave driver empty, and the whole composite — including the @instance@provider tail — is forwarded as the model name to the provider API.
  • parseLLMIDParts (internal/agent/component/llm_credentials.go) returns (parts[0], parts[1], parts[2]) for 4+ segments, so resolveTenantModelInstanceCredentials looks up the wrong provider and instance and finds no credentials.

Where

  • internal/agent/component/llm_id.go:17splitCompositeLLMID
  • internal/agent/component/llm_credentials.go:366parseLLMIDParts
  • Reachable via resolveChatModelRef, called by the LLM, Agent, and Categorize components (llm.go:663, agent.go:877, categorize.go:71).

Impact

Any agent-canvas node configured with a composite llm_id whose model name contains @ resolves no provider driver and no tenant credentials on the Go backend: the tenant_model_provider/tenant_model_instance lookup targets the wrong names, so the call proceeds with an empty API key and the composite string (suffixes included) as the upstream model name. The same DSL works on the Python backend.

How to reproduce

// unit-level
splitCompositeLLMID("text-embedding-nomic-embed-text-v1.5@q8_0@lmstudio@LM-Studio")
// got:  ("text-embedding-nomic-embed-text-v1.5@q8_0@lmstudio@LM-Studio", "", false)
// want: ("text-embedding-nomic-embed-text-v1.5@q8_0", "LM-Studio", true)

parseLLMIDParts("a@b@c@d@e")
// got:  ("a", "b", "c")
// want: ("a@b@c", "d", "e")

End-to-end regression test: TestCategorize_ResolvesInstanceCredentialsModelNameWithAt — a Categorize node with ModelID = "text-embedding-nomic-embed-text-v1.5@q8_0@lmstudio@LM-Studio" must resolve the LM-Studio provider's lmstudio instance API key. On the old code the captured Driver is "" and APIKey is "".

Suggested fix

Take the provider from the last @-separated segment, the instance from the second-to-last, and rejoin the remaining leading segments with @ as the model name — i.e. strings.Join(parts[:len(parts)-2], "@") — matching Python's rsplit("@", 2) and the sibling Go helpers.