Interface LLMConfigurationSource

Functional Interface:
This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

@FunctionalInterface public interface LLMConfigurationSource
An external source of LLM settings, consulted per agent at resolution time.

Registered on the bridge with SCOPBridge.llmConfigurationSource(LLMConfigurationSource), this layer sits in front of the @RoleSpec/@AgentSpec annotations: whatever it names wins, and whatever it leaves out keeps its annotation value. Consumers point at a location instead of reimplementing the precedence rule.

Three properties are part of the contract, not of any one implementation:

  • Field-level. Only the fields the source names are applied.
  • Not cached. forAgent(String) is called on every resolution and must re-read its backing store, so an edit is visible to the next resolution in the same process.
  • No silent fallback. A source that exists but cannot be read must throw LLMConfigurationException rather than answer with annotation values.

A source is trusted infrastructure, not input. An overlay can set endpoint and apiKeyEnv, and those two together are more than they look: apiKeyEnv names any environment variable or System property visible to the process — EnvLoader checks System properties first, and it loads the whole .env file into them — and endpoint decides where the resulting value is sent as a bearer token, along with the request body. Whoever controls a source can therefore read a secret that has nothing to do with LLMs and exfiltrate it, without any access to the code. Give a source the same trust you give the deployment's own configuration.

Two consequences worth stating plainly. A source can supply model for an agent whose annotations declare none, so it can enable LLM dispatch for an agent the developer never configured for it. And each resolution logs the lookup key, the fields the source contributed, and the endpoint and apiKeyEnv values when it sets them — neither is itself a secret — so a redirection leaves a trace.

Since:
0.16.4
  • Method Summary

    Modifier and Type
    Method
    Description
    Reads TNSAI_LLM_<AGENT>_<FIELD> through EnvLoader, which checks System properties before environment variables, so -DKEY=value and .env loading work uniformly with the rest of the codebase.
    folder(Path folder)
    Reads <folder>/<agentName>.json, re-read on every call.
    forAgent(String agentName)
    The settings this source holds for one agent.
    A source that holds nothing; the bridge default, so the layer is opt-in.
  • Method Details

    • forAgent

      Optional<LLMOverrides> forAgent(String agentName)
      The settings this source holds for one agent.
      Parameters:
      agentName - the agent's name — see SCOPBridge.resolveLLMSpec(Object, Object, Object, String) for how it is derived
      Returns:
      the overlay for that agent, or Optional.empty() if this source holds none
      Throws:
      LLMConfigurationException - if settings exist for the agent but cannot be read
    • noOp

      static LLMConfigurationSource noOp()
      A source that holds nothing; the bridge default, so the layer is opt-in.
    • folder

      static LLMConfigurationSource folder(Path folder)
      Reads <folder>/<agentName>.json, re-read on every call.

      The file is a flat JSON object whose keys are a subset of provider, model, temperature, maxTokens, endpoint and apiKeyEnv. A missing file yields Optional.empty(); an empty object yields an overlay that names no field. An unknown key is an error rather than a no-op, so that a misspelled temprature fails loudly instead of silently doing nothing.

      Write access to this directory is configuration authority — see the trust paragraph on LLMConfigurationSource. It is not limited to choosing a model: naming an apiKeyEnv reads any environment variable or System property the process can see, and an endpoint decides where that value is sent. Treat the folder as part of the deployment's trusted configuration, not as a place untrusted input lands.

      The agent name is a lookup key, not a path: a name containing a path separator, a NUL, or .. is rejected rather than resolved, and the resolved file must still lie inside the folder.

      Lookup is as case-sensitive as the underlying filesystem, so Researcher and researcher are one entry on a case-folding filesystem (macOS APFS, which also folds Unicode normalisation forms) and two on a case-sensitive one.

      A file over 1 MiB is refused rather than read; a real one is a few hundred bytes.

      Parameters:
      folder - directory holding one JSON file per agent
      Throws:
      NullPointerException - if folder is null
    • environment

      static LLMConfigurationSource environment()
      Reads TNSAI_LLM_<AGENT>_<FIELD> through EnvLoader, which checks System properties before environment variables, so -DKEY=value and .env loading work uniformly with the rest of the codebase.

      <AGENT> is the agent name upper-cased with every non-alphanumeric character replaced by _; <FIELD> is one of PROVIDER, MODEL, TEMPERATURE, MAX_TOKENS, ENDPOINT, API_KEY_ENV. So a Researcher agent's model comes from TNSAI_LLM_RESEARCHER_MODEL.

      This extends to provider, model, temperature and maxTokens the precedence LLMConfiguration already applies to base URL and API key.

      Because the key is normalised, agent names that differ only in punctuation (data-analyst and data_analyst) map to the same variable.

      The interface's "not cached" property holds for System properties and environment variables, which are read on every call, but not for a .env file: EnvLoader loads that once per JVM, so editing it — including removing an override — takes effect only after a restart.