packages / adapter-lmstudio / src / config.ts

import { createConfigSchematics } from "@lmstudio/sdk";
import { AUTO_DETECT_MODEL_ID } from "../../lmstudio-shared/src/modelResolution";

export { AUTO_DETECT_MODEL_ID } from "../../lmstudio-shared/src/modelResolution";

export const configSchematics = createConfigSchematics()
  .field(
    "embeddingModel",
    "select",
    {
      displayName: "Embedding Model",
      subtitle:
        "The plugin will automatically detect and use the first available embedding model.",
      options: [
        {
          value: AUTO_DETECT_MODEL_ID,
          displayName: "Auto-Detect (Use first loaded/available)",
        },
      ],
    },
    AUTO_DETECT_MODEL_ID
  )
  .field(
    "embeddingModelManual",
    "string",
    {
      displayName: "Manual Model ID (Optional)",
      subtitle:
        "Enter a specific model ID here to override the Auto-Detect selection.",
      placeholder: "e.g., text-embedding-nomic-embed-text-v1.5",
    },
    ""
  )
  .field(
    "autoUnload",
    "boolean",
    {
      displayName: "Auto-Unload Model",
      subtitle:
        "Unload the embedding model from memory immediately after retrieval finishes.",
    },
    false
  )
  .field(
    "retrievalLimit",
    "numeric",
    {
      int: true,
      min: 1,
      displayName: "Retrieval Limit",
      subtitle: "Maximum number of chunks to return.",
      slider: { min: 1, max: 10, step: 1 },
    },
    3
  )
  .field(
    "retrievalAffinityThreshold",
    "numeric",
    {
      min: 0.0,
      max: 1.0,
      displayName: "Retrieval Affinity Threshold",
      subtitle: "Minimum similarity score for relevance.",
      slider: { min: 0.0, max: 1.0, step: 0.01 },
    },
    0.5
  )
  .field(
    "answerabilityGateEnabled",
    "boolean",
    {
      displayName: "Answerability Gate",
      subtitle:
        "Classify prompts before retrieval to skip casual messages and catch likely no-match cases.",
    },
    true
  )
  .field(
    "answerabilityGateThreshold",
    "numeric",
    {
      min: 0.0,
      max: 1.0,
      displayName: "Gate Confidence Threshold",
      subtitle:
        "Minimum gate confidence required before taking an early non-retrieval path.",
      slider: { min: 0.0, max: 1.0, step: 0.01 },
    },
    0.7
  )
  .field(
    "ambiguousQueryBehavior",
    "select",
    {
      displayName: "Ambiguous Query Behavior",
      subtitle:
        "Choose whether ambiguous prompts should trigger a clarification request or continue normally.",
      options: [
        {
          value: "ask-clarification",
          displayName: "Ask for clarification",
        },
        {
          value: "attempt-best-effort",
          displayName: "Attempt best effort",
        },
      ],
    },
    "ask-clarification"
  )
  .field(
    "multiQueryEnabled",
    "boolean",
    {
      displayName: "Multi-Query Retrieval",
      subtitle:
        "Generate a few deterministic query rewrites and fuse the retrieval results.",
    },
    false
  )
  .field(
    "multiQueryCount",
    "numeric",
    {
      int: true,
      min: 2,
      max: 5,
      displayName: "Multi-Query Count",
      subtitle: "How many rewrite variants to generate when multi-query is enabled.",
      slider: { min: 2, max: 5, step: 1 },
    },
    3
  )
  .field(
    "hybridEnabled",
    "boolean",
    {
      displayName: "Hybrid Retrieval",
      subtitle:
        "Combine semantic retrieval with a lightweight lexical pass for better recall on exact terms.",
    },
    false
  )
  .field(
    "semanticWeight",
    "numeric",
    {
      min: 0.0,
      max: 1.0,
      displayName: "Hybrid Semantic Weight",
      subtitle: "Blend weight for semantic scores in hybrid retrieval.",
      slider: { min: 0.0, max: 1.0, step: 0.05 },
    },
    0.65
  )
  .field(
    "lexicalWeight",
    "numeric",
    {
      min: 0.0,
      max: 1.0,
      displayName: "Hybrid Lexical Weight",
      subtitle: "Blend weight for lexical scores in hybrid retrieval.",
      slider: { min: 0.0, max: 1.0, step: 0.05 },
    },
    0.35
  )
  .field(
    "modelRerankEnabled",
    "boolean",
    {
      displayName: "Model-Assisted Rerank",
      subtitle:
        "Use an LM Studio chat model to rescore the strongest retrieved chunks before answering.",
    },
    false
  )
  .field(
    "modelRerankTopK",
    "numeric",
    {
      int: true,
      min: 2,
      max: 8,
      displayName: "Model Rerank Top K",
      subtitle: "How many top retrieval candidates to rescore with the chat model.",
      slider: { min: 2, max: 8, step: 1 },
    },
    5
  )
  .field(
    "modelRerankMode",
    "select",
    {
      displayName: "Rerank Model Source",
      subtitle:
        "Choose whether reranking uses the active chat model, auto-detects a local chat model, or loads a specific model ID.",
      options: [
        { value: "active-chat-model", displayName: "Use active chat model" },
        { value: "auto-detect", displayName: "Auto-detect available chat model" },
        { value: "manual-model-id", displayName: "Use manual rerank model ID" },
      ],
    },
    "active-chat-model"
  )
  .field(
    "modelRerankModelId",
    "string",
    {
      displayName: "Manual Rerank Model ID",
      subtitle:
        "Optional explicit model ID for model-assisted reranking when manual mode is selected.",
      placeholder: "e.g., lmstudio-community/Qwen2.5-7B-Instruct-GGUF",
    },
    ""
  )
  .field(
    "strictGroundingMode",
    "select",
    {
      displayName: "Strict Grounding Mode",
      subtitle:
        "Choose how strictly answers must stay tied to retrieved evidence.",
      options: [
        { value: "off", displayName: "Off" },
        { value: "warn-on-weak-evidence", displayName: "Warn on weak evidence" },
        { value: "require-evidence", displayName: "Require evidence" },
      ],
    },
    "warn-on-weak-evidence"
  )
  .field(
    "correctiveRetrievalEnabled",
    "boolean",
    {
      displayName: "Corrective Retrieval",
      subtitle:
        "Retry retrieval with focused follow-up rewrites when initial evidence looks weak.",
    },
    false
  )
  .field(
    "correctiveMaxAttempts",
    "numeric",
    {
      int: true,
      min: 0,
      max: 4,
      displayName: "Corrective Max Attempts",
      subtitle: "Maximum number of corrective retrieval attempts.",
      slider: { min: 0, max: 4, step: 1 },
    },
    1
  )
  .field(
    "fusionMethod",
    "select",
    {
      displayName: "Fusion Method",
      subtitle: "How retrieval results from multiple queries are merged.",
      options: [
        { value: "reciprocal-rank-fusion", displayName: "Reciprocal Rank Fusion" },
        { value: "max-score", displayName: "Max score" },
      ],
    },
    "reciprocal-rank-fusion"
  )
  .field(
    "maxCandidatesBeforeRerank",
    "numeric",
    {
      int: true,
      min: 1,
      max: 20,
      displayName: "Max Candidates Before Rerank",
      subtitle: "Maximum number of retrieval candidates to keep before reranking.",
      slider: { min: 1, max: 20, step: 1 },
    },
    8
  )
  .field(
    "maxEvidenceBlocks",
    "numeric",
    {
      int: true,
      min: 1,
      max: 20,
      displayName: "Max Evidence Blocks",
      subtitle: "Maximum number of evidence blocks to carry into answer composition.",
      slider: { min: 1, max: 20, step: 1 },
    },
    8
  )
  .field(
    "dedupeSimilarityThreshold",
    "numeric",
    {
      min: 0.0,
      max: 1.0,
      displayName: "Dedupe Similarity Threshold",
      subtitle: "Similarity threshold for collapsing near-duplicate evidence chunks.",
      slider: { min: 0.0, max: 1.0, step: 0.01 },
    },
    0.92
  )
  .field(
    "rerankEnabled",
    "boolean",
    {
      displayName: "Rerank Retrieved Chunks",
      subtitle: "Enable reranking before evidence selection.",
    },
    true
  )
  .field(
    "rerankStrategy",
    "select",
    {
      displayName: "Rerank Strategy",
      subtitle: "Choose the reranking approach to apply to retrieved chunks.",
      options: [
        { value: "heuristic-v1", displayName: "Heuristic v1" },
        { value: "heuristic-then-llm", displayName: "Heuristic then LLM" },
      ],
    },
    "heuristic-v1"
  )
  .field(
    "rerankTopK",
    "numeric",
    {
      int: true,
      min: 1,
      max: 20,
      displayName: "Rerank Top K",
      subtitle: "How many top retrieval candidates to send into reranking.",
      slider: { min: 1, max: 20, step: 1 },
    },
    5
  )
  .field(
    "sanitizeRetrievedText",
    "boolean",
    {
      displayName: "Sanitize Retrieved Text",
      subtitle: "Clean retrieved passages before they are used in prompting or evidence output.",
    },
    false
  )
  .field(
    "stripInstructionalSpans",
    "boolean",
    {
      displayName: "Strip Instructional Spans",
      subtitle: "Remove instruction-like spans from retrieved text during sanitization.",
    },
    false
  )
  .field(
    "hybridCandidateCount",
    "numeric",
    {
      int: true,
      min: 1,
      max: 20,
      displayName: "Hybrid Candidate Count",
      subtitle: "Maximum number of candidates to keep in the hybrid retrieval branch.",
      slider: { min: 1, max: 20, step: 1 },
    },
    8
  )
  .build();