diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md index b20953e..0577a46 100644 --- a/agents/aqcat/README.md +++ b/agents/aqcat/README.md @@ -10,7 +10,7 @@ This agent gives Discovery users conversational access to that capability. A res **Intended user:** computational chemists, catalysis researchers, and materials scientists doing candidate triage. -**Related agents.** [`aqpotency`](../aqpotency/README.md) covers small-molecule potency and selectivity on the same platform and the same connection. [`sandboxaq`](../sandboxaq/README.md) is separate — it prompts a SandboxAQ model you have subscribed to in Azure AI Foundry, and needs no platform tenant. +**Related agents.** [`aqpotency`](../aqpotency/README.md) covers small-molecule potency and selectivity on the same platform and the same connection. **Successful outcome:** a ranked, correctly-attributed set of adsorption energies with units intact, each reported with its convergence status and the resolved bulk identifier, plus an explicit statement of which systems fall outside the model's applicability. diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md index e2982a2..cde81b1 100644 --- a/agents/aqpotency/README.md +++ b/agents/aqpotency/README.md @@ -10,7 +10,7 @@ This agent gives Discovery users conversational access to three framings of that **Intended user:** medicinal chemists, computational chemists, and computational biologists prioritizing compounds and assessing off-target liabilities. -**Related agents.** [`aqcat`](../aqcat/README.md) covers catalysis and surface reactivity on the same platform and the same connection. [`sandboxaq`](../sandboxaq/README.md) is separate — it prompts a SandboxAQ model you have subscribed to in Azure AI Foundry, and needs no platform tenant. +**Related agents.** [`aqcat`](../aqcat/README.md) covers catalysis and surface reactivity on the same platform and the same connection. **Successful outcome:** predicted potencies with uncertainty, correctly attributed to molecule and target, with weakly-supported predictions clearly marked so they are not ranked as though they were confident. diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md deleted file mode 100644 index 0d7dbb8..0000000 --- a/agents/sandboxaq/README.md +++ /dev/null @@ -1,196 +0,0 @@ -# SandboxAQ Foundry Model Agent Deployment Guide - -This guide provides step-by-step instructions for deploying the SandboxAQ -Foundry Model tool and its associated agent to the Microsoft Discovery -platform. - -## Overview - -This agent invokes a SandboxAQ model that your organization has subscribed to -through Azure AI Foundry, calling the OpenAI-compatible chat-completions -endpoint from a Discovery-managed tool container. This deployment includes: - -- **Dockerfile**: Used for creation of the SandboxAQ model tool container image -- **Tool Definition**: Configuration for the `sandboxaq-model` tool -- **Agent Definition**: AI agent configuration for the SandboxAQ agent - -### Which SandboxAQ agent do I want? - -Three SandboxAQ agents are published in this catalog. Choose by what you have access to: - -| Agent | Use it when | Requires | -|---|---|---| -| **`sandboxaq`** (this one) | You have subscribed to a SandboxAQ model in Azure AI Foundry and want to prompt it directly | A SandboxAQ model subscription in Foundry | -| [**`aqcat`**](../aqcat/README.md) | You want adsorption energies and surface reactivity for catalysis | A SandboxAQ AI Simulation Platform tenant | -| [**`aqpotency`**](../aqpotency/README.md) | You want small-molecule potency, panel scans, or selectivity | A SandboxAQ AI Simulation Platform tenant | - -This agent is general-purpose inference against a model deployment you control. The other two are domain agents that reach specific scientific tools on the SandboxAQ platform. - -## Prerequisites - -Before starting the deployment, ensure you have: - -1. Access to Microsoft Discovery platform -2. An active SandboxAQ model subscription deployed in Azure AI Foundry -3. Azure Container Registry (ACR) with appropriate permissions -4. Docker installed locally for image building -5. Azure CLI or PowerShell for resource management - -## Build Docker Image - -### Step 1: Build and Publish Docker Image - -1. **Build the image** from this tool directory: - - ```bash - docker build -t sandboxaq-model:latest . - ``` - -2. **Tag the image** for your Azure Container Registry: - - ```bash - docker tag sandboxaq-model:latest mycontainerregistry.azurecr.io/sandboxaq-model:latest - ``` - - > Replace `mycontainerregistry` with your actual ACR name - -3. **Login to Azure Container Registry**: - - ```bash - az acr login --name mycontainerregistry - ``` - -4. **Push the image** to your container registry: - - ```bash - docker push mycontainerregistry.azurecr.io/sandboxaq-model:latest - ``` - -## File Structure - -```text -sandboxaq/ -├── agent.yaml # Agent configuration (YAML) -├── metadata.yaml # Catalog metadata -├── tools/ -│ └── sandboxaq-model/ -│ ├── Dockerfile # Container image definition -│ ├── tool.yaml # Tool configuration (YAML) -│ └── sandboxaq_utils.py # Foundry client helper module -└── README.md # This deployment guide -``` - -## Key Configuration Details - -### Agent Capabilities - -The SandboxAQ agent provides: - -- **Model Invocation**: Sends prompts to the subscribed SandboxAQ deployment -- **Structured Output**: Preserves JSON returned by the endpoint and writes - results to `/output/final_results.json` -- **Sensitive-Data Handling**: Applies access, privacy, and human-review - guidance when working with HR or other confidential business data - -## Usage - -### Basic Queries - -| Prompt | Description | -|--------|-------------| -| "Run the SandboxAQ model on this prompt and return the result as JSON." | Direct invocation | -| "Summarize this HR policy using the subscribed SandboxAQ deployment." | Summarization | -| "Ask the SandboxAQ model to classify these support tickets." | Classification | - -### Advanced Queries - -| Prompt | Description | -|--------|-------------| -| "Run this prompt at temperature 0.7 and cap the output at 1000 tokens." | Tuned sampling parameters | -| "Use a system prompt of 'You are a concise policy analyst' for this request." | Custom system instruction | -| "Invoke the model on each of these five prompts and collect the results." | Batch invocation in one script | - -## Architecture - -This agent operates as a `kind: prompt` agent within Discovery Studio. - - User Input → SandboxAQ Agent (LLM) → sandboxaq-model Tool (Container) → Azure AI Foundry → Results - -- **Model:** Configured via the `{{CHAT-MODEL}}` parameter at deploy time -- **Tool:** `sandboxaq-model` container exposing a `python3` code environment - -The tool exposes a Python code environment rather than a fixed action. The -agent writes a script that imports `sandboxaq_utils` and calls -`invoke_model(prompt, system_prompt=..., temperature=..., max_tokens=...)`, -which returns a dict with `model`, `content`, `finish_reason`, `usage`, and -`raw_response`. - -Prompts therefore travel as Python string literals and are never interpolated -into a shell command, so text containing apostrophes, double quotes, `$`, or -newlines reaches the deployment unmodified. - -## Configuration - -| Parameter | Description | Example | -|---|---|---| -| `{{CHAT-MODEL}}` | Azure AI Foundry model deployment name for the agent | `gpt-4o` | - -The tool reads its endpoint and credential from runtime environment -variables. They are deliberately absent from `tool.yaml`: the tool schema only -supports static, committed values, and the credential must never be committed. -Set them in your deployment's environment-variable configuration (for -example a `sandboxaq-model-EnvVars.json` file), as the PubMed agent does: - -```json -{ - "FOUNDRY_ENDPOINT": "https://.openai.azure.com/openai", - "FOUNDRY_API_KEY": "", - "FOUNDRY_DEPLOYMENT": "" -} -``` - -| Environment variable | Required | Description | -|---|---:|---| -| `FOUNDRY_ENDPOINT` | Yes | Endpoint base URL. For `azure-openai`, include the `/openai` API base; for `serverless`, the URL that `/chat/completions` is appended to | -| `FOUNDRY_API_KEY` | Yes | Secret for the subscribed deployment | -| `FOUNDRY_DEPLOYMENT` | `azure-openai`: Yes; `serverless`: No | SandboxAQ model deployment name. Sent as the `model` field for `serverless` | -| `FOUNDRY_API_STYLE` | No | `azure-openai` (default): `/deployments/{name}/chat/completions` with an `api-key` header. `serverless`: `/chat/completions` with a Bearer token, for Foundry serverless and Foundry Models endpoints | -| `FOUNDRY_API_VERSION` | No | API version. Defaults to `2024-10-21` for `azure-openai`; omitted for `serverless` unless set | - -The endpoint must be reachable from the Discovery tool container. Error -messages from the tool report the HTTP status and response body but never the -endpoint URL or credential. - -## Support - -For issues or questions with this agent, contact SandboxAQ: - - -SandboxAQ contact: support@sandboxaq.com - -For platform issues, open a GitHub issue: - - -## Tools - -| Tool | Path | Description | -|---|---|---| -| `sandboxaq-model` | `tools/sandboxaq-model/` | Invokes a subscribed SandboxAQ model through its Azure AI Foundry OpenAI-compatible chat-completions endpoint. | - -## Known Limitations - -- Requires an existing SandboxAQ subscription in Azure AI Foundry; the agent - cannot provision or discover deployments on your behalf. -- Supports the chat-completions API surface only. Embeddings, batch, and - streaming endpoints are not exposed. -- Credentials are supplied as environment variables on the deployed tool, so - rotating a key requires updating the tool configuration. -- For sensitive HR data, apply your organization's privacy, access-control, - and human-review requirements. This agent does not make employment - decisions. - -## Contributing - -This project welcomes contributions and suggestions. Please see the -repository's top-level [CONTRIBUTING guidelines](https://github.com/microsoft/discovery/blob/main/CONTRIBUTING.md) -for details on how to contribute. diff --git a/agents/sandboxaq/agent.yaml b/agents/sandboxaq/agent.yaml deleted file mode 100644 index 46ebc0f..0000000 --- a/agents/sandboxaq/agent.yaml +++ /dev/null @@ -1,78 +0,0 @@ -kind: prompt -name: sandboxaq -displayName: SandboxAQ Foundry Model Agent -description: > - Invokes subscribed SandboxAQ models through an Azure AI Foundry endpoint. -model: - id: '{{CHAT-MODEL}}' - options: - temperature: 0 - topP: 0 - maxOutputTokens: 4096 -instructions: |- - You are the SandboxAQ Foundry Model Agent. You reach the subscribed - SandboxAQ deployment by writing Python scripts that run in the tool's code - environment. - - # INSTALLED PYTHON PACKAGES - `requests`, `json` (stdlib), and `sandboxaq_utils` (custom module). - **NOT available:** pandas, numpy, rdkit, matplotlib. Do NOT import these. - - **ALWAYS use `from sandboxaq_utils import invoke_model`** -- this is the - ONLY supported way to call the deployment. Never build the request URL, - headers, or credential yourself, and never invoke the model through a shell - command; passing prompt text through a shell corrupts any prompt containing - quotes or `$`. - - ## CRITICAL RULES - - 1. Put the prompt in a Python variable, not in a shell command. Triple-quoted - string literals handle apostrophes, quotes, and newlines correctly. - 2. Write results to `/output/final_results.json` with `save_json()`. - 3. Do not claim a model was invoked unless the script completed and returned - a response. - 4. Keep user inputs and model outputs clearly separated in your report. - Preserve structured output when the endpoint returns JSON. - 5. Never expose endpoint URLs, API keys, authorization headers, or other - deployment secrets -- not in scripts, logs, or your reply. - 6. If the request is missing information the endpoint requires, ask one - focused clarifying question before running anything. - - ### Script template - - ```python - from sandboxaq_utils import invoke_model, save_json - - prompt = """Summarize the attached policy in five bullet points.""" - - result = invoke_model( - prompt, - system_prompt="You are a concise policy analyst.", - temperature=0, - max_tokens=4096, - ) - - print(result["content"]) - save_json(result, "/output/final_results.json") - ``` - - `invoke_model` returns a dict with `model`, `content`, `finish_reason`, - `usage`, and `raw_response`. If `content` is empty, check `finish_reason` - (for example `content_filter`) and tell the user the model returned no text - rather than reporting an empty answer. It raises `RuntimeError` when the - deployment is not configured, unreachable, or returns an HTTP error; those - messages never contain the endpoint or credential, so report them verbatim - rather than retrying blindly. - - For HR or other sensitive business data: - - Follow the user's organization's access and data-handling policies. - - Do not infer protected attributes or make employment decisions. - - State when the model response is uncertain or requires human review. - - Return only the minimum sensitive information needed for the request. -discoveryExtensions: - humanInTheLoop: Disabled - tools: - - toolId: '{{sandboxaqModelToolId}}' - confirmation: Disabled - disableDataHandlingTools: false - disableDiscoveryInjectedTools: false diff --git a/agents/sandboxaq/metadata.yaml b/agents/sandboxaq/metadata.yaml deleted file mode 100644 index 1a100e1..0000000 --- a/agents/sandboxaq/metadata.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: sandboxaq -type: agent -version: 1.0.0 -associated_tools: - - agents/sandboxaq/tools/sandboxaq-model -publisher: - name: SandboxAQ - contact: support@sandboxaq.com - support_url: https://www.sandboxaq.com/contact - party: 3p -description: > - Invokes SandboxAQ models subscribed through Azure AI Foundry for secure, - structured inference workflows. -tags: - - language-model diff --git a/agents/sandboxaq/tools/sandboxaq-model/Dockerfile b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile deleted file mode 100644 index 5bd04db..0000000 --- a/agents/sandboxaq/tools/sandboxaq-model/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM mcr.microsoft.com/azurelinux/base/python:3.12 - -WORKDIR /app - -# System update + TLS certificates (MUST be first RUN for layer sharing) -RUN tdnf -y update && \ - tdnf -y install ca-certificates && \ - update-ca-trust && \ - tdnf clean all -RUN pip3 install --no-cache-dir requests - -# Create working directories -RUN mkdir -p /input /output /workdir - -# /app: safe from volume mounts, reachable via PYTHONPATH -COPY sandboxaq_utils.py /app/ - -WORKDIR /workdir -ENV PYTHONPATH="/app" - -CMD ["/bin/bash"] diff --git a/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py deleted file mode 100644 index 0efe073..0000000 --- a/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py +++ /dev/null @@ -1,128 +0,0 @@ -"""Helpers for calling a subscribed SandboxAQ model through Azure AI Foundry. - -Agent scripts import from this module rather than shelling out, so prompt text -is never interpreted by a shell: - - from sandboxaq_utils import invoke_model, save_json - - result = invoke_model("Summarize this policy") - save_json(result, "/output/final_results.json") -""" - -import json -import os - -import requests - -DEFAULT_API_VERSION = "2024-10-21" -DEFAULT_TIMEOUT_SECONDS = 300 - - -def required_env(name): - value = os.environ.get(name) - if not value: - raise RuntimeError(f"Required environment variable is not set: {name}") - return value - - -def build_request(deployment): - """Return the chat-completions URL and auth headers for the configured endpoint. - - FOUNDRY_API_STYLE selects the request shape: - - `azure-openai` (default): {endpoint}/deployments/{deployment}/chat/completions - with an `api-key` header. The endpoint includes the `/openai` API base. - - `serverless`: {endpoint}/chat/completions with a Bearer token, for - Foundry serverless / Foundry Models endpoints. The deployment, when - set, is sent as the `model` field. - """ - endpoint = required_env("FOUNDRY_ENDPOINT").rstrip("/") - api_key = required_env("FOUNDRY_API_KEY") - style = (os.environ.get("FOUNDRY_API_STYLE") or "azure-openai").strip().lower() - if style == "azure-openai": - if not deployment: - raise RuntimeError( - "Required environment variable is not set: FOUNDRY_DEPLOYMENT" - ) - api_version = os.environ.get("FOUNDRY_API_VERSION") or DEFAULT_API_VERSION - url = ( - f"{endpoint}/deployments/{deployment}/chat/completions" - f"?api-version={api_version}" - ) - auth = {"api-key": api_key} - elif style == "serverless": - api_version = os.environ.get("FOUNDRY_API_VERSION") - url = f"{endpoint}/chat/completions" - if api_version: - url += f"?api-version={api_version}" - auth = {"Authorization": f"Bearer {api_key}"} - else: - raise RuntimeError( - "FOUNDRY_API_STYLE must be 'azure-openai' or 'serverless'" - ) - return url, {**auth, "Content-Type": "application/json"} - - -def invoke_model( - prompt, - system_prompt="", - temperature=0, - max_tokens=4096, - timeout=DEFAULT_TIMEOUT_SECONDS, -): - """Send a prompt to the configured SandboxAQ deployment. - - Returns a dict with `model`, `content`, `finish_reason`, `usage`, and - `raw_response`. `content` is an empty string when the endpoint returns no - text (for example a `content_filter` finish reason). Raises RuntimeError on - missing configuration, transport or HTTP failures, and empty responses; - error messages never include the endpoint URL or credential. - """ - deployment = os.environ.get("FOUNDRY_DEPLOYMENT", "") - url, headers = build_request(deployment) - messages = [] - if system_prompt: - messages.append({"role": "system", "content": system_prompt}) - messages.append({"role": "user", "content": prompt}) - body = { - "messages": messages, - "temperature": temperature, - "max_tokens": max_tokens, - } - if deployment and "Authorization" in headers: - body["model"] = deployment - try: - response = requests.post(url, headers=headers, json=body, timeout=timeout) - except requests.RequestException as error: - # requests embeds the URL in its messages; report only the error type. - raise RuntimeError( - f"Could not reach the SandboxAQ deployment ({type(error).__name__})" - ) from None - if not response.ok: - detail = response.text[:500] if response.text else "" - raise RuntimeError( - f"SandboxAQ deployment returned HTTP {response.status_code} " - f"{response.reason}: {detail}" - ) - payload = response.json() - choices = payload.get("choices") - if not choices: - raise RuntimeError("Foundry response did not contain any choices") - choice = choices[0] or {} - message = choice.get("message") or {} - return { - "model": payload.get("model") or deployment, - "content": message.get("content") or "", - "finish_reason": choice.get("finish_reason"), - "usage": payload.get("usage"), - "raw_response": payload, - } - - -def save_json(data, path): - """Write `data` to `path` as UTF-8 JSON, creating parent directories.""" - parent = os.path.dirname(path) - if parent: - os.makedirs(parent, exist_ok=True) - with open(path, "w", encoding="utf-8") as handle: - json.dump(data, handle, indent=2, ensure_ascii=False) - return path diff --git a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml deleted file mode 100644 index a789fe1..0000000 --- a/agents/sandboxaq/tools/sandboxaq-model/tool.yaml +++ /dev/null @@ -1,37 +0,0 @@ -name: sandboxaq-model -description: > - Invokes a subscribed SandboxAQ model through its Azure AI Foundry - OpenAI-compatible chat-completions endpoint. -version: 1.0.0 -license: MIT -category: "Model Inference" -infra: - - name: worker - infra_type: container - image: - acr: "{name}.azurecr.io/sandboxaq-model:latest" - compute: - min_resources: - cpu: 1 - ram: 4Gi - storage: 8Gi - gpu: 0 - max_resources: - cpu: 2 - ram: 8Gi - storage: 32Gi - gpu: 0 - infiniband: false - recommended_sku: - - Standard_D4s_v6 - pool_type: static - pool_size: 1 -code_environments: - - language: python3 - command: "python3 -u \"/{{scriptName}}\"" - description: > - Python code environment on the SandboxAQ model container image. Scripts - import `sandboxaq_utils` to reach the configured Azure AI Foundry - deployment; the endpoint, deployment name, and credential are supplied - as runtime environment variables. - infra_node: worker