Package com.tnsai.rag

Interface RetrievalSpi


public interface RetrievalSpi
Service Provider Interface for knowledge-context injection at action-dispatch time.

Implementations resolve the knowledge an action names through @ActionSpec(knowledge = ...) against the host's declared @Knowledge, give or search it according to each entry's use, and inject the formatted context string into the action's context map under RETRIEVED_CONTEXT_KEY. Downstream LLM executors read that key to splice the knowledge into the prompt. use = SEQUENTIAL entries go through the same reserved context keys so ordered consumption reuses the existing narration path.

Wired into ActionExecutor.executeInternal via find(), which resolves the cached provider snapshot through SpiLoader. If no implementation is on the classpath (e.g. tnsai-intelligence isn't a dependency), the call is a no-op — actions continue to run with no retrieved context, and SEARCH knowledge silently degrades to a documentation hint. This matches the SecurityEnforcerHandle / McpToolExecutor optional-SPI pattern already used by ActionExecutor.

Issue #172, Phase 1 — earlier turns surfaced that the memory and knowledge annotations were declarative-only. This SPI is the runtime binding side; the default implementation lives in tnsai-intelligence alongside RAGPipeline / VectorMemoryStore / source loaders.

Since:
0.5.4 (issue #172, Phase 1)
  • Field Details

  • Method Details

    • validateConfiguration

      default void validateConfiguration(Class<?> targetClass)
      Validates the knowledge declarations on a target before that target can dispatch an action.

      The default is a no-op so tnsai-core remains usable without an installed retrieval runtime. Implementations should resolve only declaration-time capabilities here; provider work that depends on a query remains in enforce(com.tnsai.metadata.ActionMetadata, com.tnsai.roles.Role, java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>).

      Parameters:
      targetClass - Role or object class whose declarations are being wired
      Since:
      0.15.0
    • validateConfiguration

      default void validateConfiguration(Class<?> targetClass, List<KnowledgeEntry> outer, LLMClient llm)
      Validates a target's knowledge once the knowledge around it is known: every action scope resolved against the target's own declarations plus outer, and every DIRECT source measured against its budget.

      Called at agent init and by hosts that bind a target to their own sources. The default is a no-op for the same reason as validateConfiguration(Class).

      Parameters:
      targetClass - Role or object class whose knowledge is being wired
      outer - knowledge in reach beyond the class's own, such as the agent's; it replaces a class entry of the same name
      llm - the model the target's actions run on, or null; its input window sizes the default DIRECT budget
      Since:
      0.18.0
    • validateConfiguration

      default void validateConfiguration(Class<?> targetClass, List<KnowledgeEntry> outer, LLMClient llm, Map<String,List<String>> actionKnowledge)
      Validates a target's knowledge as validateConfiguration(Class, java.util.List, com.tnsai.llm.LLMClient) does, with the knowledge of some actions given by name: an action actionKnowledge names sees the entries given there instead of the ones its @ActionSpec names. The default ignores actionKnowledge.
      Parameters:
      targetClass - Role or object class whose knowledge is being wired
      outer - knowledge in reach beyond the class's own
      llm - the model the target's actions run on, or null
      actionKnowledge - entry names by action name
      Since:
      0.18.0
    • validateConfiguration

      default void validateConfiguration(Class<?> targetClass, List<KnowledgeEntry> outer, LLMClient llm, boolean pathsPerRun)
      Validates a target's knowledge as validateConfiguration(Class, java.util.List, com.tnsai.llm.LLMClient) does, for a host that locates FILE sources per run.

      A FILE source declared without a path has no file until such a host resolves one for a run, so with pathsPerRun its DIRECT budget is measured when it loads instead. Without it, as at agent init, the source is measured here and a missing path fails. The default ignores pathsPerRun.

      Parameters:
      targetClass - Role or object class whose knowledge is being wired
      outer - knowledge in reach beyond the class's own
      llm - the model the target's actions run on, or null
      pathsPerRun - true when the host resolves FILE paths per run
      Since:
      0.18.0
    • prepareChatKnowledge

      default ChatKnowledgeContext prepareChatKnowledge(Object agent, List<KnowledgeEntry> entries, LLMClient llm)
      Prepares the DIRECT knowledge an agent's chat turns give.

      Called once at agent initialization, only when an enabled @AgentSpec entry in the chat scope (KnowledgeDeclarations.chatScope(java.util.List)) is DIRECT. Implementations check every DIRECT source against its budget here, so an oversized source or an unknown model window fails at initialization rather than on the first turn.

      The default throws: a runtime that cannot give this knowledge must not let the agent chat without it.

      Parameters:
      agent - the agent instance
      entries - the agent's chat knowledge, in declaration order, SEARCH entries included so the blocks keep their place around the SEARCH evidence
      llm - the agent's model, or null; its input window sizes the default DIRECT budget
      Returns:
      the per-turn knowledge
      Throws:
      IllegalStateException - if this runtime does not support it
      Since:
      0.18.0
    • enforce

      void enforce(ActionMetadata action, Role role, Map<String,Object> params, Map<String,Object> context)
      Resolves the knowledge the given action names (it may name none) and, if any, gives or searches it against the host's declared knowledge according to each entry's use. Mutates context by adding the formatted context string under RETRIEVED_CONTEXT_KEY, the assembled document count under RETRIEVED_DOCUMENT_COUNT_KEY, the fallback provenance of that context under RETRIEVED_STALE_FALLBACK_KEY, and whether the context lost anything to the configured context window under RETRIEVED_CONTEXT_TRUNCATED_KEY.

      Implementations must not throw for benign cases: no named knowledge, missing knowledge sources, empty retrieval result. They may throw for genuine configuration errors (unparseable source path, contradictory topK / minScore, etc.) — those propagate up as ActionExecutionException(VALIDATION) per the dispatcher's convention.

      Parameters:
      action - the action being dispatched (carries the method handle for annotation lookup)
      role - the role instance hosting the action (carries the class for class-level annotations)
      params - the action's working parameters — implementations may use one as the retrieval query (Phase 1: the first String parameter, since the knowledge declaration doesn't name which param is the query)
      context - the action's context map; mutated in place
    • enforce

      default void enforce(ActionMetadata action, Object target, Map<String,Object> params, Map<String,Object> context)
      Same contract as enforce(ActionMetadata, Role, Map, Map) for a target that is not a Role.

      SCOP Conversation dispatches plain objects through ActionExecutor.execute(Object). The Role overload cannot accept that target, so without this method knowledge on a SCOP action compiles and is discovered, then never runs (TAN-5876).

      The default delegates to the Role overload when target is a Role, and is a no-op otherwise. Implementations that already resolve knowledge sources from target.getClass() must override this so a non-Role host is not silently skipped.

      Parameters:
      action - the action being dispatched
      target - the object hosting the action (Role or a SCOP POJO)
      params - the action's working parameters
      context - the action's context map; mutated in place
      Since:
      0.14.1
    • requireLoaded

      default Map<String,Integer> requireLoaded(Object target, Collection<String> names, Map<String,Object> context)
      Loads the named SEARCH sources of target in the binding a dispatch of its declared knowledge with this context binds, and returns the number of documents each holds. The context is read as enforce(com.tnsai.metadata.ActionMetadata, com.tnsai.roles.Role, java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>) reads it: KNOWLEDGE_SCOPE_KEY for the scope and RESOLVED_SOURCE_PATHS_KEY for the run-time locations. Nothing is retrieved.

      A dispatcher that builds the context offers this beside its dispatch, so a host checks its knowledge against the context its actions get rather than rebuilding it.

      Parameters:
      target - the object hosting the knowledge
      names - declared, enabled sources read with use SEARCH
      context - a context built as for the dispatch; not changed
      Returns:
      each name's document count, in sorted name order
      Throws:
      UnsupportedOperationException - if this provider does not support the preflight
      Since:
      0.18.0
    • requireReady

      default Map<String,Integer> requireReady(Object target, Map<String,Object> context)
      Checks that every configured WHEN_CONFIGURED source in the target's reach holds at least one unit, keyed as its actions' dispatch keys it.

      An unconfigured WHEN_CONFIGURED source is skipped, and so is every MUST_LOAD source, whose load already fails on a missing file.

      Parameters:
      target - the object hosting the knowledge
      context - a context built as for the dispatch; not changed
      Returns:
      each checked source's unit count, in sorted name order; empty when the target declares no knowledge
      Throws:
      KnowledgeNotReadyException - if a configured source is missing, empty or unreadable
      UnsupportedOperationException - if this provider does not support the readiness check
      Since:
      0.18.1
    • find

      static Optional<RetrievalSpi> find()
      Discovers the registered RetrievalSpi via SpiLoader.

      The result is cached in an AtomicReference so the lookup happens at most once per JVM. Optional.empty() means no implementation is on the classpath, which is a supported configuration — callers should treat it as a no-op.

      Returns:
      the discovered SPI, or empty if no implementation is registered