diff --git a/.gitignore b/.gitignore index 0c5e9d3a..14b1b847 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ wheels/ .venv/ workspace/ workspace_*/ +examples/**/uv.lock # ursa src/ursa/util/agent_memory_db/ diff --git a/README.md b/README.md index 9f5a9f9d..3a1349c8 100644 --- a/README.md +++ b/README.md @@ -36,23 +36,14 @@ The MkDocs documentation in `docs/` is organized around installation, getting st You can install `ursa` as a command line app with `pip install`; or with [`uv`](https://docs.astral.sh/uv/) via ```bash -uv tool install ursa-ai +uv tool install 'ursa[dashboard]' ``` -A reusable YAML configuration file is the preferred way to select endpoints and runtime settings. For example: +A standard OpenAI setup needs no configuration file: -```yaml -llm_model: - model: openai:gpt-5.2 - api_key: - env: OPENAI_API_KEY -workspace: . -``` - -Then start the command line app with: - -``` -ursa --config config.yaml +```bash +export OPENAI_API_KEY="..." +ursa ``` This starts the full-screen terminal app. Type `/` to browse commands, diff --git a/configs/example.yaml b/configs/example.yaml index d21fc2c7..7cc972b4 100644 --- a/configs/example.yaml +++ b/configs/example.yaml @@ -6,7 +6,8 @@ use_web: true inference_providers: openai_public: base_url: https://api.openai.com/v1 - api_key_env: OPENAI_API_KEY + api_key: + env: OPENAI_API_KEY llm_model: # Model options depend on the selected provider: # https://reference.langchain.com/python/langchain/models/#langchain.chat_models.init_chat_model diff --git a/docs/agents/acquisition/arxiv.md b/docs/agents/acquisition/arxiv.md index 2ee071e7..44c01742 100644 --- a/docs/agents/acquisition/arxiv.md +++ b/docs/agents/acquisition/arxiv.md @@ -89,9 +89,9 @@ By default, the agent writes artifacts under the agent den: - final direct-summarization file: `arxiv_generated_summaries/final_summary.txt` - RAG workflow artifacts are managed by the shared RAG path when `rag_embedding` is provided. -## CLI +## TUI -The interactive CLI registers this agent as: +The TUI registers this agent as: ```text arxiv diff --git a/docs/agents/acquisition/index.md b/docs/agents/acquisition/index.md index 375e2e58..df21c744 100644 --- a/docs/agents/acquisition/index.md +++ b/docs/agents/acquisition/index.md @@ -95,6 +95,8 @@ Acquired documents are stored under the configured `database_path` inside the ag Exact filenames and citations are determined by each concrete acquisition agent's `_id()` and `_citation()` methods. -## CLI availability +## TUI availability -The interactive CLI currently registers `arxiv` and `web` acquisition agents. `OSTIAgent` is exported from `ursa.agents` for Python/API use; it is not currently registered as a CLI short name in the inspected source. +The TUI currently registers `arxiv` and `web` acquisition agents. `OSTIAgent` +is exported from `ursa.agents` for Python/API use; it is not currently +registered as a TUI short name in the inspected source. diff --git a/docs/agents/acquisition/osti.md b/docs/agents/acquisition/osti.md index be411cf3..d184da5e 100644 --- a/docs/agents/acquisition/osti.md +++ b/docs/agents/acquisition/osti.md @@ -97,7 +97,7 @@ By default, the agent writes artifacts under the agent den: - final direct-summarization file: `acq_summaries/final_summary.txt` - RAG workflow artifacts are managed by the shared RAG path when `rag_embedding` is provided. -## CLI +## TUI `OSTIAgent` is exported from `ursa.agents` for Python/API use: @@ -105,4 +105,4 @@ By default, the agent writes artifacts under the agent den: from ursa.agents import OSTIAgent ``` -In the inspected source, it is not currently registered as an interactive CLI short name. +In the inspected source, it is not currently registered as a TUI short name. diff --git a/docs/agents/acquisition/web-search.md b/docs/agents/acquisition/web-search.md index 53bb8582..d68a2de2 100644 --- a/docs/agents/acquisition/web-search.md +++ b/docs/agents/acquisition/web-search.md @@ -96,9 +96,9 @@ By default, the agent writes artifacts under the agent den: - final direct-summarization file: `acq_summaries/final_summary.txt` - RAG workflow artifacts are managed by the shared RAG path when `rag_embedding` is provided. -## CLI +## TUI -The interactive CLI registers this agent as: +The TUI registers this agent as: ```text web diff --git a/docs/agents/chat.md b/docs/agents/chat.md index 3d554527..c06e1c6f 100644 --- a/docs/agents/chat.md +++ b/docs/agents/chat.md @@ -21,7 +21,7 @@ state = agent.invoke("Summarize the files in this workspace.") print(agent.format_result(state)) ``` -For conversational continuation, reuse the returned state through `format_query` or let the CLI maintain state for you. +For conversational continuation, reuse the returned state through `format_query` or let the TUI maintain state for you. ```python state = agent.invoke("Remember that this project studies alloy phase stability.") @@ -59,7 +59,7 @@ When `use_web=True`, it also binds: - `run_osti_search` - `run_arxiv_search` -If persistent RAG tools are configured through `rag_tools`, `AgentWithTools` can expose those as additional tools. MCP tools can also be attached in the CLI when MCP servers are configured. +If persistent RAG tools are configured through `rag_tools`, `AgentWithTools` can expose those as additional tools. MCP tools can also be attached in the TUI when MCP servers are configured. ## Graph behavior @@ -75,11 +75,12 @@ This means `ChatAgent` can use multiple tools over multiple turns, but it does n ## BasicChatAgent -The module also contains `BasicChatAgent`, a simple chat-only implementation with no tool loop. It is useful for minimal conversational behavior, but `ChatAgent` is the public, tool-capable chat agent exported by `ursa.agents` and used by the CLI `chat` behavior. +The module also contains `BasicChatAgent`, a simple chat-only implementation with no tool loop. It is useful for minimal conversational behavior, but `ChatAgent` is the public, tool-capable chat agent exported by `ursa.agents` and used by the TUI `chat` behavior. -## CLI usage +## TUI usage -In the interactive URSA CLI, use the `chat` agent. Web/search tools are opt-in: +In the URSA TUI, use the `chat` agent. Web/search tools are opt-in through a CLI +flag: ```bash ursa --use-web diff --git a/docs/agents/dsi.md b/docs/agents/dsi.md index e4787b75..d56333f6 100644 --- a/docs/agents/dsi.md +++ b/docs/agents/dsi.md @@ -43,7 +43,7 @@ When initializing `DSIAgent`, you can customize its behavior with these paramete ## Advanced Usage -### From the URSA CLI +### From the URSA TUI ```bash ursa % ursa diff --git a/docs/agents/index.md b/docs/agents/index.md index ceadeaf4..107b6637 100644 --- a/docs/agents/index.md +++ b/docs/agents/index.md @@ -2,7 +2,7 @@ URSA agents are reusable behaviors that can chat, plan, execute, search, maintain persistent research artifacts, reason over documents, refine prompts, or interact with external tools. -The interactive command-line interface exposes common agents by short names, including: +The TUI exposes common agents by short names, including: - `chat` - `plan` @@ -30,7 +30,8 @@ Additional agents may be available depending on optional dependencies and config ## Web and external information -Web/search tools are opt-in for information-control reasons. For CLI sessions, enable them with: +Web/search tools are opt-in for information-control reasons. When launching the +TUI, enable them with this CLI flag: ```bash ursa --use-web diff --git a/docs/chatollama_setup.md b/docs/chatollama_setup.md deleted file mode 100644 index 62a330aa..00000000 --- a/docs/chatollama_setup.md +++ /dev/null @@ -1,15 +0,0 @@ -# Running with ChatOllama - -Disable OPENAI_API_KEY by setting the following two env variales: -(without both of these env vars ollama complains about auth) - -``` -$ export OPENAI_API_KEY=ollama -$ export OPENAI_BASE_URL= -``` - -Example Auth Error - -``` -openai.AuthenticationError: Error code: 401 - {'error': {'message': 'Incorrect API key provided: ollama. You can find your API key at https://platform.openai.com/account/api-keys.', 'type': 'invalid_request_error', 'param': None, 'code': 'invalid_api_key'}} -``` diff --git a/docs/cli.md b/docs/cli.md deleted file mode 100644 index f0be77cb..00000000 --- a/docs/cli.md +++ /dev/null @@ -1,3 +0,0 @@ -# CLI - -The CLI getting-started guide has moved to [Getting Started - CLI](getting-started/cli.md). Detailed persistence commands are documented under [Persistence](persistence/index.md). diff --git a/docs/command_line.md b/docs/command_line.md index bea078cb..0cbbfc92 100644 --- a/docs/command_line.md +++ b/docs/command_line.md @@ -3,38 +3,43 @@ You can install `ursa` as a command line app with `pip install`; or with [`uv`](https://docs.astral.sh/uv/) via ```bash -uv tool install ursa-ai +uv tool install 'ursa[dashboard]' ``` To use the command line app, run -``` +```bash ursa --llm_model.model openai:gpt-5.2 ``` This starts the full-screen terminal app. Type `/` to browse commands, `#` to choose an agent behavior, or `@` to insert a workspace path. -See [Getting Started - CLI](getting-started/cli.md#full-screen-interface-controls) -for prompt editing, multiline input, clipboard, and exit behavior. You can chat with an LLM by simply typing into the terminal. -``` +```text How are you? Thanks for asking! I’m doing well. How are you today? What can I help you with? ``` Use the required `#` macro to route a prompt to another agent behavior: -``` +```text #plan Write a python script to do linear regression using only numpy. ``` -Agent macros route only the prompt in which they appear. Output from a previous -agent is not automatically appended to the next prompt; quote or reference any -needed result explicitly when switching behaviors. +If you run subsequent agents, the last output will be appended to the prompt for the next agent. -You can get a list of available command line options via +So, to run the Planning Agent followed by the Execution Agent: +```text +#plan Write a python script to do linear regression using only numpy. + +... + +#execute Execute the plan. ``` + +You can get a list of available command line options via +```bash ursa --help ``` diff --git a/docs/configuration/files-and-env.md b/docs/configuration/files-and-env.md index ae35b5f0..5c281556 100644 --- a/docs/configuration/files-and-env.md +++ b/docs/configuration/files-and-env.md @@ -33,23 +33,19 @@ On every platform, URSA then checks `~/.config/ursa/config.yaml` and, when `XDG_CONFIG_HOME` is set, `$XDG_CONFIG_HOME/ursa/config.yaml`. These user files are loaded in that order, with duplicates skipped. A missing file is ignored. -## YAML files: preferred +## User YAML files: preferred + +For defaults that should follow you across projects, edit the user config path +listed above. A small user config is usually better than a complete copy of the +resolved defaults. For example: ```yaml -llm_model: - model: openai:gpt-5.4 - api_key: - env: OPENAI_API_KEY -workspace: ./ursa-workspace -group: default -use_web: false -agent_config: - execute: - safe_codes: - - python +emb_model: + model: openai:text-embedding-3-large ``` -Run: +OpenAI chat needs no YAML; set `OPENAI_API_KEY` and run `ursa`. Use an explicit +file only for a project-specific or one-off override: ```bash ursa --config config.yaml @@ -89,9 +85,17 @@ URSA exposes environment-variable equivalents for many CLI settings, but for mos Example: -```bash -export OPENAI_API_KEY="..." -``` +=== "macOS/Linux" + + ```bash + export OPENAI_API_KEY="..." + ``` + +=== "Windows PowerShell" + + ```powershell + $env:OPENAI_API_KEY = "..." + ``` Then in YAML: @@ -104,9 +108,18 @@ llm_model: You can also set URSA configuration options directly: -```bash -URSA_LLM_MODEL__MODEL=openai:gpt-5.4 ursa -``` +=== "macOS/Linux" + + ```bash + URSA_LLM_MODEL__MODEL=openai:gpt-5.4 ursa + ``` + +=== "Windows PowerShell" + + ```powershell + $env:URSA_LLM_MODEL__MODEL = "openai:gpt-5.4" + ursa + ``` Use `ursa --help` to view supported `URSA_...` variables. @@ -148,5 +161,4 @@ ursa --config ./.ursa/config.yaml --print-config=file,resolved The complete form is `--print-config=LEVEL[+],STAGE`. Levels are `system`, `user`, `file`, and `final`; stages are `merged` and `resolved`. Add `+` to include -lower-precedence sources. If you provide only a level, URSA uses the `resolved` -stage; for example, `--print-config=user` shows resolved user configuration. +lower-precedence sources. diff --git a/docs/configuration/index.md b/docs/configuration/index.md index d519fe5a..0faef428 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -1,47 +1,55 @@ # Configuration -YAML files make model, workspace, agent, RAG, and MCP settings easy to reuse. -URSA configs can also be +OpenAI works with the built-in defaults after you set `OPENAI_API_KEY`; you do +not need a config file for the standard first run. YAML files make changed +model, workspace, agent, RAG, and MCP settings easy to reuse. URSA configs can be [layered with environment variables and CLI flags][configuration-files-cli-flags-and-environment-variables], with commands to inspect the resulting configuration. -## Minimal config file +## Prefer a user config -Create `config.yaml`: +Put settings that should apply across projects in your platform's persistent +user config. For example, this changes only the embedding model: ```yaml -llm_model: - model: openai:gpt-5.4 - api_key: - env: OPENAI_API_KEY -workspace: ./ursa-workspace +emb_model: + model: openai:text-embedding-3-large ``` -Run URSA with: +See [Configuration files, CLI flags, and environment variables][configuration-files-cli-flags-and-environment-variables] +for user config paths and precedence. Use `--config` for a project-specific or +one-off override, not as a requirement for ordinary OpenAI use. -```bash -ursa --config config.yaml -``` +## Define reusable inference providers -## Common top-level settings +Put endpoint and credential settings under `inference_providers`, then select a +provider from each model. This keeps connection details in one place when chat +and embedding models share an endpoint: ```yaml -workspace: ./ursa-workspace -group: default -thread_id: null -use_web: false +inference_providers: + research_gateway: + base_url: https://models.example.edu/v1 + api_key: + env: RESEARCH_LLM_API_KEY llm_model: - model: openai:gpt-5.4 - api_key: - env: OPENAI_API_KEY + model: openai:chat-model-name + inference_provider: research_gateway max_completion_tokens: 10000 -emb_model: null -rag_tools: [] -agent_config: {} -mcp_servers: {} +emb_model: + model: openai:embedding-model-name + inference_provider: research_gateway ``` -Use: +The provider name is local to your configuration. A selected provider must +exist, and model-specific values override values inherited from it. Keep secrets +in environment references; put non-secret values such as `base_url` directly in +the YAML file. + +For ordinary OpenAI use, the built-in `openai` provider already supplies the +endpoint. Set `OPENAI_API_KEY` and skip this configuration entirely. + +Use this command to inspect the complete resolved configuration: ```bash ursa --print-config @@ -49,71 +57,25 @@ ursa --print-config to inspect the full active configuration, including defaults and null values. -## Model configuration - -URSA uses LangChain's unified model initialization. Model names usually use this form: - -```text -: -``` - -Examples: - -```yaml -llm_model: - model: openai:gpt-5.4 -``` - -```yaml -llm_model: - model: anthropic:claude-sonnet-4-5 -``` - -```yaml -llm_model: - model: google_genai:gemini-2.5-pro -``` - -```yaml -llm_model: - model: ollama:gpt-oss-2b - base_url: http://localhost:11434 -``` - -## Credential references - -Keep credentials out of configuration files by referencing environment -variables or the operating system keyring. See [Secrets][secrets] for keyring -and MCP header examples. - -## Inference providers - -Use `inference_providers` to share endpoint and credential settings between -models: +## Common top-level settings ```yaml -inference_providers: - openai_public: - base_url: https://api.openai.com/v1 - api_key: - env: OPENAI_API_KEY +workspace: ./ursa-workspace +group: default +thread_id: null +use_web: false llm_model: model: openai:gpt-5.4 - inference_provider: openai_public -emb_model: - model: openai:text-embedding-3-large - inference_provider: openai_public +emb_model: null +rag_tools: [] +agent_config: {} +mcp_servers: {} ``` -Models inherit unspecified provider settings; model-specific values override -them. URSA validates provider values and rejects any model whose selected -provider does not exist. Set a nullable model value to `null` to clear an -inherited value. - -URSA includes an explicit `openai` provider by default, with -`https://api.openai.com/v1` as its base URL and `OPENAI_API_KEY` as its API-key -environment reference. The default chat model selects this provider. You can -override its settings by defining `inference_providers.openai`. +Model names normally use `:`. See +[Models and inference providers][models-and-inference-providers] for complete, +tabbed examples covering OpenAI, OpenAI-compatible services, Anthropic, Google, +Ollama, and Azure OpenAI. ## `use_web` and `agent_config` @@ -134,9 +96,7 @@ directory named `tmp` already exists, URSA uses that directory. ## More configuration topics -- [OpenAI-compatible endpoints][openai-compatible-endpoints] -- [Ollama and local endpoints][ollama-and-local-endpoints] -- [LangChain providers][langchain-providers] +- [Models and inference providers][models-and-inference-providers] - [Secrets][secrets] - [Configuration files, CLI flags, and environment variables][configuration-files-cli-flags-and-environment-variables] - [MCP server configuration][mcp-server-configuration] diff --git a/docs/configuration/langchain-providers.md b/docs/configuration/langchain-providers.md deleted file mode 100644 index cc719a58..00000000 --- a/docs/configuration/langchain-providers.md +++ /dev/null @@ -1,67 +0,0 @@ -# LangChain providers - -URSA initializes chat and embedding models through LangChain. The model string usually uses: - -```text -: -``` - -The following provider integrations are installed with URSA's core dependencies and are common choices for URSA workflows. - -## OpenAI - -```yaml -llm_model: - model: openai:gpt-5.4 - api_key: - env: OPENAI_API_KEY -``` - -## Anthropic - -```yaml -llm_model: - model: anthropic:claude-sonnet-4-5 - api_key: - env: ANTHROPIC_API_KEY -``` - -## Google GenAI - -```yaml -llm_model: - model: google_genai:gemini-2.5-pro - api_key: - env: GOOGLE_API_KEY -``` - -## Ollama - -```yaml -llm_model: - model: ollama:gpt-oss-20b - base_url: http://localhost:11434 -``` - -## Azure OpenAI - -```yaml -llm_model: - model: azure_openai:deployment-name - base_url: https://your-resource.openai.azure.com/ - api_key: - env: AZURE_OPENAI_API_KEY -``` - -Azure deployments often need provider-specific settings. LangChain accepts additional provider keyword arguments, and URSA allows extra model fields in the YAML model configuration. - -## Additional provider options - -URSA passes model settings to LangChain. For provider-specific options, consult the LangChain provider documentation: - -- `langchain-openai` -- `langchain-anthropic` -- `langchain-google-genai` -- `langchain-ollama` - -If you use a provider integration that is not installed by URSA, install the relevant LangChain package in the same environment. diff --git a/docs/configuration/models.md b/docs/configuration/models.md new file mode 100644 index 00000000..8adf54c1 --- /dev/null +++ b/docs/configuration/models.md @@ -0,0 +1,235 @@ +# Models and inference providers + +URSA initializes chat and embedding models through LangChain. A model name +normally uses `:`. Connection settings belong in a named +`inference_providers` entry; `llm_model` and `emb_model` select that entry with +`inference_provider`. + +For the standard OpenAI service, no YAML is required. Set `OPENAI_API_KEY` and +run `ursa`; URSA's built-in `openai` provider supplies the endpoint and defaults. + +## Hosted and local model examples + +=== "OpenAI (built in)" + + === "macOS/Linux" + + ```bash + export OPENAI_API_KEY="..." + ursa + ``` + + === "Windows PowerShell" + + ```powershell + $env:OPENAI_API_KEY = "..." + ursa + ``` + + To change only the model in your user config: + + ```yaml + llm_model: + model: openai:gpt-5.4 + ``` + +=== "OpenAI-compatible" + + ```yaml + inference_providers: + research_gateway: + base_url: https://models.example.edu/v1 + api_key: + env: RESEARCH_LLM_API_KEY + llm_model: + model: openai:my-model-name + inference_provider: research_gateway + ``` + + Use the provider's actual model name and put the non-secret URL directly in + the file. + +=== "Anthropic" + + ```yaml + inference_providers: + anthropic: + api_key: + env: ANTHROPIC_API_KEY + llm_model: + model: anthropic:claude-sonnet-4-5 + inference_provider: anthropic + ``` + +=== "Google GenAI" + + ```yaml + inference_providers: + google: + api_key: + env: GOOGLE_API_KEY + llm_model: + model: google_genai:gemini-2.5-pro + inference_provider: google + ``` + +=== "Ollama" + + Install [Ollama](https://ollama.com/), run `ollama pull gpt-oss-20b`, and + use: + + ```yaml + inference_providers: + local_ollama: + base_url: http://localhost:11434 + llm_model: + model: ollama:gpt-oss-20b + inference_provider: local_ollama + emb_model: + model: ollama:nomic-embed-text:latest + inference_provider: local_ollama + ``` + +=== "Azure OpenAI" + + ```yaml + inference_providers: + azure: + base_url: https://your-resource.openai.azure.com/ + api_key: + env: AZURE_OPENAI_API_KEY + llm_model: + model: azure_openai:deployment-name + inference_provider: azure + ``` + + Azure deployments can require additional provider-specific model fields. + URSA passes extra model settings through to the LangChain integration. + +## Use one provider for chat and embeddings + +Models inherit endpoint and credential values from the selected provider. They +can share one provider while retaining their own model names: + +```yaml +inference_providers: + lab: + base_url: https://models.example.edu/v1 + api_key: + env: LAB_LLM_API_KEY +llm_model: + model: openai:chat-model + inference_provider: lab +emb_model: + model: openai:embedding-model + inference_provider: lab +``` + +A value set directly on a model overrides the provider value. Set a nullable +model value to `null` to clear an inherited value. + +## Temporary CLI overrides + +Configuration files are preferable for reusable endpoint settings, but every +model field can also be overridden for a single run. For example: + +=== "macOS/Linux" + + ```bash + ursa \ + --llm_model.model openai:my-model-name \ + --llm_model.inference_provider research_gateway + ``` + +=== "Windows PowerShell" + + ```powershell + ursa ` + --llm_model.model openai:my-model-name ` + --llm_model.inference_provider research_gateway + ``` + +The referenced `research_gateway` still comes from a loaded system, user, or +explicit config file. See [Files, CLI flags, and environment +variables][configuration-files-cli-flags-and-environment-variables] for the +complete precedence order. + +## TLS verification + +URSA verifies TLS certificates by default and loads the operating system trust +store. For a temporary test endpoint only, verification can be disabled on the +provider: + +```yaml +inference_providers: + test_endpoint: + base_url: https://test-model.example/v1 + ssl_verify: false +``` + +Disabling verification exposes credentials and traffic to interception. Install +the correct certificate authority instead whenever possible. + +## Install additional integrations + +URSA includes `langchain-openai`, `langchain-anthropic`, +`langchain-google-genai`, and `langchain-ollama`. Other model integrations use +their corresponding `langchain-*` package. LangGraph extensions such as durable +checkpoint backends use `langgraph-*` packages. + +=== "uv tool installation" + + Recreate URSA's isolated tool environment and add the required package with + `--with`. This example adds PostgreSQL checkpoint support: + + === "macOS/Linux" + + ```bash + uv tool install --force \ + --with langgraph-checkpoint-postgres \ + 'ursa[dashboard]' + ``` + + === "Windows PowerShell" + + ```powershell + uv tool install --force ` + --with langgraph-checkpoint-postgres ` + 'ursa[dashboard]' + ``` + + For an additional model provider, replace the `langgraph-*` package with + its integration package, for example `--with langchain-groq`. + +=== "uv virtual environment — macOS/Linux" + + ```bash + uv venv + source .venv/bin/activate + uv pip install 'ursa-ai[dashboard]' langgraph-checkpoint-postgres + ``` + +=== "uv virtual environment — Windows PowerShell" + + ```powershell + uv venv + .\.venv\Scripts\Activate.ps1 + uv pip install 'ursa-ai[dashboard]' langgraph-checkpoint-postgres + ``` + +Packages must be installed in the same environment as URSA. Installing them in +an unrelated project environment will not make them available to a `uv tool` +installation. + +## Local-model caveats + +Local models vary in tool-calling support, context length, instruction +following, and their ability to recover from execution errors. For +execution-heavy workflows, choose a model with reliable tool calling and test +it first in a disposable workspace. + +## Endpoint controls + +For controlled environments, combine custom endpoints with URSA groups and +allowed base URLs. See +[Groups and endpoint security](../persistence/groups-and-security.md). diff --git a/docs/configuration/ollama.md b/docs/configuration/ollama.md deleted file mode 100644 index c6b3375e..00000000 --- a/docs/configuration/ollama.md +++ /dev/null @@ -1,59 +0,0 @@ -# Ollama and local endpoints - -URSA can use local models through Ollama via LangChain's `ollama` provider. - -## Start Ollama - -Install Ollama from the [Ollama website](https://ollama.com/) and start the service. Then pull a model: - -```bash -ollama pull gpt-oss-20b -``` - -## Configure URSA - -Create `config.yaml`: - -```yaml -llm_model: - model: ollama:gpt-oss-20b - base_url: http://localhost:11434 - -workspace: ./ursa-ollama-workspace -``` - -Run: - -```bash -ursa --config config.yaml -``` - -## CLI equivalent - -```bash -ursa \ - --llm_model.model ollama:gpt-oss-20b \ - --llm_model.base_url http://localhost:11434 -``` - -## Embeddings with Ollama - -If you need an embedding model, configure `emb_model`: - -```yaml -emb_model: - model: ollama:nomic-embed-text:latest - base_url: http://localhost:11434 -``` - -## Caveats for local models - -Local models vary widely in: - -- tool-calling support, -- context length, -- instruction following, -- code-generation quality, -- ability to recover from execution errors. - -For execution-heavy workflows, use a model with reliable tool-calling behavior and test on a small workspace first. diff --git a/docs/configuration/openai-compatible.md b/docs/configuration/openai-compatible.md deleted file mode 100644 index 7e808250..00000000 --- a/docs/configuration/openai-compatible.md +++ /dev/null @@ -1,49 +0,0 @@ -# OpenAI-compatible endpoints - -Many hosted and self-hosted model services expose an OpenAI-compatible API. Configure these with the `openai` provider plus a custom `base_url`. - -## YAML configuration - -```yaml -llm_model: - model: openai:my-model-name - base_url: https://my-endpoint.example.com/v1 - api_key: - env: MY_ENDPOINT_API_KEY -``` - -Run: - -```bash -ursa --config config.yaml -``` - -## CLI override - -```bash -ursa \ - --llm_model.model openai:my-model-name \ - --llm_model.base_url https://my-endpoint.example.com/v1 \ - --llm_model.api_key.env MY_ENDPOINT_API_KEY -``` - -## SSL verification - -By default URSA verifies TLS certificates. If you are using a test endpoint with a custom certificate, you can configure: - -```yaml -llm_model: - model: openai:my-model-name - base_url: https://my-endpoint.example.com/v1 - api_key: - env: MY_ENDPOINT_API_KEY - ssl_verify: false -``` - -Only disable SSL verification when you understand the risk. - -## Endpoint allowlists and groups - -For controlled environments, combine custom endpoints with URSA groups and -allowed base URLs. See -[Groups and endpoint security](../persistence/groups-and-security.md). diff --git a/docs/getting-started/dashboard.md b/docs/getting-started/dashboard.md index 73cf52eb..4afceb64 100644 --- a/docs/getting-started/dashboard.md +++ b/docs/getting-started/dashboard.md @@ -2,24 +2,8 @@ The URSA web dashboard provides a browser-based interface for running URSA workflows. -!!! note "Screenshots forthcoming" - A complete dashboard walkthrough should include screenshots. This page currently covers installation and launch commands; a fuller visual guide can be added later. - -## Install dashboard dependencies - -Install URSA with the dashboard extra: - -=== "uv" - - ```bash - uv pip install "ursa-ai[dashboard]" - ``` - -=== "pip" - - ```bash - python -m pip install "ursa-ai[dashboard]" - ``` +Install URSA first with `uv tool install 'ursa[dashboard]'` as described in the +[getting started guide][getting-started]. ## Launch the dashboard @@ -39,7 +23,18 @@ ursa-dashboard \ --config config.yaml ``` -The config file initializes the dashboard LLM endpoint settings. +The optional config file initializes the dashboard LLM endpoint settings. It is +not needed for the built-in OpenAI provider. + +## First session + +1. Open **Settings → LLM** and confirm the endpoint and credential source. +2. Create a session. +3. Select a folder you are comfortable modifying, or choose **Temporary + workspace** for disposable work. +4. Choose an agent and submit a prompt. +5. Follow the activity timeline and inspect generated files in the artifacts + panel. ## Configure API credentials diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md new file mode 100644 index 00000000..5166318a --- /dev/null +++ b/docs/getting-started/index.md @@ -0,0 +1,178 @@ +# Getting started + +This guide takes you from installation to a first URSA conversation, then shows +the terminal interface and browser dashboard. OpenAI models work without a config +file; configuration is only needed when you want to change a default or use a +different endpoint. + +## 1. Install URSA + +URSA requires Python 3.11 or newer. The `uv` tool installation is recommended: + +=== "uv tool (recommended)" + + Install [`uv`](https://docs.astral.sh/uv/getting-started/installation/) if + needed, then install URSA and the dashboard in an isolated tool environment: + + ```bash + uv tool install 'ursa[dashboard]' + ``` + + Upgrade it later with: + + ```bash + uv tool upgrade ursa + ``` + +=== "venv + pip" + + Use this option when you already manage Python virtual environments: + + === "macOS/Linux" + + ```bash + python3 -m venv .venv + source .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install 'ursa-ai[dashboard]' + ``` + + === "Windows PowerShell" + + ```powershell + py -3 -m venv .venv + .\.venv\Scripts\Activate.ps1 + python -m pip install --upgrade pip + python -m pip install 'ursa-ai[dashboard]' + ``` + +=== "Conda + pip" + + ```bash + conda create -y -n ursa-env python=3.12 + conda activate ursa-env + python -m pip install 'ursa-ai[dashboard]' + ``` + +Verify both applications: + +```bash +ursa --help +ursa-dashboard --help +``` + +## 2. Start with the built-in OpenAI configuration + +Set your OpenAI API key and launch URSA: + +=== "macOS/Linux" + + ```bash + export OPENAI_API_KEY="..." + ursa + ``` + +=== "Windows PowerShell" + + ```powershell + $env:OPENAI_API_KEY = "..." + ursa + ``` + +That is a complete working setup. The built-in `openai` inference provider +already supplies the model and OpenAI base URL, so an OpenAI-only config file is +unnecessary. + +!!! warning "Choose workspaces deliberately" + The execution agent can write files and run commands. Start URSA in a + disposable exercise directory, or pass `--workspace` with a directory you + are comfortable modifying. + +## 3. Optional: customize your user configuration + +Use a user config for defaults that should follow you across projects. Do not +copy the same `config.yaml` into every project. + +| Platform | User configuration path | +| --- | --- | +| macOS | `~/Library/Application Support/ursa/config.yaml` | +| Linux | `~/.config/ursa/config.yaml` | +| Windows | `%APPDATA%/ursa/config.yaml` | + +For example, this changes only the embedding model and leaves the built-in +OpenAI chat configuration intact: + +```yaml +emb_model: + model: openai:text-embedding-3-large +``` + +Inspect the merged user configuration with: + +```bash +ursa --print-config=user,resolved +``` + +See [Configuration][configuration] for other providers and precedence rules. + +## 4. Learn the TUI + +Run `ursa`. The welcome panel confirms the active model, workspace, and agent. + +- Enter ordinary text to chat. +- Type `#` to open the agent picker. `#plan` creates a plan; `#execute` can use + tools, run commands, and create workspace artifacts. +- Type `/` to browse application commands. `/keymap` shows every shortcut. +- Type `@` to find and insert a workspace file into a prompt. + +Try these in order: + +```text +Explain the difference between the chat, planning, and execution agents. +``` + +```text +#plan Plan a small parameter sweep and describe the outputs we should retain. +``` + +```text +#execute Create hello_ursa.txt containing a one-sentence description of this workspace. +``` + +Review proposed tool actions before approving them. Use a named agent when you +want its state to persist between launches: + +```bash +ursa --name tutorial +``` + +The [TUI guide][getting-started-tui] covers commands, web-tool opt-in, and named +agents in more detail. + +## 5. Use the dashboard + +Launch the browser interface: + +```bash +ursa-dashboard +``` + +It opens `http://127.0.0.1:8080`. Then: + +1. Open **Settings → LLM** and confirm the endpoint and credential source. +2. Create a session and select a folder or a temporary workspace. +3. Choose an agent, enter a prompt, and follow the live activity timeline. +4. Inspect generated files in the workspace/artifacts panel. +5. Use **Environment runs** when you want to launch a team or symposium from + YAML instead of a single-agent session. + +The dashboard and TUI use the same URSA concepts, but browser credentials are +managed in **Settings** and each dashboard session has an explicit workspace. +See the [dashboard guide][getting-started-web-dashboard] for credential storage, +remote-access safety, and environment runs. + +## 6. Run an example + +Continue with the [examples gallery][examples]. The environment walkthrough is +a good first exercise; the Nomad/MIST example shows how URSA can call a served +scientific model through MCP. diff --git a/docs/getting-started/python-scripts.md b/docs/getting-started/python-scripts.md index 689ef758..cb44aa26 100644 --- a/docs/getting-started/python-scripts.md +++ b/docs/getting-started/python-scripts.md @@ -2,23 +2,57 @@ URSA agents can be used directly from Python. This is useful when you want to build repeatable workflows, integrate URSA with existing scripts, or compose agents programmatically. -## Prerequisites +## Set up a Python project -- URSA is installed in your Python environment. -- You have configured access to an LLM endpoint. -- You have a dedicated workspace for any execution tasks. +Install URSA in the project environment that will run your script. A separate +`uv tool install ursa` installation provides the `ursa` command, but its +isolated environment is not importable by project scripts. + +=== "uv project (recommended)" + + ```bash + uv init ursa-script + cd ursa-script + uv add ursa-ai + ``` + +=== "venv + pip — macOS/Linux" + + ```bash + mkdir ursa-script + cd ursa-script + python3 -m venv .venv + source .venv/bin/activate + python -m pip install --upgrade pip + python -m pip install ursa-ai + ``` + +=== "venv + pip — Windows PowerShell" + + ```powershell + New-Item -ItemType Directory ursa-script + Set-Location ursa-script + py -3 -m venv .venv + .\.venv\Scripts\Activate.ps1 + python -m pip install --upgrade pip + python -m pip install ursa-ai + ``` + +Before continuing, [configure an LLM endpoint][configuration] and choose a +dedicated workspace for execution tasks. ## Minimal execution-agent script Create `run_ursa.py`: ```python -from langchain.chat_models import init_chat_model from langchain_core.messages import HumanMessage from ursa.agents import ExecutionAgent +from ursa.cli.config import UrsaConfig, resolve_ursa_config -llm = init_chat_model(model="openai:gpt-5.4") +config = resolve_ursa_config(UrsaConfig()) +llm = config.llm_model.init_chat_model() agent = ExecutionAgent(llm=llm) result = agent.invoke({ @@ -36,38 +70,61 @@ print(result["messages"][-1].content) Run it: ```bash -python run_ursa.py +uv run run_ursa.py ``` !!! warning "Execution safety" `ExecutionAgent` can create files and run shell commands. Use a dedicated workspace and review generated code and commands. -## Use a local or custom endpoint - -The Python API uses LangChain chat models, so the same provider packages and endpoint settings apply. For example, with Ollama: +## Initialize chat and embedding models from a URSA config -```python -from langchain.chat_models import init_chat_model +Use the same YAML model configuration in scripts that you use with the TUI and +dashboard. For example, create `config.yaml`: -llm = init_chat_model( - model="ollama:llama3.1", - base_url="http://localhost:11434", -) +```yaml +emb_model: + model: openai:text-embedding-3-large ``` -For a custom OpenAI-compatible endpoint: +The built-in `openai` inference provider supplies the endpoint and reads +`OPENAI_API_KEY`. Load, resolve, and instantiate both models: ```python -import os -from langchain.chat_models import init_chat_model +from pathlib import Path + +from ursa.cli.config import UrsaConfig, resolve_ursa_config -llm = init_chat_model( - model="openai:my-model-name", - base_url="https://my-endpoint.example.com/v1", - api_key=os.environ["MY_ENDPOINT_API_KEY"], +config = resolve_ursa_config(UrsaConfig.from_file(Path("config.yaml"))) + +chat_model = config.llm_model.init_chat_model() +embedding_model = ( + config.emb_model.init_embedding() + if config.emb_model is not None + else None ) ``` +Resolution applies the selected `inference_providers` settings and resolves API +key references in memory. It does not write the secret back to the YAML file. +`UrsaConfig.from_file()` reads the specified file; use the CLI when you need its +full system, user, environment, explicit-file, and command-line precedence. + +The resulting objects are ordinary LangChain chat and embedding models and can +be passed to URSA agents, environments, or other LangChain components. + +## Connect an MCP server and add its tools to an agent + +Follow the [standalone MCP tools example](../examples/mcp_agent_tools/index.md) +to start a local server, configure it, discover its tools, attach them to a +`ChatAgent`, and invoke the agent from Python. + +## Use another provider + +Keep endpoint and credential settings in the URSA configuration rather than +duplicating them in Python. See [Models and inference +providers][models-and-inference-providers] for hosted, OpenAI-compatible, and +local examples. The resolved model object above uses those same settings. + ## Compose agents with environments When one agent is not the right shape for the work, URSA environments let you diff --git a/docs/getting-started/cli.md b/docs/getting-started/tui.md similarity index 64% rename from docs/getting-started/cli.md rename to docs/getting-started/tui.md index 5a5fddf1..9ce48256 100644 --- a/docs/getting-started/cli.md +++ b/docs/getting-started/tui.md @@ -1,38 +1,44 @@ -# Getting Started - CLI +# Getting Started - TUI -This guide walks through starting URSA from the terminal, chatting with the -default assistant, and routing messages to the planning and execution agents. +This guide walks through URSA's terminal user interface (TUI): starting it with +the `ursa` CLI command, chatting with the default assistant, and running the +planning and execution agents. ## Prerequisites -- URSA is installed. See [Installation](../installation/index.md). -- `OPENAI_API_KEY` is set for the default OpenAI endpoint. +- URSA is installed. See [Getting started][getting-started]. +- You have access to an LLM endpoint. - You have a dedicated workspace directory for files URSA may create or modify. !!! warning "Be aware of your workspace" The execution agent can write files and run shell commands. Be careful using workspaces with source tree or data directory you cannot risk modifying. Good practice is to make backups or copies of directories before working. -For Ollama, Anthropic, Google GenAI, custom OpenAI-compatible endpoints, and -configuration files, see [Configuration](../configuration/index.md). +## 1. Start with the default configuration -## 1. Start URSA - -Set your OpenAI API key and launch URSA: +OpenAI works without a config file. Set the API key in your shell: === "macOS/Linux" ```bash export OPENAI_API_KEY="..." - ursa ``` === "Windows PowerShell" ```powershell $env:OPENAI_API_KEY = "..." - ursa ``` +The built-in `openai` provider supplies the model and base URL. Use a persistent +[user configuration][configuration-files-cli-flags-and-environment-variables] +only when you need to change a default or select another provider. + +## 2. Start URSA + +```bash +ursa +``` + You should see the full-screen URSA interface. Type `/` to browse app commands, `#` to route a message to an agent, or `@` to insert a workspace path. @@ -49,7 +55,7 @@ path. | **Ctrl+Q** or `/exit` | Exit gracefully, waiting for an active turn to finish. | | **Ctrl+D** | Exit immediately without cleanup; reserve this for a stuck turn. | -## 2. Chat with the assistant +## 3. Chat with the assistant ```text Summarize what URSA can help me do. @@ -57,10 +63,10 @@ Summarize what URSA can help me do. Plain text input is handled by the default chat behavior. -## 3. Route a message to the planning agent +## 4. Use the planning agent -Route a message to the planning agent with the `#plan` macro. Typing `#` opens -the agent picker and inserts the selected agent at the front of the message: +Run the planning agent with the `#plan` macro. Typing `#` opens the agent +picker and inserts the selected behavior at the front of the prompt: ```text #plan Write a plan for building a suite of surrogate models on data.csv and performing assessment of predictive capability and uncertainty quantification. @@ -68,7 +74,7 @@ the agent picker and inserts the selected agent at the front of the message: The leading `#` is required; `plan ...` without it is ordinary chat input. -## 4. Route a message to the execution agent +## 5. Use the execution agent The execution agent can write files and run commands in the configured workspace. @@ -79,17 +85,7 @@ The execution agent can write files and run commands in the configured workspace Review the actions and outputs carefully. For more safety guidance, see [Sandboxing and information control][sandboxing-and-information-control]. -To direct the agent to a particular workspace file, type `@` and choose it -from the path picker: - -```text -#execute Read @data/measurements.csv and create a histogram of the pressure column. -``` - -The picker inserts the path into the message; the receiving agent decides how -to use it and must have an appropriate file tool. - -## 5. Optional: use a named agent +## 6. Optional: use a named agent A named agent stores state so you can return to it later: @@ -99,7 +95,7 @@ ursa --name my-first-agent For detailed commands to list, save, copy, share, import, and delete agents, see [Persistence](../persistence/index.md). -## Useful CLI commands +## Useful `ursa` CLI commands ```bash ursa @@ -109,9 +105,8 @@ ursa --name my-agent ursa --use-web ``` -Web tools are opt in. Use `--use-web` only when you want URSA to make network -requests through its web-search tools. Configuration files and their -`use_web` setting are covered in [Configuration](../configuration/index.md). +Web tools are opt in. Use `--use-web` or `use_web: true` only when you want URSA +to make network requests through its web-search tools. ## Where next? diff --git a/docs/hooks.py b/docs/hooks.py new file mode 100644 index 00000000..c9e865fd --- /dev/null +++ b/docs/hooks.py @@ -0,0 +1,194 @@ +"""MkDocs startup hooks and metadata-driven example pages.""" + +import os +import posixpath +import re +from pathlib import Path, PurePosixPath +from urllib.parse import quote, unquote, urlsplit + +import yaml +from jinja2 import Environment, FileSystemLoader, StrictUndefined +from mkdocs.structure.files import File, Files + +from ursa.util.http import inject_truststore_into_ssl + + +# Inventory downloads happen while MkDocs plugins process their configuration, +# before any URSA command-line entry point can initialize TLS. +inject_truststore_into_ssl() + + +REPOSITORY_ROOT = Path(__file__).resolve().parent.parent +EXAMPLES_ROOT = REPOSITORY_ROOT / "examples" +DOCS_ROOT = REPOSITORY_ROOT / "docs" +EXAMPLE_METADATA = "example.yaml" +TEMPLATES_ROOT = Path(__file__).resolve().parent / "templates" +TEMPLATES = Environment( + loader=FileSystemLoader(TEMPLATES_ROOT), + undefined=StrictUndefined, + autoescape=False, + keep_trailing_newline=True, +) +MARKDOWN_LINK = re.compile(r"(!?)\[([^]]+)]\(([^)]+)\)") + + +def _examples() -> list[tuple[Path, dict]]: + """Return validated example folders and metadata in display order.""" + found: list[tuple[Path, dict]] = [] + for metadata_path in EXAMPLES_ROOT.rglob(EXAMPLE_METADATA): + folder = metadata_path.parent + metadata = yaml.safe_load(metadata_path.read_text(encoding="utf-8")) + if not isinstance(metadata, dict): + raise ValueError(f"{metadata_path} must contain a YAML mapping") + for required in ("title", "summary", "tags"): + if not metadata.get(required): + raise ValueError(f"{metadata_path} is missing '{required}'") + if not isinstance(metadata["tags"], list) or not all( + isinstance(tag, str) and tag for tag in metadata["tags"] + ): + raise ValueError( + f"{metadata_path} 'tags' must be a list of strings" + ) + for required_file in ("README.md", "pyproject.toml"): + if not (folder / required_file).is_file(): + raise ValueError(f"{folder} is missing {required_file}") + found.append((folder, metadata)) + return sorted( + found, + key=lambda item: ( + "source-only" in item[1]["tags"], + str(item[1]["title"]).casefold(), + ), + ) + + +def _example_slug(folder: Path) -> str: + """Use the folder's repository-relative path as its stable URL slug.""" + return folder.relative_to(EXAMPLES_ROOT).as_posix() + + +def _github_ref() -> str: + """Map Mike's docs version to this repository's source branch or tag.""" + version = os.environ.get("MIKE_DOCS_VERSION", "main") + if version == "main": + return version + return version if version.startswith("v") else f"v{version}" + + +def _published_links(markdown: str, folder: Path) -> str: + """Link docs to rendered pages and other files to versioned GitHub.""" + + def replace(match: re.Match) -> str: + marker, label, destination = match.groups() + if destination.startswith("<") and ">" in destination: + end = destination.index(">") + target = destination[1:end] + suffix = destination[end + 1 :] + else: + target, separator, remainder = destination.partition(" ") + suffix = f"{separator}{remainder}" if separator else "" + + parsed = urlsplit(target) + if parsed.scheme or parsed.netloc or not parsed.path: + return match.group(0) + + source = (folder / unquote(parsed.path)).resolve() + if not source.is_file() or not source.is_relative_to(REPOSITORY_ROOT): + return match.group(0) + + if source.is_relative_to(DOCS_ROOT): + docs_path = source.relative_to(DOCS_ROOT).as_posix() + page_path = PurePosixPath("examples", _example_slug(folder)) + published_url = posixpath.relpath(docs_path, page_path.as_posix()) + if parsed.query: + published_url += f"?{parsed.query}" + if parsed.fragment: + published_url += f"#{parsed.fragment}" + return f"{marker}[{label}]({published_url}{suffix})" + + repository_path = source.relative_to(REPOSITORY_ROOT).as_posix() + if marker: + github_url = ( + f"https://raw.githubusercontent.com/lanl/ursa/" + f"{_github_ref()}/{quote(repository_path, safe='/')}" + ) + else: + github_url = ( + f"https://github.com/lanl/ursa/blob/{_github_ref()}/" + f"{quote(repository_path, safe='/')}" + ) + if parsed.query: + github_url += f"?{parsed.query}" + if parsed.fragment: + github_url += f"#{parsed.fragment}" + return f"{marker}[{label}]({github_url}{suffix})" + + return MARKDOWN_LINK.sub(replace, markdown) + + +def _example_page(folder: Path, metadata: dict) -> str: + """Render an example README with Material metadata and source link.""" + readme = (folder / "README.md").read_text(encoding="utf-8").rstrip() + heading, separator, body = readme.partition("\n") + if not separator or not heading.startswith("# "): + raise ValueError( + f"{folder / 'README.md'} must start with a level-one heading" + ) + return TEMPLATES.get_template("example-page.md.jinja").render( + title=heading.removeprefix("# "), + body=_published_links(body.lstrip(), folder), + tags=metadata["tags"], + github_url=( + f"https://github.com/lanl/ursa/tree/{_github_ref()}/examples/" + f"{_example_slug(folder)}" + ), + ) + + +def _examples_index(examples: list[tuple[Path, dict]]) -> str: + """Insert the template-rendered card catalog into the root README.""" + readme = (EXAMPLES_ROOT / "README.md").read_text( + encoding="utf-8" + ).rstrip() + marker = "" + if marker not in readme: + raise ValueError(f"{EXAMPLES_ROOT / 'README.md'} is missing {marker}") + + cards = [ + { + **metadata, + "url": f"{_example_slug(folder)}/index.md", + } + for folder, metadata in examples + ] + all_tags = sorted( + {tag for card in cards for tag in card["tags"]}, + key=str.casefold, + ) + catalog = TEMPLATES.get_template("example-catalog.md.jinja").render( + examples=cards, + all_tags=all_tags, + ).rstrip() + return readme.replace(marker, catalog) + "\n" + + +def on_files(files: Files, config) -> Files: + """Expose example READMEs as virtual documentation pages.""" + examples = _examples() + files.append( + File.generated( + config, + "examples/index.md", + content=_examples_index(examples), + ) + ) + for folder, metadata in examples: + slug = _example_slug(folder) + files.append( + File.generated( + config, + f"examples/{slug}/index.md", + content=_example_page(folder, metadata), + ) + ) + return files diff --git a/docs/index.md b/docs/index.md index b01dc3ae..cb9d502e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,58 +24,40 @@ Use URSA when you want to: We recommend installing with [`uv`](https://docs.astral.sh/uv/): ```bash -uv venv --python 3.12 .venv -source .venv/bin/activate -uv pip install ursa-ai +uv tool install 'ursa[dashboard]' ``` -If you prefer `pip`: - -```bash -python -m venv .venv -source .venv/bin/activate -python -m pip install --upgrade pip -python -m pip install ursa-ai -``` - -For the web dashboard, install the dashboard extra: - -```bash -uv pip install "ursa-ai[dashboard]" -# or -python -m pip install "ursa-ai[dashboard]" -``` - -See the [Installation](installation/index.md) section for platform-specific details. +See [Getting started][getting-started] for installation alternatives and a +walkthough of using URSA. ## Quick first run -Create a reusable configuration file, for example `config.yaml`: +For OpenAI, set the standard API-key environment variable and run URSA. The +built-in provider includes the model and base URL, so no config file is needed: -```yaml -llm_model: - model: openai:gpt-5.4 - api_key: - env: OPENAI_API_KEY -``` +=== "macOS/Linux" -Then run: + ```bash + export OPENAI_API_KEY="..." + ursa + ``` -```bash -ursa --config config.yaml -``` +=== "Windows PowerShell" + + ```powershell + $env:OPENAI_API_KEY = "..." + ursa + ``` Inside the URSA app, type `/` to browse commands or try: -```text -Summarize what URSA can help me do. -#execute Write and run a Python script that prints the first 10 prime numbers. -``` +- `Summarize what URSA can help me do.` +- `#execute Write and run a Python script that prints the first 10 prime numbers.` ## Where to go next -- [Install URSA](installation/index.md) -- [Get started with the CLI][getting-started-cli] +- [Follow the getting started guide][getting-started] +- [Try a worked example][examples] - [Get started with Python scripts][getting-started-python-scripts] - [Configure model endpoints](configuration/index.md) - [Use named agents and persistence](persistence/index.md) diff --git a/docs/install.md b/docs/install.md deleted file mode 100644 index c35db1e8..00000000 --- a/docs/install.md +++ /dev/null @@ -1,3 +0,0 @@ -# Installation - -The installation guide has moved to [Installation](installation/index.md). diff --git a/docs/installation/index.md b/docs/installation/index.md deleted file mode 100644 index 708ffcbd..00000000 --- a/docs/installation/index.md +++ /dev/null @@ -1,53 +0,0 @@ -# Installation - -URSA is published on PyPI as [`ursa-ai`](https://pypi.org/project/ursa-ai/). The installed command-line entry points are: - -```text -ursa -ursa-dashboard -``` - -We recommend installing URSA with [`uv`](https://docs.astral.sh/uv/) because it creates reproducible Python environments quickly and handles dependency resolution well. Clear `pip` instructions are also provided because many users already manage Python environments with `venv`, Conda, or system tooling. - -## Python version - -URSA requires Python 3.11 or newer. For most users, Python 3.12 or newer is a good default. - -## Choose an installation path - -- [Install with uv][install-with-uv] — recommended for most new URSA projects. -- [Install with pip][install-with-pip] — useful if you already use `venv`, - Conda, or another Python environment manager. - -## Optional extras - -URSA includes optional dependency groups for features that are not required by the core package. The most commonly used extra is the web dashboard: - -```bash -uv pip install "ursa-ai[dashboard]" -# or -python -m pip install "ursa-ai[dashboard]" -``` - -The dashboard extra installs the web-server dependencies needed by `ursa-dashboard`. - -Other optional extras exist for specialized workflows, such as LAMMPS, DSI, office-document readers, OpenTelemetry, Materials Project, image support, and optimization tooling. Install them only when needed. - -## Verify your installation - -After installing, run: - -```bash -ursa --help -``` - -If you installed the dashboard extra, also check: - -```bash -ursa-dashboard --help -``` - -## Next steps - -After installation, create a reusable model configuration file and run the -[CLI getting-started guide][getting-started-cli]. diff --git a/docs/installation/pip.md b/docs/installation/pip.md deleted file mode 100644 index 14f72daa..00000000 --- a/docs/installation/pip.md +++ /dev/null @@ -1,60 +0,0 @@ -# Install with pip - -Use `pip` if you already manage Python environments with `venv`, Conda, or another environment manager. - -## Create a clean virtual environment - -=== "macOS/Linux" - - ```bash - python3 -m venv .venv - source .venv/bin/activate - python -m pip install --upgrade pip - ``` - -=== "Windows PowerShell" - - ```powershell - py -3 -m venv .venv - .\.venv\Scripts\Activate.ps1 - python -m pip install --upgrade pip - ``` - -## Install URSA - -```bash -python -m pip install ursa-ai -``` - -## Install with dashboard support - -```bash -python -m pip install "ursa-ai[dashboard]" -``` - -Then verify: - -```bash -ursa --help -ursa-dashboard --help -``` - -## Conda environment with pip - -If you prefer Conda for Python environment management: - -```bash -conda create -y -n ursa-env python=3.12 -conda activate ursa-env -python -m pip install ursa-ai -``` - -With dashboard support: - -```bash -python -m pip install "ursa-ai[dashboard]" -``` - -## Next step - -Continue with [Getting Started - CLI][getting-started-cli]. diff --git a/docs/installation/uv.md b/docs/installation/uv.md deleted file mode 100644 index 7e57af10..00000000 --- a/docs/installation/uv.md +++ /dev/null @@ -1,84 +0,0 @@ -# Install with uv - -[`uv`](https://docs.astral.sh/uv/) is the recommended way to install URSA. It is fast, works well with isolated environments, and makes it easy to reproduce an installation. - -## Install uv - -If you do not already have `uv`, install it from Astral's official installer: - -=== "macOS/Linux" - - ```bash - curl -LsSf https://astral.sh/uv/install.sh | sh - ``` - -=== "Windows PowerShell" - - ```powershell - powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" - ``` - -See the [official uv installation guide](https://docs.astral.sh/uv/getting-started/installation/) for additional methods. - -## Create an environment and install URSA - -```bash -uv venv --python 3.12 .venv -source .venv/bin/activate -uv pip install ursa-ai -``` - -On Windows PowerShell, activate the environment with: - -```powershell -.\.venv\Scripts\Activate.ps1 -``` - -## Install with dashboard support - -```bash -uv pip install "ursa-ai[dashboard]" -``` - -Then verify: - -```bash -ursa --help -ursa-dashboard --help -``` - -## Project-style installation - -If you are creating a new project around URSA: - -```bash -uv init -p 3.12 my-ursa-project -cd my-ursa-project -uv add ursa-ai -``` - -With dashboard support: - -```bash -uv add "ursa-ai[dashboard]" -``` - -## Optional: install as a uv tool - -For command-line-only use, you can install URSA as a `uv` tool: - -```bash -uv tool install ursa-ai -``` - -If you need the dashboard command from the tool installation: - -```bash -uv tool install "ursa-ai[dashboard]" -``` - -For Python scripting, prefer a project or virtual environment installation so your scripts and URSA share the same environment. - -## Next step - -Continue with [Getting Started - CLI][getting-started-cli]. diff --git a/docs/javascripts/example-filter.js b/docs/javascripts/example-filter.js new file mode 100644 index 00000000..5728947b --- /dev/null +++ b/docs/javascripts/example-filter.js @@ -0,0 +1,47 @@ +function initializeExampleFilter() { + const filter = document.querySelector(".example-tag-filter"); + const catalog = document.querySelector(".example-catalog"); + if (!filter || !catalog || filter.dataset.initialized === "true") return; + + filter.dataset.initialized = "true"; + const buttons = [...filter.querySelectorAll("[data-example-tag]")]; + const cards = [...catalog.querySelectorAll(":scope > ul > li")]; + const status = document.querySelector(".example-filter-status"); + + function selectTag(tag) { + let visible = 0; + for (const card of cards) { + const tags = [...card.querySelectorAll(".example-tags .md-tag")] + .map((item) => item.textContent.trim()); + const filteredOut = Boolean(tag) && !tags.includes(tag); + card.classList.toggle("example-card--filtered", filteredOut); + if (!filteredOut) visible += 1; + } + for (const button of buttons) { + button.setAttribute( + "aria-pressed", + String(button.dataset.exampleTag === tag), + ); + } + if (status) { + status.textContent = tag + ? `${visible} example${visible === 1 ? "" : "s"} tagged “${tag}”` + : `${visible} examples`; + } + } + + for (const button of buttons) { + button.addEventListener("click", () => selectTag(button.dataset.exampleTag)); + } + selectTag(""); +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initializeExampleFilter); +} else { + initializeExampleFilter(); +} + +if (typeof document$ !== "undefined") { + document$.subscribe(initializeExampleFilter); +} diff --git a/docs/persistence/rag.md b/docs/persistence/rag.md index 40ab33bd..fa5824fb 100644 --- a/docs/persistence/rag.md +++ b/docs/persistence/rag.md @@ -1,6 +1,6 @@ # Persistent RAG collections -URSA supports persistent Retrieval-Augmented Generation (RAG) collections. A persistent RAG collection lets you ingest documents once, store the resulting vectorstore on disk, and query that collection later from the CLI or through another URSA agent as a tool. +URSA supports persistent Retrieval-Augmented Generation (RAG) collections. A persistent RAG collection lets you ingest documents once, store the resulting vectorstore on disk, and query that collection later from the TUI or through another URSA agent as a tool. Persistent RAG collections are separate from regular persisted URSA agents. They are stored under: @@ -239,7 +239,8 @@ If the regular agent group does not exist, URSA raises an error and asks you to ursa create-group ``` -See the CLI guide for more information about creating and managing groups. +See the TUI guide for interactive use and the CLI reference for commands that +create and manage groups. ## Typical workflow @@ -267,7 +268,7 @@ See the CLI guide for more information about creating and managing groups. ursa --name catalyst-assistant --group chemistry --rag-tools catalyst-papers ``` -5. Ask questions in the URSA CLI. If the agent calls the RAG tool, you will see output like: +5. Ask questions in the URSA TUI. If the agent calls the RAG tool, you will see output like: ```text [Request to catalyst-papers]: Summarize the evidence for improved stability. @@ -279,4 +280,4 @@ See the CLI guide for more information about creating and managing groups. - Re-running `rag-ingest` on the same source updates the persistent RAG collection by indexing documents not already present in the vectorstore. - RAG collection names use the same naming policy as persisted URSA agents. - RAG tools are available to URSA agents that support tools. -- The RAG CLI uses URSA's configured language model and embedding model settings. +- The TUI uses URSA's configured language model and embedding model settings. diff --git a/docs/persistence_updates/01_persistent_named_agents.md b/docs/persistence_updates/01_persistent_named_agents.md index c07927bb..928df5a1 100644 --- a/docs/persistence_updates/01_persistent_named_agents.md +++ b/docs/persistence_updates/01_persistent_named_agents.md @@ -111,7 +111,7 @@ ursa import-agent ./ursa_agent_default_lab-assistant_full_YYYYMMDD_HHMMSS.tar.gz ## Web dashboard -The dashboard uses the same persistent agent store as the CLI: +The dashboard and TUI use the same persistent agent store: ```text ~/.cache/ursa_agents// diff --git a/docs/persistence_updates/02_groups_and_security.md b/docs/persistence_updates/02_groups_and_security.md index 3c44c124..68d8bdf7 100644 --- a/docs/persistence_updates/02_groups_and_security.md +++ b/docs/persistence_updates/02_groups_and_security.md @@ -103,7 +103,7 @@ For non-default groups, URSA enforces that the model base URL matches the group' This applies in main runtime paths including: - base agent construction -- CLI/HITL model setup +- TUI model setup - RAG agent model and embedding setup - RAG ingest/query commands @@ -131,4 +131,4 @@ URSA copies that group config into the RAG group when creating the RAG group if - Use named groups for projects that must stay on approved model endpoints. - Keep group names simple and descriptive. - Update group configs when endpoint policy changes. -- Launch the dashboard with the same group you use from the CLI for a project. +- Launch the dashboard with the same group you use in the TUI for a project. diff --git a/docs/persistence_updates/06_web_dashboard_workflow.md b/docs/persistence_updates/06_web_dashboard_workflow.md index 8e4c314b..08e578bc 100644 --- a/docs/persistence_updates/06_web_dashboard_workflow.md +++ b/docs/persistence_updates/06_web_dashboard_workflow.md @@ -1,6 +1,7 @@ # Web Dashboard Workflow -The dashboard provides a session-based UI over URSA agents while reusing the same persistent named-agent store as the CLI. +The dashboard provides a session-based UI over URSA agents while reusing the +same persistent named-agent store as the TUI. ## Launch diff --git a/docs/rag.md b/docs/rag.md index d5952944..a0ae0ba8 100644 --- a/docs/rag.md +++ b/docs/rag.md @@ -1,6 +1,6 @@ # Persistent RAG Agents -URSA supports persistent Retrieval-Augmented Generation (RAG) collections. A persistent RAG collection lets you ingest documents once, store the resulting vectorstore on disk, and query that collection later from the CLI or through another URSA agent as a tool. +URSA supports persistent Retrieval-Augmented Generation (RAG) collections. A persistent RAG collection lets you ingest documents once, store the resulting vectorstore on disk, and query that collection later from the TUI or through another URSA agent as a tool. Persistent RAG collections are separate from regular persisted URSA agents. They are stored under: @@ -239,7 +239,8 @@ If the regular agent group does not exist, URSA raises an error and asks you to ursa create-group ``` -See the CLI guide for more information about creating and managing groups. +See the TUI guide for interactive use and the CLI reference for commands that +create and manage groups. ## Typical workflow @@ -267,7 +268,7 @@ See the CLI guide for more information about creating and managing groups. ursa --name catalyst-assistant --group chemistry --rag-tools catalyst-papers ``` -5. Ask questions in the URSA CLI. If the agent calls the RAG tool, you will see output like: +5. Ask questions in the URSA TUI. If the agent calls the RAG tool, you will see output like: ```text [Request to catalyst-papers]: Summarize the evidence for improved stability. @@ -279,4 +280,4 @@ See the CLI guide for more information about creating and managing groups. - Re-running `rag-ingest` on the same source updates the persistent RAG collection by indexing documents not already present in the vectorstore. - RAG collection names use the same naming policy as persisted URSA agents. - RAG tools are available to URSA agents that support tools. -- The RAG CLI uses URSA's configured language model and embedding model settings. +- The TUI uses URSA's configured language model and embedding model settings. diff --git a/docs/reference/logging_events.md b/docs/reference/logging_events.md index 0f3c9f23..f56f11bb 100644 --- a/docs/reference/logging_events.md +++ b/docs/reference/logging_events.md @@ -11,7 +11,7 @@ of using `print(...)`, `console.print(...)`, or Rich directly in agents and tools. See the [Python scripts guide][getting-started-python-scripts] for running -agents, the [CLI guide][getting-started-cli] for URSA's interactive interface, +agents, the [TUI guide][getting-started-tui] for URSA's interactive interface, and the [dashboard guide][getting-started-web-dashboard] for the web interface. ## Event model @@ -211,7 +211,7 @@ Use the same configuration with `await agent.ainvoke(...)`. Pass callbacks at the top-level invocation when possible so nested agents and tools share one event stream. -The CLI uses +The TUI uses [`HITLLogEventHandler`][ursa.cli.callbacks.HITLLogEventHandler]. The default [`EventLoggingHandler`][ursa.util.events.EventLoggingHandler] diff --git a/docs/reference/metrics.md b/docs/reference/metrics.md index 48dcc341..572eccf3 100644 --- a/docs/reference/metrics.md +++ b/docs/reference/metrics.md @@ -235,7 +235,7 @@ python ursa/scripts/metrics_cli.py ./workspaces/t1/run_0007.json --chart tokens- ## Output naming & where to find things **Per run (single JSON):** -``` +```text /run_breakdown_lollipop.png /run_breakdown_tokens_bar.png /run_breakdown_tokens_kde.png @@ -243,7 +243,7 @@ python ursa/scripts/metrics_cli.py ./workspaces/t1/run_0007.json --chart tokens- ``` **Thread-level (in --dir):** -``` +```text thread__lollipop.png thread__tokens_bar.png thread__tokens_kde.png @@ -254,7 +254,7 @@ thread__agents_tps.png ``` **SUPER (at the root --dir for all-recursive):** -``` +```text super_lollipop.png super_tokens_bar.png super_tokens_kde.png diff --git a/docs/reference/plan-execute-checkpointing.md b/docs/reference/plan-execute-checkpointing.md index 27b0a6f8..9dc55681 100644 --- a/docs/reference/plan-execute-checkpointing.md +++ b/docs/reference/plan-execute-checkpointing.md @@ -66,7 +66,7 @@ All interactive prompts include a **countdown** and then pick a safe default. Us ## Files you’ll see in a workspace -``` +```text / ├── executor_checkpoint.db # live executor DB (current run) ├── executor_checkpoint_1.db # snapshot after step 1 (single mode) @@ -102,7 +102,7 @@ You can resume from **any** of these snapshot files. - Without `--resume-from`, you’ll get an interactive chooser (with countdown). Default is `executor_checkpoint.db` (live). **Example tree (6 steps planned):** -``` +```text workspace/ ├── executor_checkpoint.db ├── executor_checkpoint_1.db @@ -192,7 +192,7 @@ python examples/two_agent_examples/plan_execute/plan_execute_from_yaml.py \ ``` You’ll see messages like: -``` +```text [checkpoint] saved step snapshot: executor_checkpoint_1.db [checkpoint] saved step snapshot: executor_checkpoint_2.db ... diff --git a/docs/stylesheets/example-filter.css b/docs/stylesheets/example-filter.css new file mode 100644 index 00000000..98d08e6a --- /dev/null +++ b/docs/stylesheets/example-filter.css @@ -0,0 +1,33 @@ +.example-tag-filter { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin: 0 0 1rem; +} + +.example-tag-filter__button { + border: 0; + cursor: pointer; + font: inherit; +} + +.example-tag-filter__button[aria-pressed="true"] { + background-color: var(--md-accent-fg-color); + color: var(--md-accent-bg-color); +} + +.example-filter-status { + margin: -0.5rem 0 1rem; + min-height: 1.4em; +} + +.example-tags .md-tag { + line-height: 1.5; + margin-right: 0.25em; + padding-inline: 0.45em; + padding-block: 0; +} + +.example-catalog .example-card--filtered { + display: none !important; +} diff --git a/docs/templates/example-catalog.md.jinja b/docs/templates/example-catalog.md.jinja new file mode 100644 index 00000000..cf438462 --- /dev/null +++ b/docs/templates/example-catalog.md.jinja @@ -0,0 +1,22 @@ +
+ +{% for tag in all_tags %} + +{% endfor %} +
+ +

+ +
+ +{% for example in examples %} +- **[{{ example.title }}]({{ example.url }})** + + {{ example.summary }} + + + {% for tag in example.tags %}{{ tag }}{% endfor %} + + +{% endfor %} +
diff --git a/docs/templates/example-page.md.jinja b/docs/templates/example-page.md.jinja new file mode 100644 index 00000000..9c07909f --- /dev/null +++ b/docs/templates/example-page.md.jinja @@ -0,0 +1,10 @@ +--- +tags: +{% for tag in tags %} + - {{ tag }} +{% endfor %} +--- + +# [:fontawesome-brands-github:]({{ github_url }} "View this example on GitHub"){ .example-github-link aria-label="View this example on GitHub" } {{ title }} + +{{ body }} diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 56e17871..00000000 --- a/docs/usage.md +++ /dev/null @@ -1,3 +0,0 @@ -# Usage - -New-user usage guides now live under [Getting Started](getting-started/cli.md). diff --git a/docs/wdi.md b/docs/wdi.md deleted file mode 100644 index 2655f031..00000000 --- a/docs/wdi.md +++ /dev/null @@ -1,3 +0,0 @@ -# Web Dashboard - -The dashboard getting-started guide has moved to [Getting Started - Web Dashboard](getting-started/dashboard.md). diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..94ca4365 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,57 @@ +# Examples + +Choose an example based on what you want URSA to do. Each published example is +a self-contained folder with setup instructions, dependencies, inputs, and +expected results. Start with an example tagged `beginner` if this is your first +time using an execution agent, the dashboard, or MCP. + + + +## Adding an example + +Use one example per folder with this minimum structure: + +```text +examples/category/my_example/ +├── README.md # purpose, setup, run, expected results, and cleanup +├── example.yaml # documentation metadata +├── pyproject.toml # isolated Python dependencies, even when empty +└── ... # scripts, inputs, images, and other example files +``` + +The metadata requires a title, summary, and tags. Examples are ordered +alphabetically by title: + +```yaml +title: My example +summary: One sentence explaining what the reader will learn. +tags: + - execution-agent + - beginner +``` + +The documentation build discovers every `example.yaml`. It publishes the +folder's `README.md` at a matching `/examples/.../` URL and adds it to the +catalog. Keep relative README links local to the example folder; the published +page rewrites links to adjacent files so they open the version-matched source +on GitHub. + +### Common tags + +Use lowercase, hyphenated tags. Prefer these common tags so related examples +remain easy to find: + +| Tag | Use for | +| --- | --- | +| `guided` | A narrative walkthrough with ordered setup, execution, and review steps | +| `source-only` | A focused source example intended primarily to be configured and run | +| `beginner` | A good first example with minimal prerequisites | +| `tui` | Workflows driven through URSA's terminal user interface | +| `dashboard` | Workflows using the browser dashboard | +| `python-api` | Direct use of URSA classes from Python | +| `execution-agent` | Tasks that run commands or create workspace artifacts | +| `mcp` | MCP servers, clients, or tools attached to URSA agents | +| `multi-agent` | Teams, symposia, or other composed-agent workflows | +| `simulation` | Running or analyzing a scientific simulation | +| `plotting` | Producing plots or other visual artifacts | +| `optimization` | Search, experiment selection, or mathematical optimization | diff --git a/examples/environments/README.md b/examples/environments/README.md new file mode 100644 index 00000000..fccf7de2 --- /dev/null +++ b/examples/environments/README.md @@ -0,0 +1,108 @@ +# Build an agent team + +Give one URSA agent responsibility for a result, then let it delegate focused +work to specialists. In this example, a principal investigator coordinates a +research specialist and a data analyst. They share a workspace and return one +synthesized answer. + +Start with the team before trying the larger symposium: + +1. Inspect [`agent_team.yaml`](agent_team.yaml) and notice the PI, the two members, and the tools + each member may use. +2. Run the team from Python and watch the PI divide the task. +3. Change the roles or prompt, then run it again to see how the delegation + changes. +4. When you are ready to compare independent solutions, open + [`agent_symposium.yaml`](agent_symposium.yaml). It places a nested team and an independent solver in + a review-and-synthesis workflow. + +Read [Agent teams](../../docs/environments/agent-teams.md) and +[Agent symposia](../../docs/environments/agent-symposia.md) for the concepts and full +configuration reference. + +## Run the team + +Clone URSA, open a terminal at the repository root, and set your OpenAI API key. +Then install this example's dependencies and run its small Python entry point. + +=== "macOS/Linux" + + ```bash + cd examples/environments + export OPENAI_API_KEY="your-api-key" + uv sync + uv run python run_team.py + ``` + +=== "Windows PowerShell" + + ```powershell + Set-Location examples\environments + $env:OPENAI_API_KEY = "your-api-key" + uv sync + uv run python run_team.py + ``` + +The runner loads [`agent_team.yaml`](agent_team.yaml), initializes the configured chat model, and +asks the team to compare two approaches to a data-analysis task. Edit the task +inside [`run_team.py`](run_team.py) to give the team a problem of your own. + +```python +--8<-- "examples/environments/run_team.py" +``` + +If you use another model provider, configure its credentials and update the +model in `run_team.py`. See [Models and inference +providers](../../docs/configuration/models.md) for supported configurations. + +## Shape the team + +Edit the roles and prompts in [`agent_team.yaml`](agent_team.yaml). Keep each role specific: the PI +should coordinate and synthesize, while each member should own a distinct kind +of work. The included team configuration is short enough to use as a starting +point: + +```yaml +--8<-- "examples/environments/agent_team.yaml" +``` + +The PI and members can use web or execution tools according to their `config` +blocks. Review those permissions before you launch the team, especially when +you point it at a non-temporary workspace. The [environment +documentation](../../docs/environments/index.md) explains workspaces, persistence, member +models, and execution behavior in more detail. + +## Run it from the dashboard + +Install the dashboard-enabled URSA tool, then launch it: + +=== "macOS/Linux" + + ```bash + uv tool install 'ursa[dashboard]' + ursa-dashboard + ``` + +=== "Windows PowerShell" + + ```powershell + uv tool install "ursa[dashboard]" + ursa-dashboard + ``` + +Open the displayed local URL, configure your model under **Settings**, and open +**Environment runs**. Create a team, replace the starter definition with the +contents of [`agent_team.yaml`](agent_team.yaml), enter a task, validate the YAML, and launch the +run. The dashboard shows the environment graph and live work timeline. + +Follow the [dashboard getting-started guide](../../docs/getting-started/dashboard.md) +for credential storage, workspace selection, run history, and cancellation. + +## Try the symposium + +After the team works, use [`agent_symposium.yaml`](agent_symposium.yaml) as the next exercise. The +symposium sends the same problem to a nested team and an independent solver, +asks them to review and revise their work, and has an organizer synthesize the +result. You can launch that YAML from **Environment runs** in the dashboard, or +load it with `AgentSymposiumEnvironment.from_yaml()` as shown in the [Python +scripts guide](../../docs/getting-started/python-scripts.md#compose-agents-with-environments). diff --git a/examples/environments/example.yaml b/examples/environments/example.yaml new file mode 100644 index 00000000..8af7e77e --- /dev/null +++ b/examples/environments/example.yaml @@ -0,0 +1,6 @@ +title: Agent teams and symposiums +summary: Launch reusable team and symposium environments from YAML definitions. +tags: + - guided + - multi-agent + - dashboard diff --git a/examples/environments/pyproject.toml b/examples/environments/pyproject.toml new file mode 100644 index 00000000..2e364b7e --- /dev/null +++ b/examples/environments/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "ursa-example-environments" +version = "0.1.0" +description = "Launch reusable team and symposium environments from YAML definitions." +requires-python = ">=3.11" +dependencies = [ + "ursa-ai", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../..", editable = true } diff --git a/examples/environments/run_team.py b/examples/environments/run_team.py new file mode 100644 index 00000000..f8f2dc5c --- /dev/null +++ b/examples/environments/run_team.py @@ -0,0 +1,11 @@ +from langchain.chat_models import init_chat_model + +from ursa.environments import AgentTeamEnvironment + + +llm = init_chat_model("openai:gpt-5.4-mini") +team = AgentTeamEnvironment.from_yaml("agent_team.yaml", llm=llm) +result = team.invoke( + "Compare two defensible approaches to a small data-analysis task." +) +print(result) diff --git a/examples/mcp_agent_tools/README.md b/examples/mcp_agent_tools/README.md new file mode 100644 index 00000000..a488b51f --- /dev/null +++ b/examples/mcp_agent_tools/README.md @@ -0,0 +1,95 @@ +# Attach MCP tools to a Python agent + +Start a local MCP server, let URSA discover its tools, and attach those tools to +a `ChatAgent`. This example keeps the server deliberately small so you can see +the complete connection before adapting it to a real service. + +The workflow has two processes: + +1. `laboratory_server.py` serves one `list_measurements` tool over local + Streamable HTTP. +2. `attach_mcp_tools.py` reads `config.yaml`, initializes the configured model, + attaches the discovered MCP tool, and asks the agent to use it. + +Read the [MCP configuration guide](../../docs/configuration/mcp.md) for other +transports and authentication settings. The [Python scripts guide](../../docs/getting-started/python-scripts.md) +explains model initialization and direct agent use. + +## Prepare the example + +Open a terminal in this folder, install the locked environment, and set your +OpenAI key. + +=== "macOS/Linux" + + ```bash + cd examples/mcp_agent_tools + uv sync + export OPENAI_API_KEY="your-api-key" + ``` + +=== "Windows PowerShell" + + ```powershell + Set-Location examples\mcp_agent_tools + uv sync + $env:OPENAI_API_KEY = "your-api-key" + ``` + +The example uses URSA's default OpenAI model. Follow [models and inference +providers](../../docs/configuration/models.md) before running it with another +provider. + +## Inspect the server configuration + +The client reads this MCP server definition: + +```yaml +--8<-- "examples/mcp_agent_tools/config.yaml" +``` + +The endpoint is local and does not include authentication. Keep it bound to +your machine for this exercise. + +## Start the MCP server + +In the first terminal, run: + +```bash +uv run laboratory_server.py +``` + +Leave that process running. It serves the MCP endpoint at +`http://127.0.0.1:8000/mcp`. + +## Attach and use the tool + +Open a second terminal in the same folder, set `OPENAI_API_KEY` there as shown +above, and run: + +```bash +uv run attach_mcp_tools.py +``` + +The script prints the tool-to-server mapping returned by `add_mcp_tools()`, then +prints the agent's summary. Confirm that `list_measurements` is attached from +the `laboratory` server and that the answer identifies `alloy-b` as the largest +reported strength while noting that `alloy-c` was measured at another +temperature. + +The client implementation is short enough to inspect in full: + +```python +--8<-- "examples/mcp_agent_tools/attach_mcp_tools.py" +``` + +`add_mcp_tools()` accepts `tool_name="list_measurements"` or a list of names +when an agent should receive only selected server tools. The server must already +be running when discovery begins. + +## Adapt the example + +Add another `@mcp.tool()` function to `laboratory_server.py`, restart the server, +and run the client again. Update the prompt so the agent has a clear reason to +choose the new tool. Review the [MCP reference](../../docs/reference/mcp.md) when +you add production transports, credentials, or remote endpoints. diff --git a/examples/mcp_agent_tools/attach_mcp_tools.py b/examples/mcp_agent_tools/attach_mcp_tools.py new file mode 100644 index 00000000..c33e0250 --- /dev/null +++ b/examples/mcp_agent_tools/attach_mcp_tools.py @@ -0,0 +1,38 @@ +import asyncio +from pathlib import Path + +from langchain_core.messages import HumanMessage + +from ursa.agents import ChatAgent +from ursa.cli.config import UrsaConfig, resolve_ursa_config +from ursa.util.mcp import start_mcp_client + + +async def main() -> None: + config = resolve_ursa_config(UrsaConfig.from_file(Path("config.yaml"))) + agent = ChatAgent( + llm=config.llm_model.init_chat_model(), + workspace=Path("ursa-script-workspace"), + ) + + mcp_client = start_mcp_client(config.mcp_servers) + tool_sources = await agent.add_mcp_tools(mcp_client) + print("Attached MCP tools:", tool_sources) + + result = await agent.ainvoke({ + "messages": [ + HumanMessage( + content=( + "Use the laboratory tools to list the available measurements, " + "then summarize the strongest sample and any temperature " + "difference that limits a direct comparison." + ) + ) + ], + "thread_id": agent.thread_id, + }) + print(result["messages"][-1].content) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/mcp_agent_tools/config.yaml b/examples/mcp_agent_tools/config.yaml new file mode 100644 index 00000000..6cb8b269 --- /dev/null +++ b/examples/mcp_agent_tools/config.yaml @@ -0,0 +1,4 @@ +mcp_servers: + laboratory: + transport: streamable-http + url: http://127.0.0.1:8000/mcp diff --git a/examples/mcp_agent_tools/example.yaml b/examples/mcp_agent_tools/example.yaml new file mode 100644 index 00000000..dbc011e3 --- /dev/null +++ b/examples/mcp_agent_tools/example.yaml @@ -0,0 +1,7 @@ +title: Attach MCP tools to a Python agent +summary: Start a local MCP server, discover its tools, and attach them to an URSA ChatAgent. +tags: + - guided + - mcp + - python-api + - beginner diff --git a/examples/mcp_agent_tools/laboratory_server.py b/examples/mcp_agent_tools/laboratory_server.py new file mode 100644 index 00000000..0e9c353f --- /dev/null +++ b/examples/mcp_agent_tools/laboratory_server.py @@ -0,0 +1,22 @@ +from mcp.server.fastmcp import FastMCP + + +mcp = FastMCP("Laboratory measurements", json_response=True) + + +@mcp.tool() +def list_measurements() -> list[dict[str, str | float]]: + """Return a small set of example laboratory measurements.""" + return [ + {"sample": "alloy-a", "temperature_k": 298.0, "strength_mpa": 512.0}, + {"sample": "alloy-b", "temperature_k": 298.0, "strength_mpa": 547.0}, + {"sample": "alloy-c", "temperature_k": 350.0, "strength_mpa": 489.0}, + ] + + +def main() -> None: + mcp.run(transport="streamable-http") + + +if __name__ == "__main__": + main() diff --git a/examples/mcp_agent_tools/pyproject.toml b/examples/mcp_agent_tools/pyproject.toml new file mode 100644 index 00000000..f1598617 --- /dev/null +++ b/examples/mcp_agent_tools/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "ursa-example-mcp-agent-tools" +version = "0.1.0" +description = "Attach tools from a local MCP server to an URSA Python agent" +requires-python = ">=3.11" +dependencies = [ + "mcp>=1.20,<2", + "ursa-ai", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../..", editable = true } diff --git a/examples/mcp_examples/README.md b/examples/mcp_examples/README.md deleted file mode 100644 index ce0cb9c6..00000000 --- a/examples/mcp_examples/README.md +++ /dev/null @@ -1,162 +0,0 @@ -# SQLite MCP Example - -This directory contains a small SQLite-backed MCP server and a simple client harness. - -The goal is to provide a readable example that shows how an MCP service can expose useful tools, and how those tools can later be wired into URSA for agentic use. - -## Files - -- `sqlite_mcp.py` - MCP server that exposes a handful of SQLite tools. - -- `test_sqlite_mcp.py` - Small hard-coded client script that connects to a running MCP server, calls its tools, and prints the results. - -- `sqlite_data/` - Created automatically when needed. Stores the example `.db` files. - -## What this example demonstrates - -This example shows how to expose a few database operations through MCP: - -- create a database -- create a table -- inspect tables and schema -- insert rows -- run a read-only query - -It is meant to be simple and readable, not a production database service. - -## Running the example - -Run this from **this directory** using two terminals. - -### Terminal 1: start the MCP server - -```bash -python sqlite_mcp.py -``` - -This starts the SQLite MCP server over Streamable HTTP on: `http://127.0.0.1:8000/mcp`. - -Leave this running. - -### Terminal 2: run the client harness -```bash -python test_sqlite_mcp.py -``` - -This connects to the running MCP server, calls several of the exposed tools, and prints the results. - -## Running the MCP server directly - -You can run the server directly with: - -```bash -python sqlite_mcp.py -``` -This starts a local Streamable HTTP MCP server on port 8000. - -This is useful when another client, such as URSA, will connect to the server. - -## Relation to URSA - -This example is a first step toward using MCP tools inside URSA. - -The intended progression is: - -1. verify that the MCP server works on its own -2. verify that a client can discover and call the tools -3. point URSA at this MCP server so an execution agent can use the same tools dynamically - -In other words, this directory gives you a minimal local MCP example first, before adding URSA-driven agent behavior on top. - -## Notes -* database files are created in sqlite_data/ -* database names are normalized to use the .db suffix -* the query tool is intentionally read-only -* this example is designed for local experimentation and learning - -## Expected result - -A successful run of test_sqlite_mcp.py should: - -1. list the available MCP tools -2. create a demo database -3. create a table -4. insert a few rows -5. query those rows back out -6. print the returned results - -## Connecting This Demo Into URSA -We're going to use this prompt (or change it as you like) with the ExecutionAgent: -```text -Use the sqlite_demo MCP tools to create a database called materials_demo -and a table called tensile_experiments with the following columns: -sample_id as a TEXT primary key, temperature_K as REAL, strain_rate_s as REAL, -grain_size_um as REAL, yield_strength_MPa as REAL, and phase_label as TEXT. - -Then generate 100 synthetic rows of data using numpy with reasonable random -distributions: temperature_K uniformly between 250 and 1200, strain_rate_s -log-uniformly between 1e-4 and 1e1, grain_size_um normally distributed around -20 with a standard deviation of 5 and clipped to positive values, and -yield_strength_MPa computed from a simple synthetic relationship where strength -decreases with temperature, increases with strain rate, and increases slightly -as grain size decreases, plus some random noise. - -Assign each row a sample_id from sample_001 to sample_100 and a phase_label -of alpha or beta based on whether temperature_K is below or above 700. - -Insert all rows into the table, query the full table back out, and then plot -yield_strength_MPa versus temperature_K with points colored by phase_label. -Save this to an appropriate PNG filename. - -Also print a short summary of the table contents and the fitted synthetic -trends you used. -``` - -Let's do this using the URSA dashboard! - -1. start the URSA dashboard with `ursa-dashboard` in one terminal. Connect to it with a web browser at - the address shown. You should see something like below in the 1st terminal - -![ursa-dashboard](./images/ursa-dashboard.png) - -2. start or make sure you still have running the `sqlite_mcp` server in another terminal, `python sqlite_mcp.py`. You should see something like below in the 2nd terminal. - -![sqlite_mcp](./images/sqlite_mcp.png) - -3. in the dashboard, go to Settings --> MCP Tools. Fill in `sqlite_demo` for the `Server name` and enter the JSON text below for the server config. Then `Save` and `Close` the Settings. -``` -{ - "transport": "streamable_http", - "url": "http://127.0.0.1:8000/mcp" -} -``` - -4. Next, click `New Session` under `Execution Agent` and copy/paste the above prompt (the block of text - - or modify as you like) into the chat window and hit `Send`. - -You should see the `sqlite_mcp.py` server (terminal 2) start scrolling as URSA's ExecutionAgent -makes calls to it. URSA should write code to solve the prompt above. If all goes well (and you're -using a competent enough LLM) you'll end up with a plot that might look something like below. You -can find this in the right-most panel, `Artifacts`, as a PNG. You may have to hit `Refresh` to -see it. - -![artifact-plot](./images/artifact-plot.png) - -You should see something like this in the STDOUT window when it does the summary you asked -for in the prompt: -```text -TABLE SUMMARY -n_rows: 100 -temperature_min: 256.99415626345524 -temperature_max: 1176.8412340549182 -strain_rate_min: 0.00012825089348783022 -strain_rate_max: 9.159627667952444 -grain_size_mean: 19.767563876348888 -grain_size_std: 5.056608889338605 -yield_strength_mean: 610.11508971663 -yield_strength_std: 97.713786427034 -phase_counts: {'alpha': 52, 'beta': 48} -``` \ No newline at end of file diff --git a/examples/nomad_mist/README.md b/examples/nomad_mist/README.md new file mode 100644 index 00000000..8541c41b --- /dev/null +++ b/examples/nomad_mist/README.md @@ -0,0 +1,177 @@ +# Connect URSA to MIST through Nomad + +Use this walkthrough to ask URSA for a molecular-property prediction from a +MIST scientific foundation model. [Nomad](https://github.com/lanl/nomad) serves +the model as an MCP tool; URSA discovers the tool, finds caffeine in PubChem, +and writes a short report from the model output. + +MIST is a family of molecular foundation models for property prediction. Browse +the [MIST models on Hugging Face](https://huggingface.co/mist-models) before you +begin if you want to see the available weights and model cards. + +## What you will do + +1. Install this example's URSA environment. +2. Start Nomad's pre-built demo container. +3. Add the local Nomad MCP endpoint to your URSA user configuration. +4. Run a guided prompt and inspect `mist_caffeine_report.txt`. + +The first container launch downloads model weights. The models used here are +small enough to run without a GPU, although a GPU makes inference faster. + +## Prerequisites + +- [uv](https://docs.astral.sh/uv/) +- Docker Desktop or Docker Engine +- An API key for the language model that will direct URSA's tool calls +- Optional: an NVIDIA GPU available to Docker + +Run every command from this `examples/nomad_mist` directory. + +## 1. Install the example environment + +```bash +uv sync +uv run ursa --help +``` + +This example uses the URSA checkout two directories above it. `uv run` ensures +that the command comes from the example's environment. + +If this is your first URSA session, follow the +[configuration overview](../../docs/configuration/index.md) to configure your +LLM credentials. Standard OpenAI access only requires `OPENAI_API_KEY`. + +## 2. Pull the Nomad image + +=== "macOS/Linux" + + ```bash + docker pull ghcr.io/lanl/nomad:latest + mkdir -p cache + ``` + +=== "Windows PowerShell" + + ```powershell + docker pull ghcr.io/lanl/nomad:latest + New-Item -ItemType Directory -Force cache | Out-Null + ``` + +The `cache` directory retains downloaded model weights between container runs. + +## 3. Start Nomad + +Choose the command for your platform and leave this terminal running. + +=== "macOS/Linux" + + ```bash + # CPU-only: delete the "--gpus all" line. + docker run --rm \ + --gpus all \ + --publish 38217:38217 \ + --volume "$PWD/cache:/var/cache/nomad" \ + ghcr.io/lanl/nomad:latest \ + serve \ + --transport=streamable-http \ + --host=0.0.0.0 \ + --port=38217 \ + /nomad/container/demo/nomad-smoke.yml + ``` + +=== "Windows PowerShell" + + ```powershell + # CPU-only: delete the "--gpus all" line. + docker run --rm ` + --gpus all ` + --publish 38217:38217 ` + --volume "${PWD}/cache:/var/cache/nomad" ` + ghcr.io/lanl/nomad:latest ` + serve ` + --transport=streamable-http ` + --host=0.0.0.0 ` + --port=38217 ` + /nomad/container/demo/nomad-smoke.yml + ``` + +Wait until Nomad reports that the server is listening on port `38217`. + +## 4. Connect URSA to Nomad + +Open your persistent URSA user configuration and merge in this block without +replacing your model settings: + +```yaml +mcp_servers: + nomad: + transport: streamable-http + url: http://localhost:38217/mcp +``` + +The [configuration-file guide](../../docs/configuration/files-and-env.md) lists +the user-config location for macOS, Linux, and Windows. The +[MCP configuration guide](../../docs/configuration/mcp.md) explains transports, +headers, timeouts, and environment expansion. + +In a second terminal, confirm that URSA loaded the user setting: + +```bash +uv run ursa --print-config=user,resolved +``` + +Look for `mcp_servers.nomad` in the output, then start the TUI: + +```bash +uv run ursa +``` + +## 5. Run the MIST workflow + +Paste this prompt into the TUI: + +```text +#execute Use the connected Nomad tools to find caffeine in PubChem, inspect the +model card for mist_models---mist_26p9M_kkgx0omx_qm9, and run that MIST model +on caffeine's canonical SMILES string. Save mist_caffeine_report.txt with the +SMILES input, every predicted property, units and descriptions when available, +and a brief explanation of what this demonstrates about connecting scientific +foundation models to URSA. +``` + +URSA should first resolve caffeine to a canonical SMILES string, inspect the +served model card, call the MIST model, and create +`mist_caffeine_report.txt` in the selected workspace. Review the report and the +tool activity rather than treating the generated prediction as experimentally +validated data. + +The requested model has a +[MIST QM9 model card](https://huggingface.co/mist-models/mist-26.9M-kkgx0omx-qm9). + +## Use the dashboard instead + +The same user-level MCP configuration is available to the browser interface. +Follow the [dashboard guide](../../docs/getting-started/dashboard.md) for the +dashboard installation, credential settings, workspace selection, and launch +command. Choose the execution agent and submit the same prompt, then inspect the +activity timeline and generated report in the artifacts panel. + +## Troubleshooting + +- Ask `#execute List the connected Nomad tools` to confirm MCP discovery before + running the full prompt. +- If no Nomad tools appear, recheck the resolved configuration and confirm that + the container is still listening on port `38217`. +- If Docker rejects `--gpus all`, remove that line and run on the CPU. +- If the first model call is slow, watch the Nomad terminal; it may still be + downloading weights into `cache`. + +Stop Nomad with **Ctrl+C**. Remove the `nomad` block from your user config when +you no longer want URSA to connect to the local server. + +The endpoint uses unauthenticated local HTTP for this exercise. Before exposing +Nomad on a network, apply appropriate authentication and transport security. +See Nomad's [getting-started guide](https://lanl.github.io/nomad/guides/getting-started.html) +and [deployment guide](https://lanl.github.io/nomad/deployments/guide.html) for +production considerations. diff --git a/examples/nomad_mist/example.yaml b/examples/nomad_mist/example.yaml new file mode 100644 index 00000000..fc57c6e4 --- /dev/null +++ b/examples/nomad_mist/example.yaml @@ -0,0 +1,5 @@ +title: MIST through Nomad +summary: Connect URSA to a molecular-property model served as an MCP tool by Nomad. +tags: + - mcp + - guided diff --git a/examples/nomad_mist/pyproject.toml b/examples/nomad_mist/pyproject.toml new file mode 100644 index 00000000..47425853 --- /dev/null +++ b/examples/nomad_mist/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "ursa-example-nomad-mist" +version = "0.1.0" +description = "URSA, Nomad, and MIST MCP integration example" +requires-python = ">=3.11" +dependencies = [ + "ursa-ai", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../..", editable = true } diff --git a/examples/single_agent_examples/acquisition_examples/README.md b/examples/single_agent_examples/acquisition_examples/README.md new file mode 100644 index 00000000..c6d43729 --- /dev/null +++ b/examples/single_agent_examples/acquisition_examples/README.md @@ -0,0 +1,166 @@ +# Compare research sources with acquisition agents + +Use this walkthrough to investigate one scientific question through three +different source collections. URSA's acquisition agents search for material, +cache the retrieved pages or papers, summarize each item, and synthesize a final +answer: + +- `ArxivAgent` searches arXiv and downloads paper PDFs. +- `OSTIAgent` searches U.S. Department of Energy OSTI records. +- `WebSearchAgent` searches the open web with DDGS. + +You will try the workflow from the TUI, the dashboard, and Python. Each entry +point uses the same acquisition machinery, but exposes it differently. + +## What you will produce + +The guided question asks how graph neural networks solve partial differential +equations, with an emphasis on shock hydrodynamics. Retrieved documents and +summaries are written beneath this directory when you run the Python example. +Treat the summaries as research leads: follow their source links and verify +important claims in the original documents. + +## Prerequisites + +- [uv](https://docs.astral.sh/uv/) +- Internet access to arXiv, OSTI, and web-search results +- An OpenAI API key, or an equivalent provider in your + [URSA configuration](../../../docs/configuration/index.md) + +Run the following commands from this `acquisition_examples` directory. + +## 1. Install the example environment + +=== "macOS/Linux" + + ```bash + uv sync + export OPENAI_API_KEY="..." + uv run ursa --help + ``` + +=== "Windows PowerShell" + + ```powershell + uv sync + $env:OPENAI_API_KEY = "..." + uv run ursa --help + ``` + +The example uses the editable URSA checkout three directories above it and +includes dashboard support. If you use a non-OpenAI endpoint, configure it +before continuing and omit the `OPENAI_API_KEY` command. See +[models and inference providers](../../../docs/configuration/models.md). + +## 2. Explore acquisition in the TUI + +Start the terminal interface: + +```bash +uv run ursa +``` + +Type `#` to open the agent picker. The TUI directly registers the arXiv and web +acquisition agents. Run these prompts one at a time: + +```text +#arxiv Find papers about graph neural networks for partial differential +equations. Compare methods and benchmarks, emphasizing possible applications +to shock hydrodynamics, and cite the papers used. +``` + +```text +#web Find reliable sources about graph neural networks for partial differential +equations. Compare methods and benchmarks, emphasizing possible applications +to shock hydrodynamics, and cite the pages used. +``` + +Watch the activity cards as URSA searches, retrieves, and summarizes sources. + +See the [TUI guide](../../../docs/getting-started/tui.md) for agent macros, +workspaces, and controls. The +[acquisition-agent overview](../../../docs/agents/acquisition/index.md) explains +the shared acquire-then-summarize graph and cached outputs. + +## 3. Run the same research task in the dashboard + +Launch the dashboard with external search tools enabled: + +=== "macOS/Linux" + + ```bash + URSA_DASHBOARD_USE_WEB=1 uv run ursa-dashboard + ``` + +=== "Windows PowerShell" + + ```powershell + $env:URSA_DASHBOARD_USE_WEB = "1" + uv run ursa-dashboard + ``` + +Open `http://127.0.0.1:8080`, then: + +1. Confirm your model and credential source under **Settings → LLM**. +2. Create a session with a disposable workspace. +3. Select the **Execution Agent**. +4. Submit this prompt: + +```text +Use the arXiv, OSTI, and web-search tools to investigate graph neural networks +for partial differential equations. Compare methods and benchmarks, emphasize +possible applications to shock hydrodynamics, distinguish claims by source +collection, and include source links. +``` + +Setting `URSA_DASHBOARD_USE_WEB=1` is required: it opts supported dashboard +agents into the arXiv, OSTI, and web-search tools. Follow the activity timeline +to see which tool supplied each part of the answer. + +See the [dashboard guide](../../../docs/getting-started/dashboard.md) for +credential storage, workspace selection, and remote-access safety. + +## 4. Compare all three agents from Python + +Run the included script: + +```bash +uv run acquisition_agents.py +``` + +The script initializes its chat model from [`config.yaml`](config.yaml), gives every +acquisition agent the same query and context, and prints three summary panels. +It limits the web and OSTI searches to five results and arXiv to three results. +Edit `config.yaml` to select another configured model or inference provider; +edit `QUERY` or `CONTEXT` in `acquisition_agents.py` to run your own comparison. + +Inspect these generated paths after the run: + +| Source | Retrieved material | Summaries | +| --- | --- | --- | +| Web | `web_db/` | `web_summaries/` | +| OSTI | `osti_db/` | `osti_summaries/` | +| arXiv | `arxiv_papers/` | `arxiv_generated_summaries/` | + +The script intentionally performs real network requests and LLM calls. Result +availability, runtime, and cost depend on the upstream services and selected +model. Reduce each `max_results` value before experimenting if you want a +smaller first run. + +For programmatic concepts and model initialization, read the +[Python guide](../../../docs/getting-started/python-scripts.md). The individual +[arXiv](../../../docs/agents/acquisition/arxiv.md), +[OSTI](../../../docs/agents/acquisition/osti.md), and +[web-search](../../../docs/agents/acquisition/web-search.md) pages document each +agent's parameters and outputs. + +## Troubleshooting + +- If `uv run ursa` cannot find a key, run `uv run ursa --print-config` and + review the [configuration guide](../../../docs/configuration/index.md). +- If the dashboard does not expose search activity, stop it, set + `URSA_DASHBOARD_USE_WEB=1`, and restart it as shown above. +- If a source returns no items, try a shorter query or rerun later; arXiv, OSTI, + and DDGS are independent upstream services. +- If an earlier run affects the comparison, move or remove that source's cache + and summary directories before rerunning. diff --git a/examples/single_agent_examples/acquisition_examples/acquisition_agents.py b/examples/single_agent_examples/acquisition_examples/acquisition_agents.py new file mode 100644 index 00000000..79fa07b5 --- /dev/null +++ b/examples/single_agent_examples/acquisition_examples/acquisition_agents.py @@ -0,0 +1,66 @@ +"""Compare URSA's web, OSTI, and arXiv acquisition agents.""" + +import asyncio +from pathlib import Path + +from rich import print as rprint +from rich.panel import Panel + +from ursa.agents import ArxivAgent, OSTIAgent, WebSearchAgent +from ursa.cli.config import UrsaConfig, resolve_ursa_config +from ursa.util.events import configure_event_logging + +QUERY = "graph neural networks for partial differential equations" +CONTEXT = ( + "Compare methods and benchmarks, emphasizing possible applications to " + "shock hydrodynamics. Identify which claims come from each source." +) + +configure_event_logging() + + +def print_summary(summary: str, title: str) -> None: + """Render one agent's aggregate summary.""" + rprint(Panel(summary, title=title)) + + +async def main() -> None: + """Run the same research question through all three source types.""" + config = resolve_ursa_config( + UrsaConfig.from_file(Path("config.yaml")) + ) + model = config.llm_model.init_chat_model() + + web_agent = WebSearchAgent( + llm=model, + max_results=5, + database_path="web_db", + summaries_path="web_summaries", + enable_metrics=True, + ) + result = await web_agent.ainvoke({"query": QUERY, "context": CONTEXT}) + print_summary(result["final_summary"], title="Web summary") + + osti_agent = OSTIAgent( + llm=model, + max_results=5, + database_path="osti_db", + summaries_path="osti_summaries", + enable_metrics=True, + ) + result = await osti_agent.ainvoke({"query": QUERY, "context": CONTEXT}) + print_summary(result["final_summary"], title="OSTI summary") + + arxiv_agent = ArxivAgent( + llm=model, + max_results=3, + database_path="arxiv_papers", + summaries_path="arxiv_generated_summaries", + enable_metrics=True, + ) + result = await arxiv_agent.ainvoke({"query": QUERY, "context": CONTEXT}) + print_summary(result["final_summary"], title="arXiv summary") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/single_agent_examples/acquisition_examples/acquistion_agents.py b/examples/single_agent_examples/acquisition_examples/acquistion_agents.py deleted file mode 100644 index f4d191e2..00000000 --- a/examples/single_agent_examples/acquisition_examples/acquistion_agents.py +++ /dev/null @@ -1,62 +0,0 @@ -import asyncio - -from langchain.chat_models import init_chat_model -from rich import print as rprint -from rich.panel import Panel - -from ursa.agents import ArxivAgent, OSTIAgent, WebSearchAgent -from ursa.util.events import configure_event_logging - -configure_event_logging() - - -def print_summary(summary: str, title: str): - rprint(Panel(summary, title=title)) - - -async def main(): - # Web search (ddgs) agent - web_agent = WebSearchAgent( - llm=init_chat_model("openai:gpt-5.4-mini"), - max_results=20, - database_path="web_db", - summaries_path="web_summaries", - enable_metrics=True, - ) - result = await web_agent.ainvoke({ - "query": "graph neural networks for PDEs", - "context": "Summarize methods & benchmarks and potential for shock hydrodynamics", - }) - print_summary(result["final_summary"], title="Web Agent Summary") - - # OSTI agent - osti_agent = OSTIAgent( - llm=init_chat_model("openai:gpt-5.4-mini"), - max_results=5, - database_path="osti_db", - summaries_path="osti_summaries", - enable_metrics=True, - ) - result = await osti_agent.ainvoke({ - "query": "quantum annealing materials", - "context": "What are the key findings?", - }) - print_summary(result["final_summary"], title="OSTI Agent Summary") - - # ArXiv agent - arxiv_agent = ArxivAgent( - llm=init_chat_model("openai:gpt-5.4-mini"), - max_results=3, - database_path="arxiv_papers", - summaries_path="arxiv_generated_summaries", - enable_metrics=True, - ) - result = await arxiv_agent.ainvoke({ - "query": "graph neural networks for PDEs", - "context": "Summarize methods & benchmarks and potential for shock hydrodynamics", - }) - print_summary(result["final_summary"], title="Arxiv Agent Summary") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/examples/single_agent_examples/acquisition_examples/config.yaml b/examples/single_agent_examples/acquisition_examples/config.yaml new file mode 100644 index 00000000..bea9a4fe --- /dev/null +++ b/examples/single_agent_examples/acquisition_examples/config.yaml @@ -0,0 +1,2 @@ +llm_model: + model: openai:gpt-5.4-mini diff --git a/examples/single_agent_examples/acquisition_examples/example.yaml b/examples/single_agent_examples/acquisition_examples/example.yaml new file mode 100644 index 00000000..bf0d50d0 --- /dev/null +++ b/examples/single_agent_examples/acquisition_examples/example.yaml @@ -0,0 +1,8 @@ +title: Research acquisition agents +summary: Query arXiv, OSTI, and the web with URSA acquisition agents. +tags: + - acquisition + - arxiv + - web-search + - python-api + - guided diff --git a/examples/single_agent_examples/acquisition_examples/pyproject.toml b/examples/single_agent_examples/acquisition_examples/pyproject.toml new file mode 100644 index 00000000..ce215211 --- /dev/null +++ b/examples/single_agent_examples/acquisition_examples/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "ursa-example-acquisition-examples" +version = "0.1.0" +description = "Query arXiv, OSTI, and the web with URSA acquisition agents." +requires-python = ">=3.11" +dependencies = [ + "ursa-ai[dashboard]", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../../..", editable = true } diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization/README.md b/examples/single_agent_examples/execution_agent/bayesian_optimization/README.md new file mode 100644 index 00000000..5a30641d --- /dev/null +++ b/examples/single_agent_examples/execution_agent/bayesian_optimization/README.md @@ -0,0 +1,148 @@ +# Continue a Bayesian optimization run from a checkpoint + +Use this example to give an `ExecutionAgent` a scientific programming task, +then return to the same thread with a follow-up visualization request. The +agent must implement and run a Bayesian optimization of the six-hump camel +function; the second script reopens its checkpoint and asks for convergence and +input-importance plots. + +This is an agent-generated workflow, not a fixed optimization implementation. +Inspect the code, numerical results, and plots that the model produces before +relying on them. + +## What the files demonstrate + +- `bayesian_optimization.py` starts the OpenAI-backed run, using + `workspace_BO/` and thread ID `BO_test`. +- `bayesian_optimization_continue.py` reuses that workspace and thread ID so it + can continue from the first run's checkpoint. +- `bayesian_optimization_ollama.py` is an independent local-model variant. It + does not participate in the two-step checkpoint walkthrough. + +Read the [ExecutionAgent guide](../../../../docs/agents/execution.md) for its +code-writing and command-execution behavior, and review +[checkpointing and sharing](../../../../docs/persistence/checkpoints-and-sharing.md) +for the persistence concepts used here. + +## Prerequisites + +- [uv](https://docs.astral.sh/uv/) +- An OpenAI API key for the checkpoint walkthrough +- A directory whose generated `workspace_BO/` contents you are comfortable + reviewing and removing + +Run every command from this `bayesian_optimization` directory. + +## 1. Install the example environment + +=== "macOS/Linux" + + ```bash + uv sync + export OPENAI_API_KEY="..." + ``` + +=== "Windows PowerShell" + + ```powershell + uv sync + $env:OPENAI_API_KEY = "..." + ``` + +The scripts use `openai:gpt-5.4-mini`. To select another endpoint, update the +model initialization in both checkpoint scripts and follow the +[configuration guide](../../../../docs/configuration/index.md). Keep both +scripts on the same model configuration when comparing the initial and +continued runs. + +## 2. Start the optimization + +```bash +uv run bayesian_optimization.py +``` + +The execution agent receives the optimization objective, writes its chosen +implementation under `workspace_BO/`, runs it, and reports its result. URSA also +records state for thread `BO_test` in that workspace and prints a timing +summary. + +Before continuing: + +1. Read the generated implementation. +2. Confirm that it evaluates the standard six-hump camel function on an + appropriate bounded domain. +3. Check that the reported best point and value are supported by saved + evaluations rather than prose alone. +4. Review any commands and dependency installations performed by the agent. + +Because an LLM chooses the implementation, exact filenames and optimization +libraries can differ between runs. + +## 3. Continue the checkpointed thread + +Run the continuation only after the first command completes successfully: + +```bash +uv run bayesian_optimization_continue.py +``` + +The continuation script points to the same `workspace_BO/`, creates a +checkpointer from that workspace, and invokes `ExecutionAgent` with the same +`BO_test` thread ID. Its prompt asks the agent to use the existing evaluation +history to create: + +- a convergence plot with the running minimum; and +- a second plot highlighting important function inputs. + +Confirm that the plots use results from the first run. If they silently create +a new optimization history, inspect the checkpoint files and first-run output +before trying again. + +## Optional: run the Ollama variant + +The Ollama script is a separate choose-and-run example; it does not resume the +OpenAI checkpoint. Install and start [Ollama](https://ollama.com/), then pull the +model named by the script: + +=== "macOS/Linux" + + ```bash + ollama pull gpt-oss:20b + uv run bayesian_optimization_ollama.py + ``` + +=== "Windows PowerShell" + + ```powershell + ollama pull gpt-oss:20b + uv run bayesian_optimization_ollama.py + ``` + +Set `set_workspace = True` in the script if you want this independent run to +write under `workspace_BO/`. Do not assume it can continue the OpenAI thread: +the Ollama variant does not configure the same checkpointer or thread ID. + +## Adapt the Python workflow + +Edit the `problem` string to change the scientific task. If you want a new +checkpoint lineage, change both `workspace` and `thread_id` consistently in the +initial and continuation scripts. Reusing only one of them can attach the +follow-up to the wrong state or make the expected state unavailable. + +See the [Python getting-started guide](../../../../docs/getting-started/python-scripts.md) +for model initialization and direct agent invocation patterns. + +## Troubleshooting and cleanup + +- If authentication fails, verify the active key or provider with the + [configuration guide](../../../../docs/configuration/index.md). +- If continuation cannot find prior state, confirm that the first run completed + and that neither script's `workspace` or `thread_id` was changed alone. +- If Ollama cannot find its model, run `ollama list` and make the script's model + string match the locally installed tag. +- To start the OpenAI walkthrough from scratch, move `workspace_BO/` somewhere + safe or remove it after confirming that you no longer need its generated code, + results, or checkpoints. + +These scripts make real LLM calls and allow generated code execution. Runtime, +cost, dependencies, and artifacts vary by model response. diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization.py b/examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization.py similarity index 100% rename from examples/single_agent_examples/execution_agent/bayesian_optimization.py rename to examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization.py diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization_continue.py b/examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization_continue.py similarity index 100% rename from examples/single_agent_examples/execution_agent/bayesian_optimization_continue.py rename to examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization_continue.py diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization_ollama.py b/examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization_ollama.py similarity index 89% rename from examples/single_agent_examples/execution_agent/bayesian_optimization_ollama.py rename to examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization_ollama.py index 68076ab7..46f7e9a6 100644 --- a/examples/single_agent_examples/execution_agent/bayesian_optimization_ollama.py +++ b/examples/single_agent_examples/execution_agent/bayesian_optimization/bayesian_optimization_ollama.py @@ -1,5 +1,4 @@ from langchain.chat_models import init_chat_model -from langchain.embeddings import init_embeddings from langchain_core.messages import HumanMessage from ursa.agents import ExecutionAgent @@ -20,8 +19,6 @@ model = init_chat_model(model="ollama:gpt-oss:20b") -embedding_model = init_embeddings(model="ollama:nomic-embed-text:latest") - # Initialize the agent executor = ExecutionAgent(llm=model) diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization/example.yaml b/examples/single_agent_examples/execution_agent/bayesian_optimization/example.yaml new file mode 100644 index 00000000..bfc76546 --- /dev/null +++ b/examples/single_agent_examples/execution_agent/bayesian_optimization/example.yaml @@ -0,0 +1,7 @@ +title: Bayesian optimization +summary: Use an execution agent to implement, checkpoint, and continue a Bayesian optimization workflow. +tags: + - execution-agent + - optimization + - python-api + - guided diff --git a/examples/single_agent_examples/execution_agent/bayesian_optimization/pyproject.toml b/examples/single_agent_examples/execution_agent/bayesian_optimization/pyproject.toml new file mode 100644 index 00000000..42d2c113 --- /dev/null +++ b/examples/single_agent_examples/execution_agent/bayesian_optimization/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "ursa-example-bayesian-optimization" +version = "0.1.0" +description = "Use an execution agent to implement, checkpoint, and continue a Bayesian optimization workflow." +requires-python = ">=3.11" +dependencies = [ + "ursa-ai", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../../../..", editable = true } diff --git a/examples/single_agent_examples/execution_agent/risk_buydown/README.md b/examples/single_agent_examples/execution_agent/risk_buydown/README.md new file mode 100644 index 00000000..9b988d9b --- /dev/null +++ b/examples/single_agent_examples/execution_agent/risk_buydown/README.md @@ -0,0 +1,168 @@ +# Choose experiments for risk buy-down + +Give URSA two small CSV files, ask an execution agent to rank candidate +experiments under cost and schedule constraints, and then audit its selected +campaign. This guided exercise emphasizes transparent calculations and +reviewable artifacts rather than treating an agent's recommendation as a real +qualification or safety decision. + +You can run the same exercise in the TUI or dashboard. In either interface, +make this directory the agent's workspace so the relative input paths resolve +and the requested outputs stay beside the example. + +## Inspect the inputs + +Open [the design-risk table](design_risks.csv) and +[the candidate-experiment table](candidate_experiments.csv) before launching an +agent. The files use these schemas: + +```text +risk_id,risk_name,risk_area,initial_probability,impact,uncertainty,description +``` + +```text +experiment_id,experiment_name,cost_kusd,duration_days,risk_targets,expected_uncertainty_reduction,confidence,description +``` + +The `risk_targets` field contains semicolon-separated risk IDs. All values and +scoring rules are synthetic. Do not use the resulting ranking as evidence of +readiness, qualification, or safety. + +## Prepare the example + +Open a terminal in the URSA repository, enter this directory, and install its +dependencies. The dashboard extra is included so either interface is available. + +=== "macOS/Linux" + + ```bash + cd examples/single_agent_examples/execution_agent/risk_buydown + uv sync + export OPENAI_API_KEY="..." + ``` + +=== "Windows PowerShell" + + ```powershell + Set-Location examples\single_agent_examples\execution_agent\risk_buydown + uv sync + $env:OPENAI_API_KEY = "..." + ``` + +OpenAI works with URSA's built-in defaults and needs no config file. If you use +another endpoint or model, configure it as described in the +[configuration guide](../../../../configuration/index.md). + +## Choose an interface + +=== "TUI" + + Start URSA from this directory: + + === "macOS/Linux" + + ```bash + uv run ursa + ``` + + === "Windows PowerShell" + + ```powershell + uv run ursa + ``` + + Keep this example directory as the workspace. Paste the prompt in the next + section with its leading `#execute`, which selects the execution agent. See + the [TUI guide](../../../../getting-started/tui.md) for agent selection and + application commands. + +=== "Dashboard" + + Start the dashboard from this directory: + + === "macOS/Linux" + + ```bash + uv run ursa-dashboard + ``` + + === "Windows PowerShell" + + ```powershell + uv run ursa-dashboard + ``` + + Open the printed address, normally `http://127.0.0.1:8080`. Create an + **Execution Agent** session and select this example directory as its + workspace. Paste the prompt below without the leading `#execute`, because + the session already selects the agent. See the + [dashboard guide](../../../../getting-started/dashboard.md) for credential + storage and workspace behavior. + +## Ask the agent to rank the experiments + +Paste this prompt into your chosen interface. Keep `#execute` when using the +TUI; remove only that prefix in an Execution Agent dashboard session. + +```text +#execute Read ./design_risks.csv and ./candidate_experiments.csv. Select an +experiment campaign that maximizes expected risk reduction subject to total +cost <= 150 kUSD, total duration <= 45 days, and at most 4 experiments. + +For each risk use: +initial_risk_score = initial_probability * impact * uncertainty + +For each experiment use: +expected_risk_reduction = + sum(initial_risk_score for targeted risks) + * expected_uncertainty_reduction + * confidence + +Search the feasible experiment combinations. Keep the calculation simple and +explainable. Write only under ./risk_buydown_outputs/ and create: +- experiment_rankings.csv +- selected_campaign.csv +- risk_before_after.csv +- experiment_value_scatter.png +- risk_before_after.png +- risk_buydown_reasoning.txt +- risk_buydown_recommendation.txt + +Validate the input columns, do not overwrite inputs, and do not present this +toy analysis as a real qualification, readiness, or safety determination. +``` + +Review each proposed tool action before approving it. The execution agent can +write files and run commands in its workspace; use a disposable copy when you +adapt this exercise to data you cannot replace. The +[ExecutionAgent guide](../../../../agents/execution.md) describes its tools and +workspace behavior, and +[Sandboxing and information control](../../../../best-practices/sandboxing.md) +explains stronger isolation options. + +## Audit the recommendation + +Open `risk_buydown_outputs/` after the agent finishes. Do not stop at the +recommendation text. Check the work in this order: + +1. Confirm that both source CSVs are unchanged. +2. Verify that `experiment_rankings.csv` contains every candidate and exposes + the score components used for ranking. +3. Recompute the selected campaign's total cost and duration from + `candidate_experiments.csv`; confirm cost is at most 150 kUSD, duration is at + most 45 days, and no more than four experiments were selected. +4. Recompute the initial risk scores and expected reductions from the formulas + in the prompt. +5. Confirm that `risk_before_after.csv` accounts for every risk, including + risks untouched by the selected campaign. +6. Inspect both plots and make sure selected experiments are distinguishable + from alternatives. +7. Read `risk_buydown_reasoning.txt` and + `risk_buydown_recommendation.txt`; require them to state remaining risks, + assumptions, and the toy nature of the exercise. + +If an artifact is missing or a constraint fails, ask the same execution-agent +session to inspect its work and correct the output rather than silently +accepting a partial result. Because an LLM chooses the implementation, exact +rankings and filenames' internal formats may vary even though the requested +files and constraints are fixed. diff --git a/examples/single_agent_examples/execution_agent/risk_buydown/example.yaml b/examples/single_agent_examples/execution_agent/risk_buydown/example.yaml new file mode 100644 index 00000000..d0f5ce4b --- /dev/null +++ b/examples/single_agent_examples/execution_agent/risk_buydown/example.yaml @@ -0,0 +1,7 @@ +title: Experiment risk buy-down +summary: Rank and select a constrained experiment campaign using transparent toy risk scores. +tags: + - guided + - execution-agent + - optimization + - plotting diff --git a/examples/single_agent_examples/execution_agent/risk_buydown/pyproject.toml b/examples/single_agent_examples/execution_agent/risk_buydown/pyproject.toml new file mode 100644 index 00000000..784bbf27 --- /dev/null +++ b/examples/single_agent_examples/execution_agent/risk_buydown/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "ursa-example-risk-buydown" +version = "0.1.0" +description = "Risk buy-down exercise used in the URSA documentation" +requires-python = ">=3.11" +dependencies = [ + "matplotlib>=3.10", + "ursa-ai[dashboard]", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../../../..", editable = true } diff --git a/examples/use_mcp_tools/README.md b/examples/use_mcp_tools/README.md new file mode 100644 index 00000000..670fab9c --- /dev/null +++ b/examples/use_mcp_tools/README.md @@ -0,0 +1,167 @@ +# Use SQLite tools through MCP + +Start a local SQLite MCP server, verify its tools with a small Python client, +and then give those tools to an URSA execution agent in the dashboard. By the +end, the agent will create a database, generate data, and return a plot as an +artifact. + +This is a local learning example, not a production database service. The server +restricts database files to `sqlite_data/` and permits only read-only queries +through its query tool. + +## Prepare the example + +Clone the URSA repository, open a terminal in its root, and enter this example +directory. Then let `uv` create an isolated environment and install the example, +URSA, and the dashboard. + +=== "macOS/Linux" + + ```bash + cd examples/use_mcp_tools + uv sync + ``` + +=== "Windows PowerShell" + + ```powershell + Set-Location examples\use_mcp_tools + uv sync + ``` + +The important files are: + +- [`sqlite_mcp.py`](sqlite_mcp.py), the MCP server and SQLite tools +- [`test_sqlite_mcp.py`](test_sqlite_mcp.py), a direct client that exercises + each tool +- `sqlite_data/`, which the server creates when it stores the example databases + +## Start the MCP server + +Open your first terminal in this directory and start the server. Leave it +running for the rest of the walkthrough. + +=== "macOS/Linux" + + ```bash + uv run sqlite_mcp.py + ``` + +=== "Windows PowerShell" + + ```powershell + uv run sqlite_mcp.py + ``` + +The server listens for Streamable HTTP connections at +`http://127.0.0.1:8000/mcp`. + +![The SQLite MCP server running in a terminal](./images/sqlite_mcp.png) + +## Exercise the tools directly + +Open a second terminal in the same directory and run the client harness before +introducing URSA. This separates a server or tool problem from an agent +configuration problem. + +=== "macOS/Linux" + + ```bash + uv run test_sqlite_mcp.py + ``` + +=== "Windows PowerShell" + + ```powershell + uv run test_sqlite_mcp.py + ``` + +The client discovers the available tools, creates `demo_test.db`, creates and +describes a table, inserts three rows, and queries them back. A successful run +ends with `Test completed successfully.` + +## Connect URSA to the server + +Keep the MCP server running. In the second terminal, launch the dashboard from +the example environment: + +=== "macOS/Linux" + + ```bash + uv run ursa-dashboard + ``` + +=== "Windows PowerShell" + + ```powershell + uv run ursa-dashboard + ``` + +Open the address printed by the command, normally +`http://127.0.0.1:8080`. In the dashboard: + +1. Open **Settings → MCP Tools**. +2. Enter `sqlite_demo` as the **Server name**. +3. Paste this server configuration: + + ```json + { + "transport": "streamable_http", + "url": "http://127.0.0.1:8000/mcp" + } + ``` + +4. Select **Save**, then close Settings. +5. Create a new **Execution Agent** session and choose a disposable workspace + or another folder you are comfortable allowing the agent to modify. + +![URSA dashboard with a session open](./images/ursa-dashboard.png) + +See the [MCP configuration guide](../../configuration/mcp.md) for other +transports and authenticated servers. See the +[dashboard guide](../../getting-started/dashboard.md) for credential, +workspace, and remote-access details. The broader +[configuration guide](../../configuration/index.md) explains how URSA combines +its built-in defaults, user configuration, and explicit config files. + +## Ask the execution agent to use SQLite + +Paste the following prompt into the new session and select **Send**: + +```text +Use the sqlite_demo MCP tools to create a database called materials_demo +and a table called tensile_experiments with the following columns: +sample_id as a TEXT primary key, temperature_K as REAL, strain_rate_s as REAL, +grain_size_um as REAL, yield_strength_MPa as REAL, and phase_label as TEXT. + +Then generate 100 synthetic rows of data using numpy with reasonable random +distributions: temperature_K uniformly between 250 and 1200, strain_rate_s +log-uniformly between 1e-4 and 1e1, grain_size_um normally distributed around +20 with a standard deviation of 5 and clipped to positive values, and +yield_strength_MPa computed from a simple synthetic relationship where strength +decreases with temperature, increases with strain rate, and increases slightly +as grain size decreases, plus some random noise. + +Assign each row a sample_id from sample_001 to sample_100 and a phase_label +of alpha or beta based on whether temperature_K is below or above 700. + +Insert all rows into the table, query the full table back out, and then plot +yield_strength_MPa versus temperature_K with points colored by phase_label. +Save this to an appropriate PNG filename. + +Also print a short summary of the table contents and the fitted synthetic +trends you used. +``` + +Watch the MCP server terminal as the agent calls its tools. When the run +finishes, inspect the stdout summary, then open the **Artifacts** panel and +refresh it if necessary. Your numeric values will vary, but the plot should +resemble this result: + +![Yield strength plotted against temperature](./images/artifact-plot.png) + +You have now tested the same MCP tools at two layers: first with a deterministic +Python client, and then through an URSA agent. Continue with the +[execution-agent guide](../../agents/execution.md) to learn how its workspace +and tool use behave, or adapt `sqlite_mcp.py` to expose tools for your own local +data source. diff --git a/examples/use_mcp_tools/example.yaml b/examples/use_mcp_tools/example.yaml new file mode 100644 index 00000000..3b6583ca --- /dev/null +++ b/examples/use_mcp_tools/example.yaml @@ -0,0 +1,7 @@ +title: SQLite tools through MCP +summary: Run a local SQLite MCP server, verify its tools, and connect it to the URSA dashboard. +tags: + - guided + - mcp + - dashboard + - beginner diff --git a/examples/mcp_examples/images/artifact-plot.png b/examples/use_mcp_tools/images/artifact-plot.png similarity index 100% rename from examples/mcp_examples/images/artifact-plot.png rename to examples/use_mcp_tools/images/artifact-plot.png diff --git a/examples/mcp_examples/images/sqlite_mcp.png b/examples/use_mcp_tools/images/sqlite_mcp.png similarity index 100% rename from examples/mcp_examples/images/sqlite_mcp.png rename to examples/use_mcp_tools/images/sqlite_mcp.png diff --git a/examples/mcp_examples/images/ursa-dashboard.png b/examples/use_mcp_tools/images/ursa-dashboard.png similarity index 100% rename from examples/mcp_examples/images/ursa-dashboard.png rename to examples/use_mcp_tools/images/ursa-dashboard.png diff --git a/examples/use_mcp_tools/pyproject.toml b/examples/use_mcp_tools/pyproject.toml new file mode 100644 index 00000000..12f54e4c --- /dev/null +++ b/examples/use_mcp_tools/pyproject.toml @@ -0,0 +1,16 @@ +[project] +name = "ursa-example-sqlite-mcp" +version = "0.1.0" +description = "Local SQLite MCP server and URSA dashboard example" +requires-python = ">=3.11" +dependencies = [ + "langchain-mcp-adapters~=0.2.2", + "mcp>=1.20,<2", + "ursa-ai[dashboard]", +] + +[tool.uv] +package = false + +[tool.uv.sources] +ursa-ai = { path = "../..", editable = true } diff --git a/examples/mcp_examples/sqlite_mcp.py b/examples/use_mcp_tools/sqlite_mcp.py similarity index 100% rename from examples/mcp_examples/sqlite_mcp.py rename to examples/use_mcp_tools/sqlite_mcp.py diff --git a/examples/mcp_examples/test_sqlite_mcp.py b/examples/use_mcp_tools/test_sqlite_mcp.py similarity index 100% rename from examples/mcp_examples/test_sqlite_mcp.py rename to examples/use_mcp_tools/test_sqlite_mcp.py diff --git a/mkdocs.yml b/mkdocs.yml index 48acdfc5..e6c92c63 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,9 @@ theme: custom_dir: docs/overrides logo: assets/favicon.png favicon: assets/favicon.png + features: + - navigation.instant + - content.code.copy extra: version: @@ -14,9 +17,26 @@ extra: default: latest alias: true +extra_css: + - stylesheets/example-filter.css + +extra_javascript: + - javascripts/example-filter.js + markdown_extensions: - admonition + - attr_list + - md_in_html - pymdownx.details + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets: + base_path: + - . - pymdownx.superfences - pymdownx.tabbed: alternate_style: true @@ -25,6 +45,7 @@ markdown_extensions: plugins: - search + - tags - autorefs - mkdocstrings: handlers: @@ -40,23 +61,22 @@ plugins: watch: - src +hooks: + - docs/hooks.py + nav: - Home: index.md - - Installation: - - Overview: installation/index.md - - Install with uv: installation/uv.md - - Install with pip: installation/pip.md - Getting Started: - - CLI: getting-started/cli.md + - Overview: getting-started/index.md + - TUI: getting-started/tui.md - Web Dashboard: getting-started/dashboard.md - Python Scripts: getting-started/python-scripts.md - Plan-Execute From YAML: getting-started/plan-execute-yaml.md - MCP Server: getting-started/mcp-server.md + - Examples: examples/index.md - Configuration: - Overview: configuration/index.md - - OpenAI-compatible endpoints: configuration/openai-compatible.md - - Ollama and local endpoints: configuration/ollama.md - - LangChain providers: configuration/langchain-providers.md + - Models and inference providers: configuration/models.md - Secrets: configuration/secrets.md - Files, CLI flags, and environment variables: configuration/files-and-env.md - MCP server configuration: configuration/mcp.md