Go agent components misparse composite `llm_id` when the model name contains `@`
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) returnshasDriver=falsefor 4+-segment ids. Callers inllm.go,agent.go, andresolveChatModelRefthen leavedriverempty, and the whole composite — including the@instance@providertail — 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, soresolveTenantModelInstanceCredentialslooks up the wrong provider and instance and finds no credentials.
Where
internal/agent/component/llm_id.go:17—splitCompositeLLMIDinternal/agent/component/llm_credentials.go:366—parseLLMIDParts- 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.
Source: infiniflow/ragflow