--- title: "Agentic Risk Analysis" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Agentic Risk Analysis} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` ## Overview PRA includes an AI agent framework with three routing modes for user input: | Input type | Route | Example | |------------|-------|---------| | `/command` | **Deterministic** — executes the tool directly, no LLM | `/mcs tasks=[...]` | | Numerical data for computation | **LLM tool call** — the model selects and calls the right tool | "Simulate 3 tasks: Normal(10,2)..." | | Conceptual / explanatory question | **RAG** — answered from the knowledge base, no tool call | "What is earned value?" | Three interfaces are available: 1. **Slash commands** (`/mcs`, `/evm`, `/risk`, ...) - deterministic tool calls that bypass the LLM for instant, reliable results 2. **Chat interface** (`pra_chat()`) - programmatic R chat object powered by ellmer where the LLM selects tools or answers from RAG 3. **Shiny app** (`pra_app()`) - browser-based experience combining all three modes, powered by shinychat ## Prerequisites ### Install Ollama Download from , then pull a model: ```bash ollama serve ollama pull llama3.2 ollama pull nomic-embed-text # for RAG embeddings ``` ### Install R dependencies ```{r eval = FALSE} install.packages(c("ellmer", "ragnar", "shiny", "bslib", "shinychat", "jsonlite")) ``` ## Slash Commands Slash commands provide deterministic tool execution, no LLM required. Type `/help` to see all available commands, or `/help ` for detailed usage with argument descriptions and examples. ### Available commands ```{r} library(PRA) cat(PRA:::format_help_overview()) ``` ### Getting help for a command Each command includes argument specifications, defaults, and examples: ```{r} cat(PRA:::format_command_help("mcs", PRA:::pra_command_registry()$mcs)) ``` ### Example: Monte Carlo simulation Run a simulation for a 3-task project directly: ```{r, eval = TRUE} set.seed(42) r <- PRA:::execute_command( '/mcs n=10000 tasks=[{"type":"normal","mean":10,"sd":2},{"type":"triangular","a":5,"b":10,"c":15},{"type":"uniform","min":8,"max":12}]' ) cat(r$result) ``` ```{r, eval = TRUE, fig.width = 7, fig.height = 4.5, out.width = "100%"} # The /mcs command stores results for chaining — visualize them: result <- PRA:::.pra_agent_env$last_mcs hist(result$total_distribution, freq = FALSE, breaks = 50, main = "Monte Carlo Simulation Results", xlab = "Total Project Duration/Cost", col = "#18bc9c80", border = "white" ) curve(dnorm(x, mean = result$total_mean, sd = result$total_sd), add = TRUE, col = "#2c3e50", lwd = 2 ) abline( v = quantile(result$total_distribution, c(0.50, 0.95)), col = c("#3498db", "#e74c3c"), lty = 2, lwd = 1.5 ) legend("topright", legend = c("Normal fit", "P50", "P95"), col = c("#2c3e50", "#3498db", "#e74c3c"), lty = c(1, 2, 2), lwd = c(2, 1.5, 1.5), cex = 0.8, bg = "white" ) ``` ### Example: Chaining MCS to contingency After running `/mcs`, chain to `/contingency` for the reserve estimate: ```{r, eval = TRUE} r <- PRA:::execute_command("/contingency phigh=0.95 pbase=0.50") cat(r$result) ``` ### Example: Sensitivity analysis Identify which tasks drive the most variance: ```{r, eval = TRUE} r <- PRA:::execute_command( '/sensitivity tasks=[{"type":"normal","mean":10,"sd":2},{"type":"triangular","a":5,"b":10,"c":15},{"type":"uniform","min":8,"max":12}]' ) cat(r$result) ``` ### Example: Earned Value Management Full EVM analysis with a single command: ```{r, eval = TRUE} r <- PRA:::execute_command( "/evm bac=500000 schedule=[0.2,0.4,0.6,0.8,1.0] period=3 complete=0.35 costs=[90000,195000,310000]" ) cat(r$result) ``` ### Example: Bayesian risk probability Calculate prior risk from two root causes: ```{r, eval = TRUE} r <- PRA:::execute_command( "/risk causes=[0.3,0.2] given=[0.8,0.6] not_given=[0.2,0.4]" ) cat(r$result) ``` Then update with observations — Cause 1 occurred, Cause 2 unknown: ```{r, eval = TRUE} r <- PRA:::execute_command( "/risk_post causes=[0.3,0.2] given=[0.8,0.6] not_given=[0.2,0.4] observed=[1,null]" ) cat(r$result) ``` ### Example: Second Moment Method Quick analytical estimate without simulation: ```{r, eval = TRUE} r <- PRA:::execute_command("/smm means=[10,12,8] vars=[4,9,2]") cat(r$result) ``` ### Input validation and guidance Missing or invalid arguments produce helpful error messages: ```{r, eval = TRUE} # Missing required arguments r <- PRA:::execute_command("/risk causes=[0.3]") cat(r$result) ``` ```{r, eval = TRUE} # Unknown command r <- PRA:::execute_command("/simulate") cat(r$result) ``` ## Chat Interface The chat interface routes queries through the LLM, which decides whether to call a tool or answer from RAG context: - **Numerical data** (distributions, costs, schedules) → LLM calls the appropriate tool and interprets the results - **Conceptual questions** ("what is CPI?", "how does MCS work?") → LLM answers from RAG knowledge base with source citations - **Ambiguous** → LLM uses RAG context and only calls a tool if data is present ```{r} library(PRA) chat <- pra_chat(model = "llama3.2") # Tool call: user provides numerical data chat$chat("Run a Monte Carlo simulation for a 3-task project with Task A ~ Normal(10, 2), Task B ~ Triangular(5, 10, 15), Task C ~ Uniform(8, 12). Use 10,000 simulations.") # RAG: conceptual question, no computation needed chat$chat("What is the difference between SPI and CPI?") ``` For guaranteed reliability with computations, use `/commands` instead. The chat interface is best suited for exploratory questions and interpretation. ### Using cloud models For better accuracy with complex queries, supply a pre-configured ellmer chat object: ```{r} # OpenAI chat <- pra_chat(chat = ellmer::chat_openai(model = "gpt-4o")) # Anthropic chat <- pra_chat(chat = ellmer::chat_anthropic(model = "claude-sonnet-4-20250514")) ``` ### Notes on chat reliability - **Conceptual questions** work well even with small models since they only require reading RAG context, not tool calling. - **Tool calling** quality depends on model size. `llama3.2` (3B) handles simple single-tool queries; larger models (8B+) are more reliable for multi-step chains. - If the model attempts to call a tool for a conceptual question, or describes what it would do instead of calling tools, use `/commands` for deterministic results. ## Interactive Shiny App For a browser-based experience with streaming responses and inline visualizations: ```{r} pra_app() ``` The app supports all three input modes in the same chat panel: - Type `/mcs tasks=[...]` for **instant deterministic** results - Type "Simulate 3 tasks..." for **LLM tool calling** - Type "What is earned value?" for **RAG-powered answers** ```{r, echo = FALSE, eval = TRUE, out.width = "100%"} knitr::include_graphics("../man/figures/pra-app-welcome.png") ``` Clicking an example prompt executes the `/command` instantly and displays rich results with tables and plots: ```{r, echo = FALSE, eval = TRUE, out.width = "100%"} knitr::include_graphics("../man/figures/pra-app-tool-call.png") ``` ### Features - **Three input modes** — `/commands` (deterministic), natural language (LLM tool calls), and conceptual questions (RAG) - **Clickable example prompts** that execute `/commands` on click - **Streaming chat** powered by shinychat with token-by-token responses - **Inline tool results** displayed as rich HTML tables and plots - **RAG source citations** — the agent cites knowledge base files for conceptual answers - **Connection status** badge showing model connectivity - **Collapsible sidebar** with model selection, RAG toggle, and CSV upload - **Export** — download the conversation as a markdown file ### Configuration options ```{r} # Custom model and port pra_app(model = "qwen2.5", port = 3838) # Disable RAG for faster responses pra_app(rag = FALSE) ``` ## RAG Knowledge Base The agent is enhanced with domain knowledge through retrieval-augmented generation (RAG). When RAG context is retrieved, the agent cites the source files in its response. ### Built-in knowledge files | File | Topics | |------|--------| | `mcs_methods.md` | Distribution selection, correlation, interpreting percentiles | | `evm_standards.md` | EVM metrics, performance indices, forecasting methods | | `bayesian_risk.md` | Prior/posterior risk, Bayes' theorem for root cause analysis | | `learning_curves.md` | Sigmoidal models (logistic, Gompertz, Pearl), curve fitting | | `sensitivity_contingency.md` | Variance decomposition, contingency reserves | | `pra_functions.md` | PRA package function reference | ### How RAG context flows 1. User asks a question 2. The question is embedded and matched against the knowledge base using hybrid search (vector similarity + BM25) 3. The top 3 matching chunks are prepended to the query with `[Source: filename]` tags 4. The agent is instructed to cite these sources in its response 5. The agent uses the context to inform its answer while still calling tools for computation ### Adding your own documents ```{r} store <- build_knowledge_base() # Add a single file add_documents(store, "path/to/my_risk_register.md") # Add all .md and .txt files in a directory add_documents(store, "path/to/project_docs/") ``` ### Disabling RAG ```{r} # Chat without RAG chat <- pra_chat(model = "llama3.2", rag = FALSE) # App without RAG pra_app(rag = FALSE) ``` ## Available Commands and Tools ### Slash commands (deterministic) | Command | Description | |---------|-------------| | `/mcs` | Monte Carlo simulation with task distributions | | `/smm` | Second Moment Method (analytical estimate) | | `/contingency` | Contingency reserve from last MCS | | `/sensitivity` | Variance contribution per task | | `/evm` | Full Earned Value Management analysis | | `/risk` | Bayesian prior risk probability | | `/risk_post` | Bayesian posterior risk after observations | | `/learning` | Sigmoidal learning curve fit and prediction | | `/dsm` | Design Structure Matrix | | `/help` | List all commands or get help for one | ### LLM tools (via chat) | Module | Tool | Use case | |--------|------|----------| | Simulation | `mcs_tool` | Full Monte Carlo with distributions | | Analytical | `smm_tool` | Quick mean/variance estimate | | Post-MCS | `contingency_tool` | Reserve at confidence level | | Post-MCS | `sensitivity_tool` | Variance contribution per task | | EVM | `evm_analysis_tool` | All 12 EVM metrics in one call | | Bayesian | `risk_prob_tool` | Prior risk from root causes | | Bayesian | `risk_post_prob_tool` | Posterior risk after observations | | Bayesian | `cost_pdf_tool` | Prior cost distribution | | Bayesian | `cost_post_pdf_tool` | Posterior cost distribution | | Learning | `fit_and_predict_sigmoidal_tool` | Pearl/Gompertz/Logistic | | DSM | `parent_dsm_tool` | Resource-task dependencies | | DSM | `grandparent_dsm_tool` | Risk-resource-task dependencies | ## Evaluation with vitals PRA includes an evaluation framework for measuring LLM tool-calling accuracy using the [vitals](https://vitals.tidyverse.org) package. The evaluation suite in `inst/eval/pra_eval.R` tests 15 scenarios across three tiers: | Tier | Description | Example | |------|-------------|---------| | Single-tool | One tool call | "Simulate 3 tasks with distributions..." | | Multi-tool chain | Sequential tool calls | "Run MCS then calculate contingency at 95%" | | Open-ended | Requires interpretation | "My project is behind schedule, here's EVM data..." | ```{r} # Run evaluation source(system.file("eval/pra_eval.R", package = "PRA")) results <- run_pra_eval(model = "llama3.2") # Compare models comparison <- run_pra_comparison( models = c("llama3.2", "qwen2.5"), rag_options = c(TRUE, FALSE) ) ``` ## Troubleshooting ### Tool calling not working Small models sometimes describe what they would do rather than actually calling tools. Use `/commands` for reliable deterministic execution: ```{r} # Instead of asking the LLM: chat$chat("Run a Monte Carlo simulation...") # Use the /command directly in the app: # /mcs tasks=[{"type":"normal","mean":10,"sd":2}] ``` Other workarounds for LLM chat: - Use `llama3.1` (8B) or larger for better tool calling than 3B models - Be explicit: "Call the mcs_tool with these parameters..." - Use a cloud model: `pra_chat(chat = ellmer::chat_openai(model = "gpt-4o"))` ### Slow responses - Use a smaller model (`llama3.2` is 3B, faster than `llama3.1` 8B) - Ensure GPU acceleration is active (`ollama ps`) - Disable RAG if not needed: `pra_chat(rag = FALSE)` - Use `/commands` — they execute instantly without LLM overhead ### RAG build fails ```{r} install.packages("ragnar") ``` ```bash ollama pull nomic-embed-text ``` ### Source citations missing If the agent does not cite sources in RAG-enabled responses, try: - Using a larger model (8B+ models follow citation instructions more reliably) - Being explicit: "Cite your sources in the response"