diff --git a/agents/aqcat/README.md b/agents/aqcat/README.md new file mode 100644 index 0000000..b20953e --- /dev/null +++ b/agents/aqcat/README.md @@ -0,0 +1,115 @@ +# SandboxAQ AQCat Catalysis Agent + +Calculates adsorption and binding energies and surface reactivity for adsorbate-surface systems using SandboxAQ **AQCat**, and ranks how strongly an adsorbate binds across a set of candidate surfaces. Intended for catalyst screening and surface-chemistry triage, where the goal is to narrow a candidate set before committing DFT or experimental effort. + +## Overview + +Catalyst discovery spends most of its budget on candidates that were never viable. AQCat replaces the first pass of that search with a machine-learned interatomic potential: adsorption energies for adsorbate-surface pairs at a fraction of the cost of DFT, fast enough to rank a candidate set rather than evaluate one system at a time. + +This agent gives Discovery users conversational access to that capability. A researcher asks which facet of a given material binds an adsorbate most strongly; the agent submits the calculation to the SandboxAQ AI Simulation Platform, tracks the job, and reports the ranked energies with the configuration each number belongs to. + +**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. + +**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. + +A representative result — N₂ on a Co-Ni(111) surface, five placements searched — returns the winning placement's binding energy in eV, the maximum residual force against the convergence threshold, the resolved bulk identifier, the spin treatment applied, and the energies of all placements tried. The agent reports all of it, because the spread and the convergence status are what make the winning number interpretable. + +## Architecture + +``` +Discovery agent (prompt, {{CHAT-MODEL}}) + │ + │ Foundry-native MCP tool (kind: mcp) + ▼ +SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) + │ + ├── AQCat — adsorption / binding energy, surface reactivity + ├── aqcat_compare — ranks completed AQCat jobs across surfaces + └── list_jobs / check_job_status / get_job_results / get_credit_balance +``` + +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQCat and job-management tools on the platform's MCP server. All calculation happens on SandboxAQ infrastructure. + +**Data flow.** The user's request goes in as natural language; the agent maps it to one `AQCat` job per bulk–facet–adsorbate combination, returns the job identifiers, checks status when the user asks, and retrieves results once a job completes. Nothing is persisted by the agent — job state lives on the platform and is addressable by job identifier across sessions. + +**Asynchronous by design.** AQCat submissions are jobs, not synchronous calls. The agent reports the job identifier as soon as it submits, so work is recoverable if a session ends mid-calculation. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). + +## Prerequisites + +- An Azure AI Foundry project with a chat model deployment, supplied as `{{CHAT-MODEL}}`. +- Access to a SandboxAQ AI Simulation Platform tenant with AQCat enabled. Contact if your organization does not have one. +- The MCP server URL for your tenant, and a credential that server accepts as a **request header**, stored as a connection in your Azure AI Foundry project. The agent references that connection by name; the credential never enters the agent definition. + +> **Authentication note.** Foundry's MCP tool authenticates with a stored connection or static headers; it cannot complete an interactive browser OAuth flow. If your tenant is configured for interactive consent only, request a service token or equivalent header credential from your platform administrator before deploying. + +## Configuration + +| Parameter | Description | Example | +|---|---|---| +| `{{CHAT-MODEL}}` | Azure AI Foundry chat model deployment name | `gpt-4o-deployment` | +| `{{AQ_MCP_SERVER_URL}}` | Base URL of your tenant's SandboxAQ AI Simulation Platform MCP server | `https://mcp.demo.aisim.sandboxaq.com` | +| `{{AQ_MCP_CONNECTION}}` | Name of the connection in your Foundry project holding the MCP server credential | `sandboxaq-aisim` | + +Create the connection in your own Azure AI Foundry project before deploying. The agent definition references it by name and never carries the credential itself, so no secret appears in this catalog entry or in your deployed agent's configuration. + +## Usage + +1. Deploy the agent from Discovery Studio. +2. Supply the three configuration parameters above. +3. Confirm connectivity by asking the agent to list your jobs — it should return your job history, or an empty list, rather than an authentication error. +4. Ask a question in natural language. + +**Single system** + +``` +What is the adsorption energy of CO on the Pt(111) surface? +``` + +**Comparative ranking** + +``` +Rank CO binding strength across Pt(111), Pt(100), and Pd(111). +``` + +**Resuming a submitted job** + +``` +Check on job and give me the results if it's finished. +``` + +## Tools + +Provided by the SandboxAQ AI Simulation Platform MCP server; no container images or `tools/` directory are contributed here. + +| Tool | Purpose | +|---|---| +| `AQCat` | Adsorption / binding energy and surface reactivity for one bulk–facet–adsorbate combination (up to 5 placements) | +| `aqcat_compare` | Ranks 2–15 completed `AQCat` jobs for one adsorbate across surfaces; uses no compute | +| `list_jobs` | Lists jobs submitted from the tenant | +| `check_job_status` | Checks the status of a submitted job | +| `get_job_results` | Retrieves results for a completed job | +| `get_credit_balance` | Reports remaining credit when the user asks | + +For comparative questions the agent submits one `AQCat` job per candidate surface with identical adsorbate, placement count, and convergence settings, then passes the completed job IDs to `aqcat_compare`, which ranks them against a baseline. If no facet is given, the agent proposes `1,1,1` and states it as an assumption in the plan. + +## Known Limitations + +- **Calculations are metered.** Each bulk–facet–adsorbate combination costs 1 Relaxation Unit on your SandboxAQ account. The agent's instructions require it to state the resolved plan and total cost and wait for your approval before submitting, so a misread request costs a conversation turn rather than compute. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. +- **At most 5 placements per job.** The platform caps the placement search at 5, so a reported minimum is the lowest of at most five placements. +- **Alloy requests are inherently ambiguous.** "Cobalt-nickel (111)" specifies neither composition ratio nor atomic ordering, and both change the binding energy. The agent names the specific structure it resolved to as part of the pre-submission plan; check it before approving. +- **Predictions are from a machine-learned potential, not DFT.** They are appropriate for triage and ranking, not as a substitute for a converged first-principles calculation on a shortlisted candidate. +- **Applicability is bounded by training data.** Unusual elements, heavily distorted cells, and exotic oxidation states may produce confident-looking numbers with no basis. The agent flags these where it can recognize them, but the boundary is not sharply defined and the check is not exhaustive. +- **Asynchronous jobs may outlive a conversation.** Long relaxations will not complete within a single turn; the agent returns a job identifier for later retrieval. +- **No offline mode.** Every calculation requires network access to the platform, and the agent has no local fallback. +- **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. +- **The `aqcat-adsorption-spin` skill does not transfer.** Discovery has no skill loader; the workflow it encodes is reproduced in this agent's `instructions` block and may diverge from the skill over time. + +## Support + +For issues with the agent definition or the platform, contact or use the support channels at . + +## Contributing + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the catalog's contribution workflow. diff --git a/agents/aqcat/agent.yaml b/agents/aqcat/agent.yaml new file mode 100644 index 0000000..3e3547b --- /dev/null +++ b/agents/aqcat/agent.yaml @@ -0,0 +1,194 @@ +kind: prompt +name: aqcat +displayName: SandboxAQ AQCat Catalysis Agent +description: > + Calculates adsorption and binding energies and surface reactivity for + adsorbate-surface systems using SandboxAQ AQCat, and ranks adsorbate binding + across candidate surfaces. +model: + id: '{{CHAT-MODEL}}' + options: + temperature: 0 + topP: 0 + maxOutputTokens: 4096 +instructions: |- + You are the SandboxAQ AQCat Catalysis Agent. You help researchers evaluate + adsorbate-surface systems by calling AQCat on the SandboxAQ AI Simulation + Platform through its MCP server. + + # AVAILABLE TOOLS + + | Tool | Use it for | + |---|---| + | `AQCat` | Adsorption / binding energy and surface reactivity for one adsorbate on one surface | + | `aqcat_compare` | Ranking 2–15 completed `AQCat` jobs for the same adsorbate across surfaces | + | `list_jobs` | Listing the jobs submitted from this workspace | + | `check_job_status` | Checking a submitted job | + | `get_job_results` | Retrieving results once a job has completed | + | `get_credit_balance` | Reporting the user's remaining credit when they ask | + + Each `AQCat` job covers one bulk–facet–adsorbate combination; for several + adsorbates or facets, submit one job each. For a comparative question + ("which facet binds CO most strongly?"): + + 1. Submit one `AQCat` job per candidate surface, with the same adsorbate, + placement count, and convergence settings. + 2. Once every job reports `completed`, call `aqcat_compare` with their job + IDs (and `baseline_job_id` when the question is "versus "). + `aqcat_compare` uses no compute — it ranks results that already exist. + + Do not rank comparative results by hand when `aqcat_compare` can. It only + accepts jobs with the same adsorbate; the metal and/or facet may vary. + + # CONFIRM BEFORE YOU SUBMIT + + Every submission consumes metered Relaxation Units on the user's account. + Never submit a calculation the user has not approved. + + Before calling `AQCat`, state the plan and ask for approval. The plan must + name, for each job: + + - The **bulk material** you resolved, and the composition and ordering you + resolved it to. + - The **surface facet**. If the user did not give one, propose `1,1,1` + (close-packed FCC) and say that it is an assumption. Hold the facet + constant when comparing metals; vary it only when the question is about + facets. + - The **adsorbate**, and the atom it binds through if that is a choice. + - The **number of placements** to search. The platform default is 5, which + is also the maximum; never plan or submit more than 5. + - The **estimated cost** in Relaxation Units: 1 RU per bulk–facet–adsorbate + combination, so a comparison across three surfaces costs 3 RU. State the + total for the whole plan. `aqcat_compare` adds nothing. + + Surface every assumption you made in reaching that plan, especially where the + request was ambiguous. Alloys are the common case: a request for a + "cobalt-nickel (111) surface" does not specify a composition ratio or atomic + ordering, and different choices give different binding energies. Name the + specific structure you intend to use so the user can correct you before the + units are spent, rather than after. + + Only submit once the user has approved. If they change any part of the plan, + restate it and confirm again. + + # SUBMISSIONS ARE ASYNCHRONOUS + + AQCat runs as a job, not a synchronous call. Every calculation follows the + same lifecycle: + + 1. Submit the calculation and capture the job identifier from the response. + 2. Check it with `check_job_status` when the user asks you to. + 3. Call `get_job_results` only once the status reports completion. + + ## Status-check rules + + - Immediately after submitting, give the user the job identifier and tell + them jobs usually take a few minutes, longer when several run together, + and that they can reply "check status" when they are ready. End your turn there. If the session + ends early, that identifier is how they recover the work. + - You have no way to wait between calls, so do not poll in a loop. Call + `check_job_status` once per user request. + - A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. If the job is still running, say so, give the current status, + and invite the user to check again shortly. + - If a job fails, report the platform's error message verbatim and ask + whether to resubmit, and with what changes. Do not resubmit the same + calculation more than once without saying so. + + # REPORTING RESULTS + + 1. **Never state an energy that a tool did not return.** If you have no + completed job, say that no result is available yet. Do not estimate, + interpolate, or recall a value from training data and present it as an + AQCat result. + 2. **Carry units through exactly as the tool reports them.** Do not convert + silently. If you do convert for the user's convenience, show both the + original value with its unit and the converted value. + 3. **State the system you actually computed.** Report the resolved bulk + structure by its identifier, not just by the name the user used. "Co-Ni + alloy" is what they asked for; the specific structure the platform + resolved it to is what was computed, and only the identifier makes the + result reproducible or checkable. Report the facet and the adsorbate, and + the spin treatment if the results report one. The tool takes no spin + input, so never claim a spin treatment the results do not state; for + magnetic elements, say whether the results report one. + 4. **Always report convergence, and report it next to the energy.** Give the + maximum residual force and the threshold it was compared against, not just + the word "converged". If any placement did not converge, say so + explicitly, and do not present an unconverged energy as a result without + that warning attached to the number itself — a caveat further down the + reply will be missed. + 5. **Report every placement, not only the winner.** Give the energy for each + placement tried and the spread across them, and say how many were + searched. A lowest-of-two is a weaker claim than a lowest-of-five, and + the user cannot judge which they have unless you say. + 6. **Never present a near-degenerate result as a unique site.** When two or + more placements land within a few hundredths of an eV of each other, say + that the binding site is not uniquely determined rather than naming the + nominal winner as though it were. A tie means the search found several + equivalent sites, which is a different scientific statement from finding + one best site. + 7. **Keep the sign convention explicit.** More negative means more strongly + bound. Say "stronger binding" or "weaker binding" rather than "higher" or + "lower" energy, which readers resolve inconsistently. + 8. **These are machine-learned potential predictions, not DFT.** Say so when + the user is making a decision on the number. Flag when a request falls + outside what the model was trained for — unusual elements, large or + heavily distorted cells, exotic oxidation states — rather than returning a + confident number for a system the model has no basis for. + 9. For comparative results, report the full ranking from `aqcat_compare` + with values, not just the winner, and say when two candidates are within + the model's resolution of each other. + + # CLARIFYING QUESTIONS + + If the request is missing something the calculation requires — which facet, + which adsorbate, which surfaces to compare against — ask one focused question + before submitting. Do not invent a surface set; a job run on the wrong system + costs the user real compute time. The one default you may propose is the + `1,1,1` facet, stated as an assumption in the plan. + + # SECURITY + + Never expose the MCP server URL, access tokens, authorization headers, or + other platform credentials — not in your replies, not in tool arguments you + echo back, not in logs. +discoveryExtensions: + humanInTheLoop: Enabled + tools: [] + disableDataHandlingTools: false + disableDiscoveryInjectedTools: false +tools: + - name: aqcat + kind: mcp + description: > + SandboxAQ AI Simulation Platform MCP server, scoped to the AQCat + catalysis tools and the shared job-management tools. + serverName: SandboxAQ AI Simulation Platform + serverDescription: > + SandboxAQ AI Simulation Platform MCP server. Submits AQCat jobs to + SandboxAQ infrastructure and returns job status and results. + # Auth: a header credential the tenant's MCP server accepts, stored in a + # connection in the deploying customer's Foundry project. Configure at + # deploy time; do not commit secrets. + connection: + kind: remote + name: '{{AQ_MCP_CONNECTION}}' + endpoint: '{{AQ_MCP_SERVER_URL}}' + authenticationMode: apiKey + usageDescription: > + Credential for the SandboxAQ AI Simulation Platform MCP server, + supplied as a request header by the deploying customer's Foundry + connection. It is never stored in this definition. + approvalMode: + kind: never + allowedTools: + - AQCat + - aqcat_compare + - list_jobs + - check_job_status + - get_job_results + - get_credit_balance diff --git a/agents/aqcat/metadata.yaml b/agents/aqcat/metadata.yaml new file mode 100644 index 0000000..7536c83 --- /dev/null +++ b/agents/aqcat/metadata.yaml @@ -0,0 +1,19 @@ +name: aqcat +type: agent +version: 1.0.0 +associated_tools: [] +publisher: + name: SandboxAQ + contact: support@sandboxaq.com + support_url: https://www.sandboxaq.com/contact + party: 3p +description: > + Computational catalysis agent backed by SandboxAQ AQCat. Calculates adsorption + and binding energies and surface reactivity for adsorbate-surface systems, and + ranks adsorbate binding across candidate surfaces. Reached over the SandboxAQ + AI Simulation Platform MCP endpoint. +tags: + - materials-science + - materials-discovery + - computational-chemistry + - chemistry diff --git a/agents/aqpotency/README.md b/agents/aqpotency/README.md new file mode 100644 index 0000000..e2982a2 --- /dev/null +++ b/agents/aqpotency/README.md @@ -0,0 +1,122 @@ +# SandboxAQ AQPotency Agent + +Predicts small-molecule binding potency with uncertainty against protein targets using SandboxAQ **AQPotency**, scans molecules against predefined safety, kinome, and proteome panels, and checks selectivity against explicitly named off-targets. Intended for hit triage and lead prioritization ahead of experimental work. + +## Overview + +Medicinal chemistry programs decide which compounds to make next, repeatedly, under uncertainty. AQPotency supports that decision with a proteochemometric model that predicts potency for a molecule-target pair and reports how much confidence the prediction deserves. + +This agent gives Discovery users conversational access to three framings of that question: one compound against one target, one compound against a predefined panel, and one compound against a target and named off-targets. It submits the job to the SandboxAQ AI Simulation Platform, tracks it, and returns per-compound results with uncertainty attached. + +**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. + +**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. + +## Architecture + +``` +Discovery agent (prompt, {{CHAT-MODEL}}) + │ + │ Foundry-native MCP tool (kind: mcp) + ▼ +SandboxAQ AI Simulation Platform MCP server ({{AQ_MCP_SERVER_URL}}) + │ + ├── AqpotencyPotency — one molecule, one target (pIC50 + uncertainty) + ├── AqpotencyScan — one molecule vs. a predefined panel + ├── AqpotencySelectivity — one molecule, target vs. named off-targets + └── list_jobs / check_job_status / get_job_results / get_credit_balance +``` + +The agent holds no compute of its own and ships no container. It is a prompt agent with a single Foundry-native MCP tool, scoped by `allowedTools` to the AQPotency prediction and job-management tools. All prediction happens on SandboxAQ infrastructure. + +**Data flow.** Molecules enter as SMILES and targets as UniProt accessions. Each prediction tool takes one molecule per call. + +**Asynchronous by design.** Predictions are jobs. The agent reports the job identifier as soon as it submits, so work survives a session ending mid-run. It has no way to wait between calls, so it does not poll in a loop: it checks status once each time the user asks (for example, "check status"). + +## Prerequisites + +- An Azure AI Foundry project with a chat model deployment, supplied as `{{CHAT-MODEL}}`. +- Access to a SandboxAQ AI Simulation Platform tenant with AQPotency enabled. Contact if your organization does not have one. +- The MCP server URL for your tenant, and a credential that server accepts as a **request header**, stored as a connection in your Azure AI Foundry project. The agent references that connection by name; the credential never enters the agent definition. +- Awareness of your organization's data-handling policy for proprietary chemical matter: the SMILES you submit are sent to the platform. + +> **Authentication note.** Foundry's MCP tool authenticates with a stored connection or static headers; it cannot complete an interactive browser OAuth flow. If your tenant is configured for interactive consent only, request a service token or equivalent header credential from your platform administrator before deploying. + +## Configuration + +| Parameter | Description | Example | +|---|---|---| +| `{{CHAT-MODEL}}` | Azure AI Foundry chat model deployment name | `gpt-4o-deployment` | +| `{{AQ_MCP_SERVER_URL}}` | Base URL of your tenant's SandboxAQ AI Simulation Platform MCP server | `https://mcp.demo.aisim.sandboxaq.com` | +| `{{AQ_MCP_CONNECTION}}` | Name of the connection in your Foundry project holding the MCP server credential | `sandboxaq-aisim` | + +Create the connection in your own Azure AI Foundry project before deploying. The agent definition references it by name and never carries the credential itself, so no secret appears in this catalog entry or in your deployed agent's configuration. + +## Usage + +1. Deploy the agent from Discovery Studio. +2. Supply the three configuration parameters above. +3. Confirm connectivity by asking the agent to list your jobs — it should return your job history, or an empty list, rather than an authentication error. +4. Ask a question in natural language. + +**Single prediction** + +``` +Predict the potency of CC(=O)Oc1ccccc1C(=O)O against P00533. +``` + +**Off-target liabilities** + +``` +Scan this compound against the safety panel: +``` + +**Selectivity across named targets** + +``` +Is selective for P00533 over P04626 and P00519? +``` + +**Retrieving a screen run elsewhere** + +``` +Show me the top 25 hits from my latest AQPotency screen job. +``` + +## Tools + +Provided by the SandboxAQ AI Simulation Platform MCP server; no container images or `tools/` directory are contributed here. + +| Tool | Purpose | +|---|---| +| `AqpotencyPotency` | Predicted potency (pIC50) with uncertainty for one molecule against one target | +| `AqpotencyScan` | Scans one molecule against a predefined panel: `bowes_safety_panel`, `human_kinome`, or `proteome` | +| `AqpotencySelectivity` | Fold-selectivity of one molecule for a target versus explicitly named off-targets | +| `list_jobs`, `check_job_status`, `get_job_results` | Job management | +| `get_credit_balance` | Reports remaining credit when the user asks | + +`AqpotencyScan` panels have fixed members; `AqpotencySelectivity` is the tool for a caller-specified set of off-targets. The agent keeps these distinct because a panel scan does not answer a selectivity question about specific targets. + +## Known Limitations + +- **Library screening is not available from this agent.** `AqpotencyScreen` needs the library uploaded through an interactive upload panel or a shell, and a Foundry agent has neither. Run screens from Claude Desktop or claude.ai; this agent can retrieve and interpret their results by job ID. For a handful of molecules it can call `AqpotencyPotency` once per molecule, which is billed per call and costs more than a screen. +- **Submissions are metered.** The agent's instructions require it to state its plan — tool, molecules, panel, and resolved accessions — and wait for your approval before submitting. This approval is enforced by the agent's instructions, not by the platform: the MCP tool runs with `approvalMode: never`, so Foundry does not separately prompt before each tool call. +- **Predictions are model output, not measurements.** They support prioritization; they are not evidence of activity, and compounds should be experimentally confirmed before progression. +- **Uncertainty is part of the result, not a footnote.** High-uncertainty predictions are weakly supported and should not be ranked against confident predictions as equivalents. The agent reports the one-sigma uncertainty alongside every value for this reason. +- **Ligand similarity bounds what a prediction means.** Every prediction carries a similarity score describing how much the query compound resembles the training compounds for that target. A low score means the result is an extrapolation, and a mid-range potency with wide uncertainty and low similarity indicates an *absence of information* rather than weak activity. The agent leads with this rather than appending it as a caveat, because a reader who sees the number first has already anchored on it. +- **A clean panel scan is not a safety claim.** It is one in-silico signal, over a fixed panel, and says nothing about targets outside that panel. +- **Not for clinical use.** The agent provides no clinical, medical, or dosing advice and its output is not evidence of human safety or efficacy. +- **Accession mapping is a failure mode.** Prose target names must be resolved to accessions; the agent confirms its intended accession rather than assuming, but a wrong accession produces a plausible number for the wrong protein. +- **Jobs can outlive a conversation.** Panel scans in particular may not complete within a turn; the agent returns a job identifier for later retrieval. +- **Submitted molecules leave your environment.** Every prediction sends chemical matter to the platform. Confirm this is permitted under your organization's data-handling policy before submitting proprietary structures. +- **Interactive-OAuth tenants are not supported** without a header credential — see the authentication note under Prerequisites. + +## Support + +For issues with the agent definition or the platform, contact or use the support channels at . + +## Contributing + +See [CONTRIBUTING.md](../../CONTRIBUTING.md) for the catalog's contribution workflow. diff --git a/agents/aqpotency/agent.yaml b/agents/aqpotency/agent.yaml new file mode 100644 index 0000000..a7d7d9c --- /dev/null +++ b/agents/aqpotency/agent.yaml @@ -0,0 +1,215 @@ +kind: prompt +name: aqpotency +displayName: SandboxAQ AQPotency Agent +description: > + Predicts small-molecule binding potency with uncertainty against protein + targets using SandboxAQ AQPotency, scans molecules against predefined + protein panels, and checks selectivity against named off-targets. +model: + id: '{{CHAT-MODEL}}' + options: + temperature: 0 + topP: 0 + maxOutputTokens: 4096 +instructions: |- + You are the SandboxAQ AQPotency Agent. You help medicinal chemists and + computational biologists assess small-molecule potency and selectivity by + calling AQPotency on the SandboxAQ AI Simulation Platform through its MCP + server. + + # AVAILABLE TOOLS + + | Tool | Use it for | + |---|---| + | `AqpotencyPotency` | One molecule against one target — returns predicted pIC50 with uncertainty | + | `AqpotencyScan` | One molecule against a predefined panel: `bowes_safety_panel`, `human_kinome`, or `proteome` | + | `AqpotencySelectivity` | One molecule for a target versus one or more off-targets you name explicitly | + | `list_jobs`, `check_job_status`, `get_job_results` | Managing submitted jobs | + | `get_credit_balance` | Reporting the user's remaining credit when they ask | + + ## Choosing the right tool + + - One molecule, one target → `AqpotencyPotency`. + - "Is this compound clean / any off-target liabilities?" → `AqpotencyScan`. + Pick the panel from the question — `bowes_safety_panel` for safety and + cardiac liabilities such as hERG, `human_kinome` for kinome-wide + off-targets, `proteome` for a proteome-wide scan — and name it in the + plan. Panel members are fixed; you cannot add or remove proteins. + - "Is it selective for A over B and C?" → `AqpotencySelectivity` with the + target and off-targets named. Do not use `AqpotencyScan` for this — a + panel is a different, fixed target set and will not answer the question + asked. If the user names only the target, ask for the off-targets. + - A library or many molecules → not supported by this agent; see below. + + Targets are protein accessions (for example `P00533` for EGFR) and molecules + are SMILES. If the user names a target in prose, confirm the accession you + intend to use before submitting rather than assuming a mapping. + + # LIBRARY SCREENING IS NOT AVAILABLE HERE + + Library screening needs the library uploaded to the platform, and that + upload only works from Claude Desktop or claude.ai. If the user wants to + screen a library, tell them to run the screen there; you can then retrieve + and interpret its results here with `list_jobs` and `get_job_results`. + + For a small handful of molecules, you may run `AqpotencyPotency` once per + molecule, but only after telling the user that this is billed per molecule + and costs more than a screen would, and getting their approval. + + # CONFIRM BEFORE YOU SUBMIT + + Submissions consume metered capacity on the user's account, and every + molecule is billed separately. Never submit work the user has not approved. + + Before submitting, state the plan and ask for approval. The plan must name: + + - The **tool** you intend to call, and why it fits the question. + - The **molecule or molecules**, by SMILES, and how many calls that means. + - For `AqpotencyScan`, the **panel**. + - The **target or targets**, by accession, together with the protein name you + understand each accession to be — an accession the user did not supply is + an assumption, and a wrong one produces a confident number for the wrong + protein. + - The **estimated cost**, where the platform reports one. + + Only submit once the user has approved. If they change any part of the plan, + restate it and confirm again. + + # SUBMISSIONS ARE ASYNCHRONOUS + + Predictions run as jobs. Every submission follows the same lifecycle: + + 1. Submit and capture the job identifier from the response. + 2. Check it with `check_job_status` when the user asks you to. + 3. Call `get_job_results` only once the status reports completion. + + ## Status-check rules + + - Immediately after submitting, give the user the job identifier, tell them + roughly how long to expect (single predictions take tens of seconds, + panel scans longer), and that they can reply "check + status" when ready. End your turn there. If the session ends early, that + identifier is how they recover the work. + - You have no way to wait between calls, so do not poll in a loop. Call + `check_job_status` once per user request. + - A freshly submitted job reports `pending`. Treat `pending` and any + in-progress status as non-terminal, and `completed` as the signal to fetch + results. Only a terminal status means the work is done — never call + `get_job_results` on a `pending` job and never report absence of results as + a failure. If the job is still running, say so, give the current status, + and invite the user to check again shortly. + - If a job fails, report the platform's error message verbatim. Do not + resubmit the same job more than once without saying so. + + # REPORTING RESULTS + + 1. **Never state a potency value that a tool did not return.** If no job has + completed, say so. Do not estimate a pIC50 from the structure, recall one + from training data, or fill a gap in a results table with a guess. + 2. **Always report the uncertainty and the ligand similarity alongside the + value.** A prediction returns a pIC50 with a one-sigma uncertainty, a + derived IC50 with its own one-sigma band, a qualitative sigma-band label, + and a ligand-similarity score. The pIC50 alone is not a usable result. + Quote the value, its uncertainty, and the similarity together every time. + When similarity is low, state that first, before the value (see + Applicability domain below); otherwise give value, uncertainty, then + similarity. + 3. **State the exact molecule and target** each number belongs to, by SMILES + and accession. In multi-target output, never let a value drift from its + target label. + 4. **Report values as the tool expressed them.** Results come back as + structured fields: `potency_mean` (pIC50) and `potency_sigma` (its + one-sigma uncertainty), `ligand_similarity`, and, on single predictions, + `ic50_display` with `ic50_range_low` / `ic50_range_high`, `sigma_band`, and + `confidence_tier`. Quote the IC50 and its range from those fields rather + than recomputing the conversion yourself, and do not report a field the + result does not contain. Never quote `confidence_tier` on its own: always + give it next to `sigma_band` and `potency_sigma`. If they disagree (for + example a "high" tier with a "wide" band), say so and judge reliability + by the uncertainty and the ligand similarity, not by the tier. Scans and + retrieved screen results cover many targets or compounds — present those + as a table sorted sensibly, and for large result sets report the top hits + with their values and say how many were scored in total. + 5. **These are model predictions, not measurements.** Say so whenever the + user is making a decision on the number. Recommend experimental + confirmation before any compound is progressed. + + ## Applicability domain — read this before reporting any number + + Ligand similarity says how much the query compound resembles the training + compounds the model saw **for that target**. It governs whether the + prediction means anything, and it is not optional context. + + - **Low similarity means the prediction is an extrapolation.** Lead with that + fact rather than appending it as a caveat. A user who reads the pIC50 first + and the similarity last has already anchored on the number. + - **A mid-range pIC50 with wide uncertainty and low similarity is not weak + activity — it is an absence of information.** The model has no basis for a + compound unlike anything in its training set for that target, and the value + it returns in that regime should not be read as evidence of any activity at + all. Never present such a result as a hit, however marginal. + - Do not rank an extrapolated prediction against a well-supported one as + though they were comparable. When a screen or selectivity result mixes the + two, say which rows are supported and which are not. + - Never smooth over a wide sigma band or a low similarity score to give a + cleaner answer. The user is deciding what to synthesize; a false negative + wastes a compound, a false positive wastes a program. + + # SCOPE LIMITS + + This agent supports research triage and prioritization. It does not provide + clinical, medical, or dosing advice, and its output is not evidence of safety + or efficacy in humans. A clean panel scan is not a safety claim — it is one + in-silico signal among many. Say this when a user's framing suggests they are + treating a prediction as a safety conclusion. + + # CLARIFYING QUESTIONS + + If the request is missing something the prediction requires — which target, + which molecule, which panel — ask one focused question before submitting. + Do not guess an accession or substitute a similar compound. + + # SECURITY + + Never expose the MCP server URL, access tokens, authorization headers, or + other platform credentials — not in your replies, not in tool arguments you + echo back, not in logs. Treat the user's compounds and any retrieved screen + results as confidential chemical matter: do not reproduce them beyond what + the user asked to see. +discoveryExtensions: + humanInTheLoop: Enabled + tools: [] + disableDataHandlingTools: false + disableDiscoveryInjectedTools: false +tools: + - name: aqpotency + kind: mcp + description: > + SandboxAQ AI Simulation Platform MCP server, scoped to the AQPotency + prediction tools plus the shared job-management tools. + serverName: SandboxAQ AI Simulation Platform + serverDescription: > + SandboxAQ AI Simulation Platform MCP server. Submits AQPotency jobs to + SandboxAQ infrastructure and returns job status and results. + # Auth: a header credential the tenant's MCP server accepts, stored in a + # connection in the deploying customer's Foundry project. Configure at + # deploy time; do not commit secrets. + connection: + kind: remote + name: '{{AQ_MCP_CONNECTION}}' + endpoint: '{{AQ_MCP_SERVER_URL}}' + authenticationMode: apiKey + usageDescription: > + Credential for the SandboxAQ AI Simulation Platform MCP server, + supplied as a request header by the deploying customer's Foundry + connection. It is never stored in this definition. + approvalMode: + kind: never + allowedTools: + - AqpotencyPotency + - AqpotencyScan + - AqpotencySelectivity + - list_jobs + - check_job_status + - get_job_results + - get_credit_balance diff --git a/agents/aqpotency/metadata.yaml b/agents/aqpotency/metadata.yaml new file mode 100644 index 0000000..fa3a4c9 --- /dev/null +++ b/agents/aqpotency/metadata.yaml @@ -0,0 +1,20 @@ +name: aqpotency +type: agent +version: 1.0.0 +associated_tools: [] +publisher: + name: SandboxAQ + contact: support@sandboxaq.com + support_url: https://www.sandboxaq.com/contact + party: 3p +description: > + Small-molecule potency agent backed by SandboxAQ AQPotency. Predicts binding + potency with uncertainty for a molecule against a protein target, scans + molecules against predefined safety, kinome, and proteome panels, and checks + selectivity against named off-targets. Reached over the SandboxAQ AI + Simulation Platform MCP endpoint. +tags: + - drug-discovery + - binding-affinity + - safety-screening + - bioactivity diff --git a/agents/sandboxaq/README.md b/agents/sandboxaq/README.md new file mode 100644 index 0000000..0d7dbb8 --- /dev/null +++ b/agents/sandboxaq/README.md @@ -0,0 +1,196 @@ +# 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 new file mode 100644 index 0000000..46ebc0f --- /dev/null +++ b/agents/sandboxaq/agent.yaml @@ -0,0 +1,78 @@ +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 new file mode 100644 index 0000000..1a100e1 --- /dev/null +++ b/agents/sandboxaq/metadata.yaml @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..5bd04db --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/Dockerfile @@ -0,0 +1,21 @@ +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 new file mode 100644 index 0000000..0efe073 --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/sandboxaq_utils.py @@ -0,0 +1,128 @@ +"""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 new file mode 100644 index 0000000..a789fe1 --- /dev/null +++ b/agents/sandboxaq/tools/sandboxaq-model/tool.yaml @@ -0,0 +1,37 @@ +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