Somnia 백서

Somnia

원문

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

**0.10 SOMI** | LLM inference plus a browser session and page render | To get your request actually executed, the deposit you send must cover the operations reserve **plus** the per-agent price multiplied by the subcommittee size: ``` msg.value ≥ minPerAgentDeposit × subSize ← operations reserve (the contract floor) + per_agent_price × subSize ← what runners actually charge today ``` For the default `subcommitteeSize = 3` and `minPerAgentDeposit = 0.01 SOMI`: | Agent Type | Operations reserve | Agent reward pot | Practical `msg.value` | | ----------------- | ------------------ | -------------------- | --------------------- | | JSON API Request | 0.03 SOMI | 0.03 × 3 = 0.09 SOMI | **0.12 SOMI** | | LLM Inference | 0.03 SOMI | 0.07 × 3 = 0.21 SOMI | **0.24 SOMI** | | LLM Parse Website | 0.03 SOMI | 0.10 × 3 = 0.30 SOMI | **0.33 SOMI** | Sending more than the practical amount is fine — anything that isn't claimed (because runners reported a smaller `executionCost`, or because some weren't elected, etc.) is rebated to you. > **Future:** Per-type fixed pricing is a stop-gap. Runners will eventually price each request based on observed resource consumption (container-seconds, tokens in/out, egress, etc.), and the on-chain median will reflect actual cost rather than a hard-coded constant. ## Flow of Funds {% @mermaid/diagram content="flowchart TD R\["Requester wallet"] -->|"msg.value"| ESC\["AgentRequester (escrow)<br/>remainingBudget = msg.value"] ``` ESC -->|"reserved at create"| RES["Operations reserve<br/>= minPerAgentDeposit × subSize"] ESC -->|"agent pot at create"| POT["Agent reward pot<br/>= msg.value − reserve<br/>(perAgentBudget × subSize)"] RES -->|"per submitResponse"| GR["Runner gas refunds<br/>(direct ETH to msg.sender)"] RES -->|"finalisation"| CB["Callback gas"] RES -->|"upkeep on timeout"| KR["Keeper gas refund"] POT -->|"on consensus,<br/>committee.deposit(...)"| COM["ValidatorCommittee"] COM -->|"validatorOf(member)"| VB["ValidatorBonuses<br/>(SOMI yield to validators)"] RES -.->|"unused → rebate"| REB["Requester (rebate)"] POT -.->|"if no responses or median = 0"| REB classDef pot fill:#1f6feb,stroke:#0969da,color:#fff classDef payee fill:#1a7f37,stroke:#116329,color:#fff classDef rebate fill:#9a6700,stroke:#7d4e00,color:#fff class RES,POT pot class GR,CB,KR,VB payee class REB rebate" %} ``` ## Lifecycle Walkthrough ### 1. Request Creation ```solidity uint256 deposit = somniaAgents.getRequestDeposit(); // operations reserve floor uint256 reward = 0.05 ether; // what you want each agent paid (≤) somniaAgents.createRequest{value: deposit + reward * 3}( // 3 = default subcommittee size agentId, address(this), this.handleResponse.selector, payload ); ``` The contract computes `perAgentBudget = reward` (in this example) and emits `RequestCreated(requestId, agentId, perAgentBudget, payload, subcommittee)`. Off-chain runners watching the event know exactly what their per-agent cap will be. ### 2. Runner Responses Each elected subcommittee member runs the agent off-chain, then calls: ```solidity somniaAgents.submitResponse(requestId, result, receipt, success, executionCost); ``` The runner's reported `executionCost` is capped at `perAgentBudget` and stored on the `Response`. The contract meters the gas this submission consumed and refunds it to the runner from the operations reserve. ### 3. Consensus → Subcommittee Payment When consensus is reached (`Success`) or proven impossible (`Failed`), the contract: 1. Calls your callback (`handleResponse`). 2. Computes `perMember = median(reported executionCosts)`. 3. Calls `committee.deposit{value: perMember × subcommitteeSize}(elected, [perMember, ..., perMember])` — every elected member gets the same amount, regardless of whether they responded in time. Slow geographically-distributed runners that didn't beat consensus to the punch are paid the same as the responders. This intentionally removes any incentive to rush a low-quality response. If the committee call reverts (e.g. a misbehaving validator-bonuses contract), the budget is restored and `CommitteeDepositFailed` is emitted — finalisation can't be DoS'd. If accumulated per-submission gas refunds have eaten into the operations reserve enough that `perMember × subcommitteeSize > remainingBudget`, the contract clamps `perMember` down to `remainingBudget / subcommitteeSize` rather than reverting. With a sensibly-tuned `minPerAgentDeposit` this is rare, but it does mean a request with abnormally expensive submissions can pay runners less than the reported median. ### 4. Rebate Whatever's left in `remainingBudget` (typically the unused portion of the operations reserve) is sent back to the requester: ```solidity function _refundRemaining(Request storage req) internal { uint256 rebate = req.remainingBudget; req.remainingBudget = 0; (bool success, ) = req.requester.call{value: rebate}(""); } ``` ### 5. Timeout Path If the request expires before consensus, anyone can call `upkeepRequests()`: * The keeper is reimbursed from the operations reserve (intrinsic gas amortised across the batch). * **No committee payment is made** — there are no responses to derive a median from. * The agent reward pot is refunded to the requester along with any unused operations reserve. ## Reading the Costs After finalisation, `getRequest(requestId)` returns the full `Request` struct. The relevant fields: | Field | Meaning | | ---------------------------- | ---------------------------------------------------------------------------------------------------------- | | `remainingBudget` | Always 0 on a finalised, settled request (everything has been distributed). | | `perAgentBudget` | The per-agent cap that was set at creation. Multiply by `subcommittee.length` to get the agent reward pot. | | `responses[i].executionCost` | What runner `i` reported (already capped at `perAgentBudget`). | Two events let off-chain consumers reconstruct the full distribution: ```solidity event SubcommitteePaid(uint256 indexed requestId, uint256 totalPaid, uint256 perMember); event NativeTransferFailed(address indexed recipient, uint256 amount); ``` ## What to Send for `msg.value` There are three sensible patterns: **1. Floor only — not useful in practice.** The contract will accept this, but `perAgentBudget = 0` and agents will skip the request; it will time out without a response. ```solidity uint256 deposit = somniaAgents.getRequestDeposit(); somniaAgents.createRequest{value: deposit}(agentId, ..., payload); ``` Listed for completeness. Don't use it except to smoke-test that the request-creation call itself works. **2. Floor + per-agent price (the normal case).** Use the per-type price from the [Current Per-Agent Prices](#current-per-agent-prices) table; runners will only pick up the request if `perAgentBudget` meets it. ```solidity uint256 floor = somniaAgents.getRequestDeposit(); uint256 perAgent = 0.07 ether; // LLM Inference; pick from the table uint256 deposit = floor + perAgent * 3; // 3 = default subcommittee size somniaAgents.createRequest{value: deposit}(agentId, ..., payload); ``` Sending more than the listed price is fine — agents are paid `min(reportedExecutionCost, perAgentBudget)`, so any margin is rebated. **3. Custom subcommittee size:** ```solidity uint256 floor = somniaAgents.getAdvancedRequestDeposit(5); // for subSize = 5 uint256 perAgent = 0.07 ether; uint256 deposit = floor + perAgent * 5; somniaAgents.createAdvancedRequest{value: deposit}( agentId, ..., payload, 5, 3, ConsensusType.Threshold, 1 minutes ); ``` ## Receiving Rebates Make sure your contract can receive native tokens — rebates are pushed automatically on finalisation: ```solidity contract MyContract { receive() external payable {} } ``` If the rebate transfer fails (e.g. recipient out of gas), the contract emits `NativeTransferFailed(recipient, amount)` and the funds remain in the `AgentRequester`. They can be recovered via off-chain coordination with the operator. ## Configuration Reference | Parameter | Default | Notes | | ------------------------- | --------------- | --------------------------------------------------------------------------------------------------- | | `minPerAgentDeposit` | 0.01 SOMI / STT | Operator-configurable. Sets the operations-reserve floor per agent. | | `defaultSubcommitteeSize` | 3 | Operator-configurable. `createRequest` uses this; `createAdvancedRequest` lets you override (≤ 10). | | `defaultThreshold` | 2 | Operator-configurable. | | `defaultTimeout` | 15 minutes | Operator-configurable. | ## Best Practices 1. **Use the deposit helpers as a floor, not the total.** `getRequestDeposit()` returns the operations-reserve floor only. Add a per-agent reward on top. 2. **Implement `receive()`.** Without it, rebate transfers will fail (silent — emitted as `NativeTransferFailed`) and your funds will be stuck. 3. **Watch `RequestCreated.perAgentBudget`.** That's the value runners see and the cap they'll be paid up to. 4. **Don't rely on a specific median policy.** A single dishonest reporter can't manipulate the median, but with only 2 successful responders, the upper-median lands on the higher of the two values. ## Next Steps * [Quickstart](/agents/invoking-agents/quickstart) — Start invoking agents * [Invoking from Solidity](/agents/invoking-agents/from-solidity) — Contract integration guide * [Custom Consensus](/agents/invoking-agents/custom-consensus) — Threshold mode for non-deterministic agents # JSON API Request Fetches JSON data from any public API endpoint and extracts specific values using a selector path. This agent is the fundamental building block for creating on-chain oracles. ## Methods | Function | Inputs | Output | Use Case | | ------------------ | ----------------------- | ---------- | ---------------------------- | | `fetchString` | url, selector | string | Text values, names | | `fetchUint` | url, selector, decimals | uint256 | Prices, positive numbers | | `fetchInt` | url, selector, decimals | int256 | Temperatures, signed numbers | | `fetchBool` | url, selector | bool | Flags, status checks | | `fetchStringArray` | url, selector | string\[] | Lists of names/IDs | | `fetchUintArray` | url, selector, decimals | uint256\[] | Price lists, numeric arrays | ## Parameters | Input | Type | Description | | ---------- | ------ | ---------------------------------------------------------------------------------------- | | `url` | string | The URL of the JSON API endpoint to fetch | | `selector` | string | A dot-notation path to extract from the response (e.g., `data.price` or `items[0].name`) | | `decimals` | uint8 | Number of decimal places to scale the value by (multiplies by 10^decimals) | ### Decimal Scaling The `decimals` parameter is used by `fetchUint`, `fetchInt`, and `fetchUintArray` to convert floating-point values from APIs into blockchain-compatible integers. The value is multiplied by 10^decimals. For example, with `decimals=8`: * `42000.50` becomes `4200050000000` * `0.00001234` becomes `1234` This follows the standard pattern for handling decimals in smart contracts (e.g., ERC-20 tokens typically use 18 decimals). ## Method Signatures ```solidity function fetchString(string url, string selector) returns (string) function fetchUint(string url, string selector, uint8 decimals) returns (uint256) function fetchInt(string url, string selector, uint8 decimals) returns (int256) function fetchBool(string url, string selector) returns (bool) function fetchStringArray(string url, string selector) returns (string[]) function fetchUintArray(string url, string selector, uint8 decimals) returns (uint256[]) ``` ## Example Use Cases * Fetching cryptocurrency prices from CoinGecko or similar APIs * Retrieving weather data for parametric insurance contracts * Pulling sports scores for prediction markets * Accessing any REST API that returns JSON ## Usage Examples ### Fetch a Price as uint256 ```javascript import { encodeFunctionData } from 'viem'; const abi = [{ type: 'function', name: 'fetchUint', inputs: [ { name: 'url', type: 'string' }, { name: 'selector', type: 'string' }, { name: 'decimals', type: 'uint8' } ], outputs: [{ name: 'result', type: 'uint256' }] }]; // Fetch Bitcoin price with 8 decimal places const calldata = encodeFunctionData({ abi, functionName: 'fetchUint', args: [ 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd', 'bitcoin.usd', 8 ] }); ``` ### Fetch a String Value ```javascript const abi = [{ type: 'function', name: 'fetchString', inputs: [ { name: 'url', type: 'string' }, { name: 'selector', type: 'string' } ], outputs: [{ name: 'result', type: 'string' }] }]; // Fetch a token symbol const calldata = encodeFunctionData({ abi, functionName: 'fetchString', args: [ 'https://api.example.com/token/info', 'data.symbol' ] }); ``` ### Fetch a Boolean Value ```javascript const abi = [{ type: 'function', name: 'fetchBool', inputs: [ { name: 'url', type: 'string' }, { name: 'selector', type: 'string' } ], outputs: [{ name: 'result', type: 'bool' }] }]; // Check if a market is open const calldata = encodeFunctionData({ abi, functionName: 'fetchBool', args: [ 'https://api.example.com/market/status', 'isOpen' ] }); ``` # LLM Inference Provides access to Somnia's on-chain deterministic AI models for text generation, analysis, and decision-making. This agent enables smart contracts to leverage large language models for intelligent automation — including tool use with both MCP servers and on-chain function calls. ## Methods ### inferString Simple single-turn inference with an optional system prompt. ```solidity function inferString(string prompt, string system, bool chainOfThought, string[] allowedValues) returns (string response) ``` #### Parameters | Input | Type | Description | | ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `prompt` | string | The user prompt to send to the model | | `system` | string | The system prompt to configure model behavior (can be empty string) | | `chainOfThought` | bool | Whether to enable chain-of-thought reasoning | | `allowedValues` | string\[] | Optional list of allowed response values — the model is constrained to return one of these. Pass an empty array for unconstrained output | #### Returns | Output | Type | Description | | ---------- | ------ | --------------------------- | | `response` | string | The generated text response | *** ### inferNumber Single-turn inference that extracts an integer from the model's response, clamped to a specified range. ```solidity function inferNumber(string prompt, string system, int256 minValue, int256 maxValue, bool chainOfThought) returns (int256 response) ``` #### Parameters | Input | Type | Description | | ---------------- | ------ | ------------------------------------------------------------------- | | `prompt` | string | The user prompt to send to the model | | `system` | string | The system prompt to configure model behavior (can be empty string) | | `minValue` | int256 | Minimum allowed value for the response | | `maxValue` | int256 | Maximum allowed value for the response | | `chainOfThought` | bool | Whether to enable chain-of-thought reasoning | #### Returns | Output | Type | Description | | ---------- | ------ | ------------------------------------------------------- | | `response` | int256 | The extracted integer, clamped to \[minValue, maxValue] | *** ### inferChat Multi-turn conversational inference with full message history. ```solidity function inferChat(string[] roles, string[] messages, bool chainOfThought) returns (string response) ``` #### Parameters | Input | Type | Description | | ---------------- | --------- | ------------------------------------------------------------ | | `roles` | string\[] | Array of message roles: "system", "user", or "assistant" | | `messages` | string\[] | Array of message contents (must match length of roles array) | | `chainOfThought` | bool | Whether to enable chain-of-thought reasoning | #### Returns | Output | Type | Description | | ---------- | ------ | --------------------------- | | `response` | string | The generated text response | *** ### inferToolsChat Inference with tool use. The LLM can call tools provided by **MCP servers** (executed in-situ by the agent) and **on-chain tools** (yielded back to the caller as ABI-encoded calldata). When on-chain tool calls are pending, the function returns the full conversation state so the caller can execute the calls and resume. ```solidity function inferToolsChat( string[] roles, string[] messages, string[] mcpServerUrls, OnchainTool[] onchainTools, uint256 maxIterations, bool chainOfThought ) returns ( string finishReason, string response, string[] updatedRoles, string[] updatedMessages, string[] pendingToolCallIds, bytes[] pendingToolCalls ) ``` #### Tool Definitions On-chain tools are defined using **Solidity function signature strings**, making them natural for on-chain callers: ```solidity struct OnchainTool { string signature; // e.g. "swap(address token, uint256 amount)" string description; // Human-readable description for the LLM } ``` Supported Solidity types in signatures: `string`, `bool`, `address`, `uint256` (and other `uint`/`int` sizes), `bytes`, and arrays of these types. MCP (Model Context Protocol) tools are discovered automatically — pass the server URLs and the agent fetches the available tools at runtime. #### Parameters | Input | Type | Description | | ---------------- | -------------- | ------------------------------------------------------ | | `roles` | string\[] | Array of message roles (system/user/assistant/tool) | | `messages` | string\[] | Array of message contents (must match length of roles) | | `mcpServerUrls` | string\[] | URLs of MCP servers whose tools the LLM may call | | `onchainTools` | OnchainTool\[] | Tools yielded back to caller as calldata | | `maxIterations` | uint256 | Maximum LLM↔tool round-trips before stopping | | `chainOfThought` | bool | Whether to enable chain-of-thought reasoning | #### Returns | Output | Type | Description | | -------------------- | --------- | --------------------------------------------------------------------------------------------------- | | `finishReason` | string | `"stop"` (LLM done), `"tool_calls"` (on-chain calls pending), or `"max_iterations"` (limit reached) | | `response` | string | Final text response (populated when `finishReason == "stop"`) | | `updatedRoles` | string\[] | Full conversation state — roles (for resumption) | | `updatedMessages` | string\[] | Full conversation state — messages (for resumption) | | `pendingToolCallIds` | string\[] | IDs of pending on-chain tool calls | | `pendingToolCalls` | bytes\[] | ABI-encoded calldata for each pending call (selector + args) | #### Return Semantics * **`finishReason == "stop"`**: The LLM has finished. `response` contains the final text. All other outputs are empty arrays. Any MCP tool calls were already executed during processing. * **`finishReason == "tool_calls"`**: The LLM wants to call on-chain tool(s). `response` is empty. `updatedRoles`/`updatedMessages` contain the full conversation history (including any MCP tool results). `pendingToolCallIds` and `pendingToolCalls` are parallel arrays — each `pendingToolCalls[i]` is calldata (4-byte function selector + ABI-encoded arguments). * **`finishReason == "max_iterations"`**: The agent reached `maxIterations` tool round-trips without the LLM producing a final response. ## Tool Use Flow ### MCP Tools (Automatic) MCP tools are executed automatically by the agent. The LLM calls the tool, the agent forwards the call to the MCP server, feeds the result back to the LLM, and continues until done. {% @mermaid/diagram content="sequenceDiagram participa
프로젝트로 돌아가기