Somnia 백서

Somnia

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 54쪽 중 51쪽

testnet.agents.somnia.host'; const platform = '0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776'; const requestId = '<your-request-id>'; // returned by platform.createRequest // Step 1 — manifest const manifest = await (await fetch( `${baseUrl}/agent-receipts?contractAddress=${platform}&requestId=${requestId}` )).json(); // → { contractAddress, requestId, receipts: [<gcs url>, ...], count } // Step 2 — per-validator receipts const receipts = await Promise.all( manifest.receipts.map(url => fetch(url).then(r => r.json())) ); console.log(`${receipts.length} validator receipts for request ${requestId}`); ``` Both query parameters are required. Omitting `contractAddress` returns: ```json HTTP 400 { "error": "Missing contractAddress query parameter" } ``` ### Quick triage with `type=minimal` For lightweight triage — "did this resolve, did the subcommittee agree, what was the error?" — the receipts service also exposes a `type=minimal` mode that returns all per-validator receipt JSON **inline in a single call**, with no second-step GCS fetch needed. ``` GET {baseUrl}/agent-receipts?contractAddress=<platform>&requestId=<id>&type=minimal ``` The minimal response: * Inlines every validator's receipt JSON in `receipts[]` (objects, not GCS URLs). * Caps long string fields (HTML page text, model output, raw responses) at 1024 characters with a `<truncated, fetch the receipt URL for complete contents>` marker. * Adds derived `consensusType`, `requestDetails`, and `count` at the top level for convenience. Use it for triage and dashboards. If a field you need was truncated, fall back to the default (URL manifest) mode and fetch the receipt's GCS URL directly for the full content. ## Receipt Structure ### Top-level fields A single per-validator receipt looks like this: ```json { "requestId": "<requestId>", "agentId": "<agentId>", "contractAddress": "<platform address>", "agentRunnerAddress": "<validator address>", "consensusType": 0, "requestDetails": { "requester": "<caller address>", "callbackAddress": "<callback address>", "callbackSelector": "0x........", "threshold": 2, "subcommitteeSize": 3, "timeoutSeconds": 300 }, "status": "success", "startedAt": "2026-01-15T10:30:00.000Z", "completedAt": "2026-01-15T10:30:09.800Z", "elapsedMs": 9800, "agentImageUri": "https://storage.googleapis.com/somnia-agents-artifacts/agents/<agent>/<hash>.tar", "request": "0x...", "response": { "result": "0x..." }, "agentReceipt": { "steps": [ /* see below */ ], "llmUsage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0, "requests": 0, "streamingRequests": 0 }, "bandwidthUsage": { "bytesIn": 0, "bytesOut": 0, "requests": 0 }, "request": "0x...", "result": "0x..." } } ``` | Field | Purpose | | ----------------------------------------------- | ----------------------------------------------------- | | `status` | `success`, `failed`, or `timeout` | | `elapsedMs` | End-to-end duration on this validator | | `agentRunnerAddress` | Which validator produced this receipt | | `agentImageUri` | Pinned artifact bundle the agent ran from (auditable) | | `requestDetails.subcommitteeSize` / `threshold` | Consensus config in force for this request | | `consensusType` | `0` = Majority, `1` = Threshold | | `agentReceipt.steps` | Per-agent execution stages (see below) | | `agentReceipt.llmUsage` | Token counts for cost analysis | | `agentReceipt.bandwidthUsage` | Outbound HTTP volume | Failed requests follow the same envelope but add a top-level `errorMessage` and omit `agentReceipt` and `response`. Always check `status` before reading `agentReceipt`. ### Step names by agent Step names live under `agentReceipt.steps` and **vary by agent type**: | Agent | Step sequence | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | JSON API Request | `request_received → request_decoded → handler_started → handler_completed → response_encoded` | | LLM Inference | `request_received → request_decoded → handler_started → llm_response → handler_completed → response_encoded` | | LLM Parse Website | `request → fetch → convert → extract` | Each step entry includes `name`, `started_at`, `status`, and an `inputs` object (and `output` where applicable). LLM-backed agents may also include auto-injected `reasoning`, `answerable`, and `confidence_score` (0–100) fields in the structured-extraction step. For LLM Inference requests where chain-of-thought was enabled (via the `chainOfThought` argument), the `llm_response` step also surfaces the model's `thinking` output. ## Using Receipts for Debugging ### Tracing failures When a request fails or times out, check `status` and the top-level `errorMessage`. For successes that returned an unexpected result, walk `agentReceipt.steps` — each step's `status` and any embedded output show exactly where execution diverged. For HTTP-based agents (JSON API Request, Parse Website), `bandwidthUsage.requests` is a quick sanity check that an outbound call happened at all. ### Checking LLM reasoning For LLM agents, the `agentReceipt.steps` entry covering the model call carries the inputs and the structured output. When the request opted into chain-of-thought (`chainOfThought = true`), the `thinking` field on the `llm_response` step surfaces the model's intermediate reasoning. The auto-injected `confidence_score` is useful for understanding low-confidence answers. ### Verifying external data (JSON API Request) The `handler_completed` step records the upstream response payload and the value extracted via the selector path — useful for confirming that the JSON shape on the API hasn't changed underneath you. ### Verifying scraped content (LLM Parse Website) Parse Website's `fetch` step records the URL(s) loaded; the `convert` step records the markdown handed to the model; the `extract` step records the structured-output result. If extraction quality is low, the `convert` output usually shows why (collapsed structure, missing sections, dynamic content not rendered). ### Monitoring cost Both `llmUsage` and `bandwidthUsage` are aggregated per validator on every receipt. Summing across requests gives a reliable per-agent cost profile (especially for LLM Inference and Parse Website, where token usage drives the bulk of work). ## Next Steps * [Gas Fees](/agents/invoking-agents/gas-fees) — Understand costs # (Advanced) Custom Consensus By default, Somnia Agents uses **Majority** consensus — the request finalizes when a threshold of validators return byte-identical results. This works well for deterministic agents (math, API lookups with exact values, etc.) but breaks down when each validator is expected to return a *different* value. **Threshold** consensus solves this. Instead of requiring matching results, it finalizes as soon as the threshold number of validators respond successfully — regardless of whether their results agree. Your callback receives *all* individual results, and you aggregate them however you like. ## Consensus Types | Type | Finalization Condition | `responses[]` in Callback | | --------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `Majority` (0) | `threshold` validators return the **same** result | Responds with all responses; at least `threshold` will match on the same `.result` value | | `Threshold` (1) | `threshold` validators return **any** successful result | All responses (check `.status` to filter successful ones) | ## Using Threshold Consensus Call `createAdvancedRequest` instead of `createRequest`: ```solidity platform.createAdvancedRequest{value: deposit}( agentId, address(this), this.handleResponse.selector, payload, 5, // subcommitteeSize 3, // threshold — finalize after 3 of 5 respond ConsensusType.Threshold, // don't require matching results 1 minutes // timeout ); ``` The `threshold` parameter controls how many successful responses are needed before finalization. A typical choice is 3 of 5 — this tolerates up to 2 validator failures while still collecting enough data points for meaningful aggregation. ```solidity // Get the required deposit for a custom subcommittee size uint256 deposit = platform.getAdvancedRequestDeposit(subcommitteeSize); ``` ## Example: Price Oracle (Median) Each validator fetches a price independently. Results may differ slightly. Your contract takes the median to filter outliers. ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; enum ConsensusType { Majority, Threshold } enum ResponseStatus { None, // 0 - Default zero value (uninitialized storage) Pending, // 1 - Awaiting responses Success, // 2 - Consensus reached normally Failed, // 3 - Validators reported failure TimedOut // 4 - Request timed out } struct Response { address validator; bytes result; ResponseStatus status; uint256 receipt; uint256 timestamp; uint256 executionCost; } struct Request { uint256 id; address requester; address callbackAddress; bytes4 callbackSelector; address[] subcommittee; Response[] responses; uint256 responseCount; uint256 failureCount; uint256 threshold; uint256 createdAt; uint256 deadline; ResponseStatus status; ConsensusType consensusType; uint256 remainingBudget; uint256 perAgentBudget; } interface IAgentRequester { function createAdvancedRequest( uint256 agentId, address callbackAddress, bytes4 callbackSelector, bytes calldata payload, uint256 subcommitteeSize, uint256 threshold, ConsensusType consensusType, uint256 timeout ) external payable returns (uint256 requestId); function getAdvancedRequestDeposit(uint256 subcommitteeSize) external view returns (uint256); } interface IPriceAgent { function getPrice(string calldata pair) external returns (uint256); } contract MedianPriceOracle { IAgentRequester public platform; uint256 public constant PRICE_AGENT_ID = 12345; // Replace with actual agent ID from the web app uint256 public constant SUBCOMMITTEE_SIZE = 5; uint256 public constant COST_PER_AGENT = 0.03 ether; // JSON Fetch (see Gas Fees) uint256 public latestPrice; constructor(address _platform) { platform = IAgentRequester(_platform); } function requestPrice(string calldata pair) external payable returns (uint256) { bytes memory payload = abi.encodeWithSelector( IPriceAgent.getPrice.selector, pair ); // Safe deposit: contract floor + per-agent execution reward. uint256 reserve = platform.getAdvancedRequestDeposit(SUBCOMMITTEE_SIZE); uint256 reward = COST_PER_AGENT * SUBCOMMITTEE_SIZE; uint256 deposit = reserve + reward; return platform.createAdvancedRequest{value: deposit}( PRICE_AGENT_ID, address(this), this.handlePrice.selector, payload, SUBCOMMITTEE_SIZE, 3, // Finalize after 3 of 5 respond ConsensusType.Threshold, 1 minutes // Timeout ); } function handlePrice( uint256 requestId, Response[] memory responses, ResponseStatus status, Request memory details ) external { require(msg.sender == address(platform), "Only platform"); if (status != ResponseStatus.Success || responses.length == 0) return; // Decode all prices from successful responses and take the median uint256 count; uint256[] memory prices = new uint256[](responses.length); for (uint256 i = 0; i < responses.length; i++) { if (responses[i].status == ResponseStatus.Success) { prices[count] = abi.decode(responses[i].result, (uint256)); count++; } } // Resize array to actual count assembly { mstore(prices, count) } if (count > 0) latestPrice = _median(prices); } function _median(uint256[] memory values) internal pure returns (uint256) { // Insertion sort (fine for small arrays) for (uint256 i = 1; i < values.length; i++) { uint256 key = values[i]; uint256 j = i; while (j > 0 && values[j - 1] > key) { values[j] = values[j - 1]; j--; } values[j] = key; } return values[values.length / 2]; } receive() external payable {} } ``` Taking the median of independent price observations naturally filters outliers and manipulation — with 3 of 5 results, the median remains accurate even if one validator returns a garbage value. ## Example: Random Oracle (XOR) Each validator returns a random `uint256`. Your contract XORs all values together to produce a combined random number. This is secure as long as **at least one** responding validator provides a truly random value — even if the others are compromised or predictable, the XOR output remains unpredictable. ```solidity interface IRandomAgent { function random() external returns (uint256); } contract RandomOracle { IAgentRequester public platform; uint256 public constant RANDOM_AGENT_ID = 67890; // Replace with actual agent ID from the web app uint256 public constant SUBCOMMITTEE_SIZE = 5; uint256 public constant COST_PER_AGENT = 0.03 ether; // adjust to match the agent type (see Gas Fees) mapping(uint256 => address) public requesters; event RandomGenerated(uint256 indexed requestId, uint256 value); constructor(address _platform) { platform = IAgentRequester(_platform); } function requestRandom() external payable returns (uint256 requestId) { bytes memory payload = abi.encodeWithSelector( IRandomAgent.random.selector ); // Safe deposit: contract floor + per-agent execution reward. uint256 reserve = platform.getAdvancedRequestDeposit(SUBCOMMITTEE_SIZE); uint256 reward = COST_PER_AGENT * SUBCOMMITTEE_SIZE; uint256 deposit = reserve + reward; requestId = platform.createAdvancedRequest{value: deposit}( RANDOM_AGENT_ID, address(this), this.handleRandom.selector, payload, SUBCOMMITTEE_SIZE, 3, // Finalize after 3 of 5 ConsensusType.Threshold, 1 minutes // Timeout ); requesters[requestId] = msg.sender; } function handleRandom( uint256 requestId, Response[] memory responses, ResponseStatus status, Request memory details ) external { require(msg.sender == address(platform), "Only platform"); if (status != ResponseStatus.Success || responses.length == 0) return; // XOR all successful values — secure if at least one provider is honest uint256 combined = 0; for (uint256 i = 0; i < responses.length; i++) { if (responses[i].status == ResponseStatus.Success) { combined ^= abi.decode(responses[i].result, (uint256)); } } emit RandomGenerated(requestId, combined); } receive() external payable {} } ``` ## When to Use Which | Use Case | Consensus | Why | | ----------------------------------------- | --------- | ----------------------------------------------------------------------- | | Deterministic computation (math, hashing) | Majority | All validators should return the exact same result | | API lookups with exact values | Majority | Same endpoint, same response | | Deterministic LLM inference | Majority | Fixed seed + temperature 0 produces identical outputs across validators | | Price feeds / continuous values | Threshold | Slight differences expected; median filters outliers | | Random number generation | Threshold | Values *must* differ; XOR combines them securely | | Weather data / sensor readings | Threshold | Approximate values; average or median for accuracy | | Non-deterministic LLM inference | Threshold | Natural variance in outputs; application decides how to reconcile | ## Threshold Tips * A threshold of 3 out of 5 is a good default — it tolerates up to 2 failures while still collecting enough data for aggregation. * Higher thresholds (e.g., 4 of 5) give more data points but are more likely to time out if a validator fails. * Avoid setting `threshold = subcommitteeSize` — a single validator failure would prevent finalization entirely. * The `responses[]` array in your callback contains all validator responses — check each `responses[i].status` to filter for successful ones. * `getAdvancedRequestDeposit(subSize)` returns the **operations-reserve floor** (`minPerAgentDeposit × subSize`). That's the minimum `msg.value` the contract will accept, and it only covers gas/callback/upkeep — the agent pot is `msg.value − floor`. Deposit more than the floor to actually pay runners for execution work. See [Gas Fees](/agents/invoking-agents/gas-fees) for the full fund-flow model. * Unused funds are rebated to the requester after finalisation. ## Next Steps * [Invoking from Solidity](/agents/invoking-agents/from-solidity) — Basic request/callback pattern * [Gas Fees](/agents/invoking-agents/gas-fees) — Understand costs and deposits # Gas Fees Invoking Somnia Agents requires payment in **SOMI** (mainnet) or **STT** (testnet). This page explains how the deposit you send is split, where every wei ends up, and what to send for `msg.value`. Amounts in the tables below are quoted in whole tokens — read them as SOMI on mainnet or STT on testnet; the split and rebate logic is identical on both networks. ## TL;DR * `getRequestDeposit()` returns the **operations-reserve floor** (gas refunds, callback gas, finalisation overhead). It is **not** the deposit you should send in practice — pay it only and your request will time out. * Send `msg.value = floor + (per_agent_price × subcommitteeSize)`, where `per_agent_price` matches the agent type (table below). The contract takes no fee; whatever isn't spent on operations or paid to agents is rebated. * Today, runners have fixed per-type prices baked into their software (see [Current Per-Agent Prices](#current-per-agent-prices)). If you don't pay at least `subcommitteeSize × per_agent_price` on top of the floor, runners will skip your request and it will time out. ## Two-Pot Model When you call `createRequest{value: deposit}`, the contract immediately splits the deposit into two virtual pots — both are held in the same `remainingBudget`, but they have separate purposes: | Pot | Size | Funds | | ---------------------- | --------------------------------------- | ----------------------------------------------------------------------------------------------------- | | **Operations reserve** | `minPerAgentDeposit × subcommitteeSize` | Per-runner gas refunds, callback gas, finalisation overhead, keeper refunds for timed-out requests | | **Agent reward pot** | `msg.value − reserve` | Median execution-cost payment to every elected subcommittee member, routed via the committee contract | The reserve is the floor the contract enforces: ```solidity require(msg.value >= minPerAgentDeposit * subcommitteeSize, InsufficientDeposit(...)); ``` If you deposit *exactly* the minimum, `perAgentBudget = 0` — and since agents watching `RequestCreated` see that cap up front, rational runners will skip the request entirely. The contract won't reject your call, but the request will almost certainly sit idle and time out. Deposit **more than** the floor if you want agents to actually do the work. The per-agent share of the reward pot is computed once at request creation, stored on the `Request`, and emitted in `RequestCreated`: ```solidity perAgentBudget = (msg.value − minPerAgentDeposit × subcommitteeSize) / subcommitteeSize ``` `perAgentBudget` is the cap on what any single runner can claim — a runner's self-reported `executionCost` is clamped to `perAgentBudget` at submission. ## Current Per-Agent Prices Runners today use fixed per-type prices in their software (no on-chain price discovery yet — that will change). If your `perAgentBudget` is below the price for the agent type you're invoking, runners will see the `RequestCreated` event, decide it's not worth their compute, and skip the request — it will sit idle and time out. | Agent Type | Per-Agent Price | Notes | | ------------------------------------------- | --------------- | ---------------------------------------------------- | | **JSON API Request** (`json-fetch`) | **0.03 SOMI** | Cheapest — single HTTP call, minimal compute | | **LLM Inference** (`llm-inference`) | **0.07 SOMI** | GPU-backed model, dominant cost is token throughput | | **LLM Parse Website** (`llm-parse-website`) |
프로젝트로 돌아가기