Skip to content

Repository files navigation

AnswerCode

🌐 English | 繁體中文

AI-powered code Q&A system built on Microsoft Agent Framework. Ask questions about your codebase and get evidence-backed answers from a Harness-powered agentic tool-calling loop.

Features

  • Secure Source Code Upload: Upload project files directly in the browser. Source allowlists, executable signature checks, rooted-path rejection, and destination confinement keep files inside the assigned workspace
  • Google Login & Persistent Storage: Sign in with Google to get dedicated persistent storage (default 300 MB quota) — uploaded projects survive across browser sessions and can be managed from the Dashboard
  • User Dashboard: Authenticated users get a /dashboard page showing all uploaded projects, storage usage with a visual progress bar, and the ability to delete individual projects
  • Agentic Q&A with Microsoft Agent Framework: Native function-calling providers run through HarnessAgent; existing AnswerCode tools are exposed as AIFunction instances while the current SSE event contract remains stable
  • Clarifying Questions: The agent can pause mid-run and ask the user a direct question via the ask_user tool when it hits a genuinely ambiguous or high-impact decision, then resume once the answer is submitted
  • Dual Answer Modes: Choose between Developer mode (technical, with file paths and line numbers) and PM mode (plain language, business-focused, no code snippets) for each question
  • Multiple LLM Providers: Dynamically configurable — add OpenAI-compatible, Azure OpenAI, Microsoft Foundry, or Ollama providers via appsettings.json
  • ReAct Fallback Loop: Providers that do not support native function calling automatically fall back to a text-based ReAct loop using <tool_call> XML tags, so any LLM can act as an agent
  • SubAgent Architecture: Follow-up questions use a 3-phase SubAgent design — (1) resolve the follow-up into a standalone question using conversation history, (2) run the agentic tool loop without history to save tokens, (3) synthesize the final answer with history context. History length is controlled by a 200K token budget instead of a fixed turn limit, with automatic compression when approaching the threshold
  • Adaptive Iteration Budgets: A rule-based question-complexity classifier (no extra LLM call) sizes the tool-loop iteration cap per question — simple lookups get a small budget, complex multi-hop questions keep the full budget
  • Pre-fetched Symbol Context: When a question names a real symbol in the codebase, the agent verifies it and pre-fetches its definition, call graph, and references before the tool loop starts, cutting down on discovery round-trips
  • Runtime-Aware Tool Scheduling: Agent Framework manages native function invocation; the ReAct fallback can run independent calls concurrently while always isolating ask_user
  • Conversation History Inspector: Click the Main token counter in the top bar to view the exact conversation turns the LLM remembers, with a download button to export the history as Markdown
  • Download Chat: Export the entire visible conversation — every question, tool call input/output, and final answer — as a single Markdown file with one click
  • Streaming Progress: Real-time SSE streaming shows each tool call as it happens, including a result summary, expandable detail items, and duration
  • Token Usage Tracking: Main agent (context resolution + synthesis) and SubAgent (tool loop) token counts are tracked separately and displayed in the top bar as Main / Sub / Total
  • Multi-Language Project Support: Auto-detects and summarizes project metadata for .NET, Node.js, Python, Go, Rust, Java, and C/C++ projects
  • Hybrid Multi-Language Code Analysis: C# uses Roslyn for precise symbol reads and reference lookup; TypeScript, JavaScript, Python, Go, and Rust use LSP servers (typescript-language-server, Pyright, gopls, rust-analyzer) for semantic definition, reference, and symbol analysis with heuristic fallback; Java and C/C++ use heuristic symbol, reference, and test discovery
  • Dark Theme UI: Web interface with syntax highlighting, Markdown rendering, Mermaid diagram support with interactive zoom/pan and fullscreen view
  • Protected Upload Lifecycle: Anonymous cleanup uses ASP.NET Core Data Protection signed delete tokens. The browser removes uploads on exit when possible, with a TTL-based background cleanup service as a safety net
  • Structured Logging: Request/response logging via Serilog with console and rolling file sinks

Prerequisites

  • .NET 10.0 SDK
  • LLM API access (OpenAI, Azure OpenAI, Microsoft Foundry, or Ollama)
  • Azure CLI with az login when using Microsoft Foundry locally

Quick Start

  1. Clone the repository and navigate to the project folder:

    cd AnswerCode
  2. Configure provider metadata in appsettings.json. Put API keys and local overrides in the gitignored appsettings.Local.json (see Configuration below).

  3. Run the application:

    dotnet run
  4. Open a browser to http://localhost:5000.

  5. Upload your source code using the drag-and-drop area or the Browse Files / Browse Folder buttons. Select a model provider, enter your question, and click Answer as Developer or Answer as PM.

Answer Modes

Two distinct modes tailor the agent's behavior and response style:

Mode Button Audience Style
Developer Answer as Developer Engineers Technical; cites file paths, line numbers, class/method names, and code snippets
PM Answer as PM Program/Project Managers Plain language; describes business workflows and module interactions without raw code

The mode is selected directly from the UI using Answer as Developer or Answer as PM.

Source Code Upload

Source code is uploaded directly from the browser:

  • Click Browse Files to select individual files, or Browse Folder to select an entire folder (preserving relative paths).
  • Drag and drop files or folders onto the upload area.
  • Multiple uploads are supported — each upload gets a unique folder ID.
  • Once uploaded, a green status badge shows the folder ID and file count. Click Remove to delete the uploaded code from the server.

Anonymous users (not signed in):

  • Upload size limit: 20 MB per upload.
  • Files are stored under wwwroot/source-code/{folderId}/ and automatically deleted when the browser tab is closed (navigator.sendBeacon()). A background service acts as a safety net, removing expired uploads after the configured TTL (default: 120 minutes).

Authenticated users (signed in with Google):

  • Upload size limit: 300 MB per upload, with a total storage quota (default 300 MB, configurable).
  • Files are stored under the user's dedicated directory and persist across sessions.
  • Manage all uploaded projects from the Dashboard (/dashboard).

The uploaded folder ID is automatically used as the projectPath for all Q&A requests.

Upload paths are treated as untrusted input. Rooted paths, traversal segments, ignored build/dependency directories, unsupported file types, and common executable binary signatures are rejected. Anonymous delete tokens are signed with ASP.NET Core Data Protection and do not depend on process-local controller state.

Authentication & Dashboard

AnswerCode supports optional Google OAuth login. Authentication is not required to use the Q&A feature — anonymous users can upload code and ask questions as before.

Signing in unlocks:

  • Persistent storage — uploaded projects are saved to your account and available across browser sessions.
  • Higher upload limit — 300 MB per upload (vs. 20 MB anonymous).
  • Dashboard — visit /dashboard to see all your uploaded projects, monitor storage usage, and delete projects you no longer need.

A dev-login shortcut (/api/auth/dev-login) is available in Development mode for local testing without Google OAuth credentials.

Configuration

Non-secret defaults are configured in appsettings.json. Use the gitignored appsettings.Local.json for API keys and machine-specific overrides. Never commit live credentials.

LLM Providers

LLM providers are configured under the LLM section. You can add as many providers as needed; each one appears in the UI's provider dropdown.

{
  "LLM": {
    "DefaultProvider": "OpenAI",
    "Providers": {
      "OpenAI": {
        "Endpoint": "https://your-endpoint.openai.com",
        "ApiKey": "your-api-key",
        "Model": "gpt-4o",
        "DisplayName": "GPT-4o"
      },
      "AzureOpenAI": {
        "Endpoint": "https://your-resource.cognitiveservices.azure.com/",
        "ApiKey": "your-api-key",
        "DeploymentName": "your-azure-deployment",
        "Model": "gpt-5.5",
        "DisplayName": "Azure GPT-5.5",
        "UseReasoningModelParameters": true
      },
      "Foundry": {
        "Endpoint": "https://your-resource.services.ai.azure.com/api/projects/your-project",
        "Model": "your-model-deployment",
        "DisplayName": "Microsoft Foundry"
      },
      "Ollama": {
        "Endpoint": "http://localhost:11434/v1/",
        "ApiKey": "ollama",
        "Model": "llama3",
        "DisplayName": "Ollama Llama3"
      }
    }
  }
}

Provider Types

  • AzureOpenAI: Use Endpoint, ApiKey, DeploymentName, and optionally Model, DisplayName, and UseReasoningModelParameters. Set UseReasoningModelParameters to true for GPT-5.2/GPT-5.4/GPT-5.5 deployments whose Azure deployment name does not include the model name.
  • Foundry: Use the Foundry key with the project Endpoint, Model, and optionally DisplayName. Authentication uses DefaultAzureCredential; run az login locally or configure Managed Identity in production. Native tool calling runs through Microsoft Agent Framework HarnessAgent.
  • OpenAI / OpenAI-compatible (any other key, including Ollama): Use Endpoint, ApiKey, Model, and optionally DisplayName. The factory treats keys other than AzureOpenAI and Foundry as OpenAI-compatible providers — Ollama works out of the box via its /v1/ endpoint.

Model Configuration Guide

  • Azure GPT-5.2 / GPT-5.4 / GPT-5.5 reasoning models: Use the AzureOpenAI provider with the Azure resource root endpoint, for example https://your-resource.cognitiveservices.azure.com/. Set DeploymentName to the Azure deployment name and set UseReasoningModelParameters to true when the deployment name does not clearly identify the model. These models use the Azure SDK opt-in for max_completion_tokens and omit unsupported sampling parameters such as temperature.
  • Azure GPT-5 Chat models: Use the AzureOpenAI provider with the same Azure resource root endpoint. Set DeploymentName and Model to values such as gpt-5-chat, and leave UseReasoningModelParameters unset or false so regular chat parameters like Temperature can be sent.
  • Azure AI Foundry OpenAI-compatible models such as gpt-oss-120b: Use the OpenAI provider, not AzureOpenAI. The endpoint must be the OpenAI-compatible base URL, for example https://your-foundry-resource.services.ai.azure.com/openai/v1/, not a full REST path like /models/chat/completions?....
  • Other OpenAI-compatible providers: Use the OpenAI provider with the provider's /v1/ base URL. Set UseReasoningModelParameters only if that model rejects max_tokens and requires max_completion_tokens.

Google Authentication

Google OAuth is configured under the Authentication section. Obtain a Client ID and Client Secret from the Google Cloud Console.

{
  "Authentication": {
    "Google": {
      "ClientId": "your-client-id",
      "ClientSecret": "your-client-secret"
    }
  }
}

Authentication is optional — the app works fully for anonymous users without these credentials.

User Storage Quota

The per-user storage limit for authenticated users is configured under UserStorage:

{
  "UserStorage": {
    "MaxSizeMB": 300
  }
}
  • MaxSizeMB: Maximum total storage per user in megabytes (default: 300).

Web Search (Tavily)

The web_search tool uses the Tavily Search API to let the agent retrieve external information. Configure the API key under the Tavily section:

{
  "Tavily": {
    "ApiKey": "tvly-your-api-key"
  }
}

If no API key is configured, the tool will return an error message and the agent will skip web search.

Upload Cleanup

Automatic cleanup of expired anonymous uploads is configured under the UploadCleanup section:

{
  "UploadCleanup": {
    "ScanIntervalMinutes": 10,
    "MaxAgeMinutes": 120
  }
}
  • ScanIntervalMinutes: How often the background service scans for expired folders (default: 10).
  • MaxAgeMinutes: Folders with no file activity beyond this age are deleted (default: 120).

Agent Behavior Tuning

Symbol context pre-fetching, question-complexity iteration budgets, and ReAct fallback concurrency are configured under the AgentSettings section:

{
  "AgentSettings": {
    "EnableSymbolContextExpansion": true,
    "EnableComplexityRouting": true,
    "EnableParallelToolExecution": true,
    "SimpleQuestionMaxIterations": 8,
    "StandardQuestionMaxIterations": 25,
    "ComplexQuestionMaxIterations": 50
  }
}
  • Providers with native tool-calling support always run through Microsoft Agent Framework HarnessAgent. Providers without native tool calling use the ReAct fallback. Harness tool calls are currently invoked sequentially so ask_user cannot overlap another tool.
  • EnableSymbolContextExpansion: Pre-fetch verified symbol definitions, call graphs, and references for symbols detected in the question (default: true).
  • EnableComplexityRouting: Size the tool-loop iteration budget based on a rule-based question complexity classification (default: true). When disabled, every question uses ComplexQuestionMaxIterations.
  • EnableParallelToolExecution: Run tool calls from the ReAct fallback concurrently instead of sequentially (default: true). The ask_user tool is always excluded and runs alone. Harness tool scheduling is managed by Agent Framework.
  • SimpleQuestionMaxIterations / StandardQuestionMaxIterations / ComplexQuestionMaxIterations: Max tool-loop iterations per complexity tier (defaults: 8 / 25 / 50).

Microsoft Agent Framework Runtime

Providers with native function calling use Microsoft Agent Framework as the primary runtime:

  • AnswerCodeOpenAIClient and AnswerCodeFoundryClient create IChatClient instances for the configured transport.
  • AnswerCodeAgentHarness builds a HarnessAgent with AnswerCode instructions, iteration limits, and adapted tools.
  • AnswerCodeToolFunction preserves each existing tool's JSON schema and delegates execution to its ITool implementation.
  • AgentFrameworkEventAdapter maps text, reasoning, function calls/results, usage, and errors to the existing SSE event model.
  • If a provider returns reasoning without a tool call or final answer, the harness retries in the same AgentSession up to three times with a targeted reminder.
  • Providers explicitly configured without native tool support use ReActAgentRunner instead.

Harness defaults that overlap AnswerCode behavior (file memory, hosted web search, todo/mode/skills providers, and built-in OpenTelemetry wrapping) are disabled. AnswerCode remains responsible for its prompts, code-analysis tools, conversation phases, and UI protocol.

Agent Tools

The agent uses these tools to explore your codebase:

Tool Description
get_file_outline Get structural outline of a file (classes, methods, properties) with line numbers — much more token-efficient than reading the whole file
find_definition Find where a symbol (class, interface, method, etc.) is defined — more precise than grep
find_references Find where a symbol is used, called, inherited, implemented, or imported across the repository
find_tests Find likely tests related to a source symbol or file
get_related_files Find a file's dependencies (imports) and dependents (files that reference it)
repo_map Generate a repository map showing module boundaries, architectural roles, cross-module dependencies, entry points, and a Mermaid diagram
call_graph Generate a static call graph from a method/function — trace downstream calls or upstream callers with cycle detection and confidence labels
grep_search Search file contents by pattern (regex)
glob_search Find files by name pattern (e.g. *.cs)
read_file Read file contents (with optional line range)
read_symbol Read one exact symbol definition with optional body/comments instead of reading a whole file
list_directory List files in a subdirectory (project root structure is auto-injected)
web_search Search the web via Tavily Search API for external information — library docs, API references, best practices, error explanations, or latest updates
config_lookup Look up a configuration key across all config files in the project — finds where a key is defined, its value in each source, and which value wins by precedence. Supports C#, JavaScript, TypeScript, Python, Java, Go, Rust, and C/C++ config patterns
ask_user Pause the run and ask the human user a clarifying question (with optional suggested answer choices) when facing an ambiguous or high-impact decision that can't be safely resolved by reading the code

Auto-injected context: The agent automatically receives a project overview (directory structure, language, framework, dependencies) at the start of each conversation, eliminating the need for an initial list_directory call and saving one full LLM round-trip.

Multi-language project detection: The overview builder auto-detects project metadata from .csproj (.NET), package.json (Node.js), requirements.txt / pyproject.toml (Python), go.mod (Go), Cargo.toml (Rust), pom.xml / build.gradle (Java), and CMakeLists.txt / Makefile (C/C++).

Symbol-aware analysis:

  • C# paths use Roslyn-backed analysis for read_symbol, find_references, find_tests, and call_graph.
  • TypeScript, JavaScript, and Python use LSP servers (typescript-language-server, Pyright) for find_definition, find_references, and get_file_outline, with heuristic fallback.
  • Go and Rust use LSP servers (gopls, rust-analyzer) for the same operations, with heuristic fallback. The LSP binaries are bundled under lsp-servers/bin/ for deployment to environments (e.g., Azure App Service) where these tools are not pre-installed.
  • Java and C/C++ use heuristic parsing and matching for those same tools.

ReAct Fallback Loop

When a configured provider reports SupportsToolCalling = false, the agent automatically switches to a ReAct text loop instead of native function calling. In this mode:

  • The LLM is given embedded tool descriptions in its system prompt.
  • Tool calls are expressed as <tool_call>{"name": "...", "arguments": {...}}</tool_call> XML tags in plain text output.
  • The server parses these tags (via ReActParser), executes the tools, and returns results in <tool_result> tags for the next turn.
  • Progress events and token tracking work the same as with native tool calling.

This allows any text-generating LLM to act as an agent without requiring OpenAI-style function calling support.

Clarifying Questions

During the tool loop, the agent can call ask_user to pause and ask the human a direct question instead of guessing:

  1. The tool emits a UserQuestion SSE event (with a unique questionId, the question text, and optional suggested answer choices) and blocks, waiting for a response.
  2. The UI displays the question and lets the user type or pick an answer.
  3. The client submits the answer via POST /api/codeqa/ask/answer with the matching questionId.
  4. The waiting tool call resolves with the answer and the agent continues the run.

If the user does not respond within 5 minutes, the tool returns a timeout message and the agent proceeds using its best judgment, stating the assumption it made in the final answer.

SubAgent Architecture

When the user asks a follow-up question (i.e., conversation history exists), the system splits the work into three phases to reduce token consumption:

Phase Role History Included Request Pattern
1. Context Resolution Resolve the follow-up into a self-contained question Yes 1
2. SubAgent Tool Loop Run the full agentic research loop No Complexity-based function-loop cap
3. Answer Synthesis Combine research findings with conversation context Yes 1

The first question in a session (no history) skips directly to the tool loop with zero overhead.

The Phase 2 values 8 / 25 / 50 are maximum function-loop iterations per request, not hard limits on individual tool calls. One model turn may request multiple tools. If Harness receives no visible final answer, it may issue up to two additional requests in the same AgentSession.

Why it matters: In the previous design, conversation history was sent with every LLM call in the tool loop (5–50 calls). With SubAgent, history is only sent twice (Phase 1 + Phase 3), making the token cost nearly independent of history length.

Token-Based History with Auto-Compression

Instead of a fixed turn limit, conversation history is managed by a 200K token budget (estimated via character count / 3). When the estimated token count reaches 180K, the system automatically compresses older conversation turns:

  1. The most recent 20% of turns are kept verbatim (at least 1 Q&A pair).
  2. Older turns are summarized into a single condensed turn via an LLM call.
  3. The compressed history replaces the original in the session store.

Compression is chain-capable — when history grows again after a previous compression, the old summary is included in the next compression cycle. This supports long-running conversations while the compressed result remains within the hard limit.

The 200K limit is a hard guard: if compression fails while history is already above the limit, or the compressed history still exceeds it, the request stops instead of sending an oversized prompt to the model.

The top bar shows Main (Phase 1 + 3) and Sub (Phase 2) token usage separately. Clicking Main opens a modal showing the exact conversation turns the LLM remembers (including compressed summary turns highlighted in yellow), with a button to download the history as Markdown.

Agent Performance Optimizations

Beyond the SubAgent architecture, three additional optimizations reduce round trips and latency in the tool-calling loop. All are configurable under AgentSettings (see Configuration).

Question Complexity Routing

A rule-based classifier (no extra LLM call) estimates how much exploration a question likely needs and sizes the iteration budget accordingly:

Complexity Example Default Iteration Budget
Simple "Where is AgentService defined?" 8
Standard Default / ambiguous questions 25
Complex "How does the SubAgent flow work end-to-end?" 50

Ambiguous questions never classify as Simple, so misclassification can only use more iterations than necessary — never truncate a hard question prematurely.

Pre-fetched Symbol Context

Before the tool loop starts, the agent scans the question for symbol-like identifiers (e.g. AgentService, resolveSymbol) and verifies each candidate against the codebase via symbol analysis. Verified symbols get their definition, one-hop call graph (callers + callees), and references pre-fetched and injected into the first message — so the agent can start with evidence already in hand instead of spending iterations on find_definitionread_symbolfind_references. Unverified candidates (ordinary words that happen to look like identifiers) are silently discarded, so fabricated context is never injected.

Tool Scheduling

Microsoft Agent Framework owns scheduling for native function calls. The text-based ReAct fallback can execute independent calls from the same turn concurrently when EnableParallelToolExecution is enabled. In both runtimes, ask_user is isolated because it pauses the run while waiting for human input.

Application Architecture

  • Controllers are transport-focused and share the existing /api/CodeQA/* route surface: UploadController, AskController, FileController, and HistoryController.
  • QuestionExecutionService owns session lookup, conversation persistence, result mapping, and elapsed-time tracking for both synchronous and SSE requests.
  • AgentService is a small orchestration facade for no-history execution and the three follow-up phases.
  • ConversationContextService owns history estimation, compression, hard-cap enforcement, context resolution, and answer synthesis.
  • AgentResearchService selects Harness or ReAct; ReActAgentRunner owns only the compatibility loop and its tool batching.
  • Upload responsibilities are separated into SourceUploadService, SourceFilePolicy, ProjectPathResolver, and DeleteTokenService.

The controller split preserves the existing API paths:

Controller Routes
UploadController POST /api/CodeQA/upload, delete/cleanup, upload and user-project lists
AskController synchronous/SSE questions, user answers, provider discovery
FileController project structure and file reads
HistoryController conversation history by session

Verification

Run the complete test suite and a Release build before deployment:

dotnet test AnswerCode.Tests/AnswerCode.Tests.csproj
dotnet build AnswerCode.csproj --configuration Release

Tests cover Agent Framework tool invocation and empty-response recovery, controller route compatibility, question execution, history hard caps, upload path confinement, signed delete tokens, analysis services, and existing storage/tool behavior.

User Experience Notes

  • Uploading code creates an isolated workspace under wwwroot/source-code/{folderId}/.
  • The selected upload is automatically reused for follow-up questions in the UI.
  • Long-running answers stream progress live, including tool activity, summaries, and timing.
  • The final answer highlights relevant files and overall tool usage so users can inspect how the agent reached its conclusion.
  • Click Download Chat in the top bar at any time to export the full visible conversation — including every tool call's input and output — as a Markdown file for offline reading.

Project Structure

AnswerCode/
├── Controllers/
│   ├── AuthController.cs         # Google OAuth login/logout + dev-login
│   ├── AskController.cs          # Q&A, SSE, user answers, and provider endpoints
│   ├── DashboardController.cs    # Authenticated dashboard API (usage, folders)
│   ├── FileController.cs         # Project structure and file-reading endpoints
│   ├── HistoryController.cs      # Conversation history endpoint
│   └── UploadController.cs       # Upload, delete, cleanup, and project-list endpoints
├── Models/                       # DTOs and configuration models
├── Services/
│   ├── Agents/                   # Agent orchestration policies, prompts, and runners
│   ├── AgentFramework/           # Harness, IChatClient, AIFunction, and SSE adapters
│   ├── Analysis/                 # Roslyn + heuristic multi-language analysis services
│   ├── Lsp/                      # LSP client infrastructure (JSON-RPC, server manager)
│   ├── Providers/                # OpenAI, Azure OpenAI, compatible, and Foundry bridges
│   ├── Questions/                # Shared synchronous/streaming question execution
│   ├── Tools/                    # Agent tools + ReActParser
│   ├── Uploads/                  # Upload policy, path confinement, tokens, and storage
│   ├── UploadCleanupService.cs   # Background service for expired upload cleanup
│   └── UserStorageService.cs     # Per-user storage management and quota enforcement
├── lsp-servers/
│   ├── bin/                      # Bundled LSP binaries (gopls.exe, rust-analyzer.exe)
│   └── node_modules/             # Node-based LSP servers (typescript-language-server, pyright)
├── wwwroot/
│   ├── index.html                # Main Q&A interface
│   ├── dashboard.html            # User dashboard (storage, project management)
│   └── source-code/              # Uploaded source code folders (runtime, gitignored)
└── appsettings.json              # Main configuration

License

See repository for license details.

About

Ask questions about your codebase and get intelligent answers powered by an agentic LLM tool-calling loop.

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages