Overall LLM Flow
May 26, 2026 ยท View on GitHub
This note explains how AIRunner uses its LLM stack today.
For document-specific behavior, see document-rag-flow.md.
Simple Explanation
In simple terms, AIRunner's current LLM flow does eight things:
ChatPromptWidgetturns the visible UI state into anLLMRequest.LLMAPIServicehands that request to the LLM worker.LLMGenerateWorkerasksLLMModelManagerfor the active backend.RequestHandlingMixinruns an LLM preprocess step on the user query and recent conversation context.- That preprocess returns request metadata such as a query rewrite, tool categories, planner hints, and document intent.
- AIRunner applies that metadata to the request, tool surface, system prompt, planner mode, and RAG state.
WorkflowManagerruns the LangGraphmodel/tools/force_responseloop.GenerationMixinstreams the visible reply, andConversationWidgetassembles the final assistant bubble.
AIRunner currently supports three main chat backends:
- local GGUF models through
ChatGGUFandllama.cpp, - OpenRouter through
ChatOpenAI, - Ollama through
ChatOllama.
Local chat inference is GGUF-only. Transformers-based local chat loading is intentionally disabled.
Thinking is now always enabled for request execution. The chat prompt footer no longer exposes a thinking toggle; GPT-OSS-style models still use the reasoning-effort control.
Main Request Flow
flowchart LR
A[User submits prompt] --> B[ChatPromptWidget builds LLMRequest]
B --> C[LLMAPIService.send_request]
C --> D[WorkerManager forwards to LLMGenerateWorker]
D --> E[LLMGenerateWorker gets LLMModelManager]
E --> F[RequestHandlingMixin.handle_request]
F --> G[Load or switch provider and model]
F --> H[LLM preprocesses prompt,<br/>conversation context, and tool inventory]
H --> I[Apply rewrite guidance,<br/>tool filters, planner state, and RAG]
I --> J[WorkflowManager.stream]
J --> K[LangGraph model/tools/force_response loop]
K --> L[GenerationMixin emits streamed text]
L --> M[ConversationWidget assembles bubble]
Workflow Graph
flowchart LR
A[START] --> B[model]
B -->|tool calls| C[tools]
B -->|forced synthesis or direct reply| D[force_response]
B -->|plain reply| E[END]
C -->|needs another step| B
C -->|synthesize or finalize| D
C -->|status-only result| E
D -->|workflow continuation| B
D -->|normal completion| E
Runtime Stages
1. Request Assembly
ChatPromptWidgetparses slash commands, action overrides, and the current prompt text.- It builds
LLMRequest.for_visible_action(...). - It attaches images for vision-capable models.
- It attaches
rag_filesand per-document metadata when documents are loaded. - It sets request-scoped reasoning effort and always marks thinking as enabled.
2. Service And Worker Handoff
LLMAPIService.send_request()packages the prompt, action, conversation ID, request ID, andLLMRequest.- In the GUI path,
WorkerManagerforwards the request toLLMGenerateWorker. LLMGenerateWorkerownsLLMModelManagerand callshandle_request()on it.
3. Provider And Model Creation
LLMModelManageruses chat_model_factory.py to create the active chat backend.- provider_config.py defines model capabilities such as context length, tool mode, thinking support, and default GGUF runtime settings.
- Local execution currently means GGUF plus
llama.cppthrough chat_gguf.py. - Remote execution currently means OpenRouter or Ollama.
4. LLM Request Preprocess
- tool_classification_mixin.py runs a dedicated LLM preprocess step before the workflow loop.
- The preprocess sees the user query, recent conversation context, and the request-scoped tool inventory.
- It can return an optional
rewritten_promptplus a structured request plan with tool categories, primary tool, planner mode, answer strategy, finalization mode, and document metadata such asdocument_query_intent,document_summary_focus, anddocument_answer_mode. - This replaces active-path keyword and sentence heuristics for tool choice and document intent classification.
- Do not reintroduce keyword lists, regex routing, word-hit scoring, sentence matching, or similar heuristics for tool choice, query classification, or summary-mode selection. Those decisions belong to the LLM preprocess contract.
5. Request-Scoped Preparation
- request_handling_mixin.py applies request-scoped model-service, dtype, and conversation overrides.
- It can clear memory for ephemeral or no-memory requests.
- It applies preprocess results to planner mode, tool filters, saved prompt guidance, and RAG state.
- When the preprocess nominates one direct document tool, request handling now applies that tool directly instead of activating planner mode just because documents are attached.
- Rewritten prompt guidance augments the system prompt instead of replacing the user's visible prompt.
6. Tool Surface And Tool Mode
- tool_filtering_mixin.py
narrows the tool surface by category, allowlist, or explicit
force_tool. - tool_management_mixin.py rebinds tools on the active workflow model for each request.
- tool_manager.py wraps registered tools, injects service-owned dependencies such as the API or active RAG manager at execution time, and applies request- scoped defaults.
- Dependency-injected wrappers expose only model-controlled arguments in
the visible tool schema. Internal kwargs such as
apistay hidden, which keeps forced-tool and LangGraph execution aligned with the real tool signature. tool_choicecan be left unset, set to one named tool, or set to"any"when a tool call is required.- In
ChatGGUF, native llama.cpp tool binding is used only whentool_calling_mode == "native". jsonmode injects tool instructions into the prompt and parses<tool_call>...</tool_call>output.reactmode uses compact tool descriptions andAction/Action Inputparsing.- GPT-OSS uses its own raw-completion commentary/final tool path.
7. Model Loop, Finalization, And Recovery
- workflow_manager.py
builds the request-scoped
WorkflowManager. - workflow_building_mixin.py compiles the LangGraph graph.
- node_functions_mixin.py trims history, builds prompts, and calls the model.
- routing_decision_mixin.py decides whether to loop, synthesize, or end.
force_responseis used for grounded synthesis, direct tool results, duplicate-call recovery, and task-completing tool replies.- Document-specific grounded replies can swap to a saved final chat prompt before the visible answer is generated. Those internal passes can unbind tools. Request-level thinking still stays on. Hidden document synthesis and verification now disable model thinking for flat document cases, but layered/frame-heavy document summaries can keep hidden-stage thinking enabled when structured document analysis says that extra reasoning is needed.
- In
ChatGGUF, those hidden-stage generation presets are now applied as per-call adapter overrides, so stage-specificmax_new_tokens,temperature,reasoning_effort, andenable_thinkingsettings reachllama.cppinstead of remaining manager-side metadata only. - For large attached documents,
analyze_loaded_documentnow carries a reduced whole-document bundle with coverage, a deterministic refined synthesis, chunk summaries, model-built structured document analysis, and supporting evidence so hidden synthesis/verification stages do not have to reason over raw excerpt inventories alone. - Premise-focused document evidence now comes from model-selected span roles over structural candidate spans rather than marker or sentence heuristics. Verification uses the structured document-analysis layer and composition cautions to reject summaries that collapse staged, remembered, or frame-level material into literal plot facts.
- Internal document synthesis and verification now use a dedicated
answer_textblock contract so the visible reply comes only from one explicit committed answer field instead of free-form recovery. Verification may replace a synthesized draft only when it also returns a validanswer_textblock. Reasoning prose, verifier critique text, and other non-committed fragments are treated as failed stage output, not as visible answers. - If the workflow still ends with no visible final
AIMessage,GenerationMixinfirst tries document-specific recovery from the checkpointed tool state for document requests before it falls back to the generic empty-result messages such as the read-only or non-mutating tool notices. - The remaining hard failure mode in this path is no longer leaked
reasoning; it is a stage returning no committed
answer_textblock even after the hidden no-think pass. When that happens, the pipeline fails closed and can still collapse into the empty-result fallback path.
8. Streaming And Rendering
GenerationMixinemitsLLM_TEXT_STREAMED_SIGNALchunks.- streaming_mixin.py
streams the workflow state and yields only new
AIMessageentries. - conversation_widget.py buffers chunks by request and sequence number.
_process_sequential_tokens()appends ordered text into the active assistant message and updates the web view.
Request-Scoped State
LLMRequestcarries the visible action, generation settings, tool preferences, attachments, and optional provider overrides.handle_request()can add internal fields such asplanner_mode,rewritten_prompt,preprocessed_primary_tool,document_query_intent,document_summary_focus,document_primary_tool,document_answer_mode,planner_tool_hints,final_system_prompt, andrequest_plan.request_idandconversation_idstay attached to the request so tool status, streamed text, and the final reply stay correlated in the UI.
Where Core Decisions Live
UI And Request Packaging
Provider And Model Selection
Request Preprocess And Tool Orchestration
- tool_classification_mixin.py
- request_handling_mixin.py
- tool_filtering_mixin.py
- tool_management_mixin.py
- workflow_manager.py
- workflow_building_mixin.py
Prompt Building, Routing, And Grounded Follow-Up
- system_prompt_mixin.py
- node_functions_mixin.py
- routing_decision_mixin.py
- document_response_policy_mixin.py
- document_conversational_followup_mixin.py
Streaming And Rendering
Practical Debugging Order
When a reply looks wrong, check the stack in this order:
- Was the request assembled correctly in chat_prompt_widget.py?
- Did llm_services.py and llm_generate_worker.py hand it to the expected manager?
- Did the preprocess step in tool_classification_mixin.py return the expected rewrite, tool categories, and document metadata?
- Did request_handling_mixin.py apply the right prompt guidance, tool filter, planner state, and RAG prep?
- Did the adapter use the correct provider and
tool_calling_modein chat_gguf.py or the active remote adapter? - Did the workflow route through
model,tools, andforce_responsethe way you expected? - Did generation recovery or fallback alter the final visible reply?
- Did conversation_widget.py receive and order the streamed chunks correctly?
- If the issue is document-specific, continue with document-rag-flow.md.