When large language models first gained the ability to call functions, developers faced a new class of engineering problem: how do you give an AI the precise vocabulary it needs to interact with domain-specific systems? A model trained on internet text knows what a stock price is in general terms, but it does not inherently know which API endpoint returns it, what parameters that endpoint expects, or how to interpret rate-limit errors when they appear. This is the gap that structured agent protocols are designed to close.
The TickDB SKILL protocol is one such specification. At its core, it is a machine-readable contract between a market data platform and any AI agent that wishes to query it. This article dissects that contract from first principles — examining the structure of the http://skill.md/ specification file, explaining how AI parsers interpret it, and demonstrating how Function Calling schemas are constructed for multi-turn market data conversations.
The Core Problem: AI Assistants and Domain-Specific APIs
Modern AI assistants excel at reasoning across general knowledge, but market data APIs present unique challenges that generic instruction-following cannot resolve. Consider a simple request: "What is the current bid-ask spread on NVIDIA?"
A human quant developer understands immediately that this requires querying an order book endpoint for a specific symbol. An AI assistant, unassisted, faces several failure modes:
- Endpoint ambiguity: The AI does not know whether to call
/depth,/quote,/realtime, or/ticker. Market data platforms vary in their endpoint naming conventions. - Parameter format uncertainty: Should the symbol be
NVDA,NVDA.US,NASDAQ:NVDA, orNVD? The AI cannot guess correctly without explicit guidance. - Response interpretation: A JSON response containing nested arrays of bid/ask levels with quantities and timestamps requires structured parsing instructions that the model must receive before the call.
- Error recovery: When a
2002error (symbol not found) arrives, the AI needs a policy for whether to try alternative symbol formats or report the failure.
The SKILL protocol addresses all four failure modes by providing a standardized, versioned specification file that an AI can read, interpret, and follow without human intervention during execution.
Anatomy of the http://skill.md/ Specification File
The http://skill.md/ file is the central artifact of the SKILL protocol. It is a Markdown-formatted document that serves dual purposes: it is human-readable for developers reviewing the specification, and it is machine-parseable for AI agents that extract structured schemas from it.
File Structure Overview
A complete skill.md file contains five primary sections:
# SKILL Name
Brief description of what this SKILL enables.
## API Endpoints
Detailed endpoint specifications...
## Function Calling Schema
JSON Schema definitions for all callable functions...
## Data Models
Structured definitions of request/response objects...
## Error Handling
Standardized error codes and recovery strategies...
## Version
SKILL version, compatibility matrix...
Section 1: API Endpoints
This section enumerates every available endpoint with complete parameter documentation. For each endpoint, the specification includes:
- HTTP method and path:
GET /v1/market/depth - Authentication requirements: Header-based (
X-API-Key) or query-parameter-based - Required parameters: Name, type, allowed values, default values
- Optional parameters: Same metadata as required, plus whether the parameter has side effects
- Rate limits: Requests per minute, burst limits, and the error code returned when exceeded
- Example request: A fully formed curl or Python example with placeholder values replaced by illustrative constants
- Example response: A representative JSON payload showing the actual data structure returned
Here is a distilled example of how an endpoint specification appears in skill.md:
### GET /v1/market/depth
Returns the current order book snapshot for a given symbol.
**Authentication**: Header (`X-API-Key`)
**Parameters**:
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| symbol | string | Yes | Symbol identifier. Format: `TICKER.EXCHANGE` (e.g., `AAPL.US`, `BTC.Binance`) |
| exchange | string | No | Override exchange for symbols listed on multiple venues. Defaults to primary. |
| depth | integer | No | Number of price levels to return. Range: 1–50. Default: 10. |
**Rate Limit**: 60 requests/minute per API key.
**Example Request**:
```bash
curl -H "X-API-Key: $TICKDB_API_KEY" \
"https://api.tickdb.ai/v1/market/depth?symbol=AAPL.US&depth=5"
Example Response:
{
"symbol": "AAPL.US",
"timestamp": 1718294400000,
"bids": [
{"price": 192.45, "size": 12500},
{"price": 192.44, "size": 8300}
],
"asks": [
{"price": 192.46, "size": 10200},
{"price": 192.47, "size": 6700}
]
}
This level of detail is intentional. A model parsing this specification must know not just the endpoint path, but the exact JSON key names (`bids`, `asks`, `price`, `size`) so it can construct valid queries and correctly interpret responses.
### Section 2: Function Calling Schema
The Function Calling schema is the most critical section for AI execution. It is a JSON Schema document that defines every callable function in a format compatible with OpenAI's Function Calling interface and equivalent implementations in other LLMs.
Each function definition includes:
- **Name**: A lowercase, underscore-separated identifier (`get_order_book_depth`)
- **Description**: A natural-language explanation of what the function does, written for an AI audience (not a human developer — the AI needs precise behavioral descriptions)
- **Parameters**: A JSON Schema describing all input parameters with types, constraints, and enumerated allowed values
- **Required fields**: Which parameters must be provided versus which have defaults
```json
{
"name": "get_order_book_depth",
"description": "Retrieves the current order book snapshot for a specified symbol, including bid and ask levels with quantities. Use this function when the user asks about order book depth, bid-ask spreads, liquidity at specific price levels, or buy/sell pressure. Returns up to 50 price levels per side.",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "Market symbol in TICKER.EXCHANGE format. Examples: AAPL.US, NVDA.US, 700.HK, BTC.Binance"
},
"depth": {
"type": "integer",
"description": "Number of price levels to return per side. Range 1-50. Default: 10.",
"default": 10,
"minimum": 1,
"maximum": 50
}
},
"required": ["symbol"]
}
}
The description field deserves particular attention. It is not a marketing blurb — it is an operational instruction that tells the model when to invoke this function. The description should enumerate:
- What the function does (concise, factual)
- When to call it (trigger scenarios: "when the user asks about spreads," "when comparing liquidity across symbols")
- What it returns (data shape and typical values)
- Constraints (limits on depth, frequency, or symbol coverage)
This level of specificity prevents two common AI failure modes: calling the wrong function for a given intent, and hallucinating parameter values that the API does not accept.
Section 3: Data Models
Market data responses contain nested, polymorphic structures. The depth endpoint returns bids and asks; the kline endpoint returns OHLCV arrays; the ticker endpoint returns 24-hour rolling statistics. Each of these has distinct field names, types, and semantic meanings.
The Data Models section provides canonical definitions for every response object:
{
"OrderBookLevel": {
"price": "number (decimal string for precision)",
"size": "integer (number of contracts or shares)"
},
"OrderBookSnapshot": {
"symbol": "string",
"timestamp": "integer (Unix milliseconds)",
"bids": "OrderBookLevel[] (sorted descending by price)",
"asks": "OrderBookLevel[] (sorted ascending by price)"
},
"OHLCVCandle": {
"timestamp": "integer (Unix milliseconds, interval start)",
"open": "number",
"high": "number",
"low": "number",
"close": "number",
"volume": "integer"
}
}
By defining these models explicitly, the specification enables an AI to construct valid output parsers, validate incoming data against expected schemas, and surface structural errors (such as missing fields) before they propagate into downstream calculations.
Section 4: Error Handling
Error codes are documented with their numeric values, semantic meanings, and prescribed recovery actions. This section is essential for multi-turn conversations where a failed API call must not terminate the interaction — the AI needs a policy for retry, alternative approaches, or graceful degradation.
{
"errors": [
{
"code": 1001,
"message": "Invalid API key",
"severity": "fatal",
"recovery": "Prompt user to verify their API key in the TickDB dashboard. Do not retry."
},
{
"code": 1002,
"message": "Missing API key",
"severity": "fatal",
"recovery": "Request that the user set the TICKDB_API_KEY environment variable."
},
{
"code": 2002,
"message": "Symbol not found",
"severity": "recoverable",
"recovery": "Attempt alternative symbol formats (e.g., add .US suffix). If all formats fail, report the failure and suggest the user verify the symbol via /v1/symbols/available."
},
{
"code": 3001,
"message": "Rate limit exceeded",
"severity": "recoverable",
"recovery": "Read the Retry-After header. Wait for the specified duration before retrying. If no header is present, wait 5 seconds. Maximum 3 retries."
}
]
}
Section 5: Versioning and Compatibility
The SKILL specification includes a version field and a compatibility matrix that maps the spec version to supported API versions. This enables both the platform and consuming AI agents to negotiate capabilities:
## Version
**SKILL Version**: 1.2.0
**API Compatibility**: v1.0, v1.1
**Last Updated**: 2026-04-15
| Feature | SKILL 1.0 | SKILL 1.1 | SKILL 1.2 |
|---------|-----------|-----------|-----------|
| Order book depth | ✓ | ✓ | ✓ |
| OHLCV klines | ✓ | ✓ | ✓ |
| Multi-exchange symbols | ✗ | ✓ | ✓ |
| Depth channel (WebSocket) | ✗ | ✗ | ✓ |
How AI Parsers Interpret the Specification
An AI agent does not read skill.md the way a human does — line by line, parsing prose. Instead, a pre-processing step extracts structured data from the Markdown document into an internal representation that the model can reason over during Function Calling.
Extraction Pipeline
The typical parsing pipeline operates as follows:
- Section identification: The parser identifies section headers (
## API Endpoints,## Function Calling Schema) and treats each as a distinct knowledge domain. - JSON extraction: Code blocks containing JSON (delimited by triple backticks) are parsed as structured data. The parser validates that the JSON is well-formed and conforms to expected schemas.
- Table parsing: Parameter tables are parsed into structured objects, extracting column headers as field names and rows as values.
- Schema compilation: The extracted Function Calling definitions are compiled into a tool manifest — the payload that is sent to the LLM to announce available functions.
- Example indexing: Example requests and responses are indexed by endpoint, enabling the parser to provide few-shot examples when the model generates a function call.
Tool Manifest Generation
The final output of the parsing pipeline is a tool manifest that conforms to the OpenAI Function Calling format (or equivalent):
{
"tools": [
{
"type": "function",
"function": {
"name": "get_order_book_depth",
"description": "Retrieves the current order book snapshot for a specified symbol...",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "Market symbol in TICKER.EXCHANGE format..."
},
"depth": {
"type": "integer",
"description": "Number of price levels to return per side...",
"default": 10
}
},
"required": ["symbol"]
}
}
},
{
"type": "function",
"function": {
"name": "get_historical_klines",
"description": "Retrieves historical OHLCV candlestick data for backtesting...",
"parameters": {
"type": "object",
"properties": {
"symbol": {
"type": "string",
"description": "Market symbol in TICKER.EXCHANGE format..."
},
"interval": {
"type": "string",
"enum": ["1m", "5m", "15m", "1h", "4h", "1d", "1w"],
"description": "Candlestick interval..."
},
"start_time": {
"type": "integer",
"description": "Start time in Unix milliseconds..."
},
"end_time": {
"type": "integer",
"description": "End time in Unix milliseconds..."
},
"limit": {
"type": "integer",
"description": "Maximum number of candles to return. Range: 1-1000. Default: 300.",
"default": 300
}
},
"required": ["symbol", "interval"]
}
}
}
]
}
This manifest is passed to the LLM via the platform's tool-calling interface. The model does not "memorize" the specification — it receives the manifest at inference time, enabling it to select the correct function and generate valid parameters for each query.
Multi-Turn Conversation State Management
Market data queries rarely happen in isolation. A realistic quant research workflow involves follow-up questions, parameter refinement, and cross-endpoint comparisons:
- User: "Show me the order book for AAPL."
- AI: Calls
get_order_book_depth(symbol="AAPL.US"), receives response. - User: "Now add TSLA to the comparison."
- AI: Calls
get_order_book_depth(symbol="TSLA.US"), merges the two responses. - User: "What about NVDA? And can you calculate the combined bid-ask spread across all three?"
- AI: Calls
get_order_book_depth(symbol="NVDA.US"); upon receiving all three responses, computes the cross-symbol spread comparison and presents a formatted table.
This multi-turn behavior requires the AI to maintain conversation state — specifically, the accumulated responses from prior function calls. The SKILL protocol addresses state management through two mechanisms:
Mechanism 1: Response Caching
Function call responses are cached in the conversation context with a defined TTL (time-to-live). Cached responses allow the AI to reference prior data without re-querying the API, which is critical both for rate-limit management and for response latency.
Cache key: get_order_book_depth:AAPL.US:10
Cache value: {bids: [...], asks: [...], timestamp: 1718294400000}
TTL: 30 seconds (order book data is transient; stale data produces incorrect analysis)
Mechanism 2: Cross-Function Data Aggregation
When a user's follow-up request requires combining data from multiple endpoints (for example, merging real-time depth data with historical klines to compute realized volatility), the SKILL protocol defines aggregation functions that the AI can call after the individual endpoint results are available.
# Post-retrieval aggregation — not an API call, but a defined operation
def compute_spread_metrics(depth_a: OrderBookSnapshot,
depth_b: OrderBookSnapshot,
depth_c: OrderBookSnapshot) -> dict:
"""
Computes cross-symbol spread metrics from three order book snapshots.
Each snapshot should be fetched within the last 30 seconds.
"""
spreads = {}
for snapshot in (depth_a, depth_b, depth_c):
best_bid = snapshot['bids'][0]['price']
best_ask = snapshot['asks'][0]['price']
spreads[snapshot['symbol']] = {
'spread_bps': round((best_ask - best_bid) / best_bid * 10000, 2),
'mid_price': round((best_bid + best_ask) / 2, 4),
'total_bid_liquidity': sum(level['size'] for level in snapshot['bids'][:5]),
'total_ask_liquidity': sum(level['size'] for level in snapshot['asks'][:5]),
'pressure_ratio': round(
sum(level['size'] for level in snapshot['bids'][:5]) /
sum(level['size'] for level in snapshot['asks'][:5]), 3
)
}
return spreads
Production-Grade Integration Code
Connecting an AI assistant to the SKILL protocol requires more than parsing the specification — it requires a robust execution layer that handles authentication, rate limiting, timeouts, and error recovery. The following Python implementation demonstrates the recommended integration pattern:
import os
import json
import time
import random
import logging
from typing import Any, Optional
from dataclasses import dataclass, field
from datetime import datetime
import requests
# Configure structured logging for production monitoring
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger = logging.getLogger("tickdb_integration")
@dataclass
class TickDBConfig:
"""Configuration for the TickDB API integration."""
api_key: str = field(default_factory=lambda: os.environ.get("TICKDB_API_KEY", ""))
base_url: str = "https://api.tickdb.ai"
timeout: tuple = (3.05, 10) # (connect_timeout, read_timeout)
max_retries: int = 3
base_delay: float = 1.0
max_delay: float = 30.0
def validate(self) -> None:
"""Validate configuration at startup — fail fast."""
if not self.api_key:
raise ValueError(
"TICKDB_API_KEY environment variable is not set. "
"Generate an API key at tickdb.ai/dashboard"
)
@dataclass
class APIResponse:
"""Standardized API response wrapper."""
success: bool
data: Optional[Any] = None
error_code: Optional[int] = None
error_message: Optional[str] = None
retry_after: Optional[int] = None
def __post_init__(self):
if self.success and (self.error_code or self.error_message):
logger.warning(
f"Conflicting response state: success=True but error_code={self.error_code}"
)
class TickDBClient:
"""
Production-grade client for TickDB API integration with AI assistants.
Supports:
- REST API calls with automatic retry and exponential backoff
- Rate-limit handling with Retry-After header support
- Structured error classification for AI error-recovery logic
- Configurable timeouts to prevent hanging in production environments
"""
# Error code constants — must match the SKILL specification
ERROR_INVALID_API_KEY = 1001
ERROR_MISSING_API_KEY = 1002
ERROR_SYMBOL_NOT_FOUND = 2002
ERROR_RATE_LIMITED = 3001
def __init__(self, config: Optional[TickDBConfig] = None):
self.config = config or TickDBConfig()
self.config.validate()
self.session = requests.Session()
self.session.headers.update({
"X-API-Key": self.config.api_key,
"Content-Type": "application/json",
"User-Agent": "TickDB-SKILL-Client/1.2.0"
})
def _build_url(self, endpoint: str) -> str:
"""Construct full URL from endpoint path."""
return f"{self.config.base_url}/v1{endpoint}"
def _handle_error(self, response: requests.Response, retry_count: int) -> APIResponse:
"""
Classify API errors and determine recovery strategy.
Returns an APIResponse with classified error information.
"""
try:
error_body = response.json()
except (json.JSONDecodeError, ValueError):
error_body = {}
code = error_body.get("code", 0)
message = error_body.get("message", "Unknown error")
retry_after = int(response.headers.get("Retry-After", 0))
if code == 0:
return APIResponse(success=True, data=error_body.get("data", error_body))
if code in (self.ERROR_INVALID_API_KEY, self.ERROR_MISSING_API_KEY):
logger.error(f"Fatal authentication error: {code} — {message}")
return APIResponse(
success=False,
error_code=code,
error_message=message
)
if code == self.ERROR_SYMBOL_NOT_FOUND:
logger.warning(f"Symbol not found: {error_body.get('symbol', 'unknown')}")
return APIResponse(
success=False,
error_code=code,
error_message=message
)
if code == self.ERROR_RATE_LIMITED:
logger.warning(f"Rate limited — Retry-After: {retry_after}s (attempt {retry_count})")
return APIResponse(
success=False,
error_code=code,
error_message=message,
retry_after=retry_after if retry_after > 0 else 5
)
# Unhandled error codes
logger.error(f"Unhandled API error {code}: {message}")
return APIResponse(
success=False,
error_code=code,
error_message=message
)
def _exponential_backoff_with_jitter(self, retry_count: int) -> float:
"""
Compute delay with exponential backoff and full jitter.
Formula: random(0, min(base_delay * 2^retry_count, max_delay))
This prevents thundering-herd behavior when many clients
retry simultaneously after a shared outage.
"""
base_delay = self.config.base_delay
max_delay = self.config.max_delay
delay = min(base_delay * (2 ** retry_count), max_delay)
jitter = random.uniform(0, delay * 0.1)
return delay + jitter
def _make_request(
self,
method: str,
endpoint: str,
params: Optional[dict] = None,
json_body: Optional[dict] = None,
retry_count: int = 0
) -> APIResponse:
"""
Execute an HTTP request with timeout, retry logic, and error classification.
"""
url = self._build_url(endpoint)
logger.info(f"Request: {method} {url} params={params}")
try:
response = self.session.request(
method=method,
url=url,
params=params,
json=json_body,
timeout=self.config.timeout
)
except requests.exceptions.Timeout:
logger.error(f"Request timed out after {self.config.timeout}")
return APIResponse(
success=False,
error_code=-1,
error_message=f"Request timed out after {self.config.timeout}"
)
except requests.exceptions.RequestException as e:
logger.error(f"Request failed: {e}")
return APIResponse(
success=False,
error_code=-2,
error_message=str(e)
)
# Classify the response
result = self._handle_error(response, retry_count)
# Retry logic for recoverable errors
if not result.success and result.retry_after is not None:
if retry_count < self.config.max_retries:
delay = result.retry_after or self._exponential_backoff_with_jitter(retry_count)
logger.info(f"Retrying in {delay:.2f}s (attempt {retry_count + 1}/{self.config.max_retries})")
time.sleep(delay)
return self._make_request(
method, endpoint, params, json_body, retry_count=retry_count + 1
)
else:
logger.warning(f"Max retries exceeded for {endpoint}")
return result
def get_order_book_depth(
self,
symbol: str,
depth: int = 10
) -> APIResponse:
"""
Fetch order book depth snapshot.
Args:
symbol: Market symbol in TICKER.EXCHANGE format (e.g., AAPL.US, BTC.Binance)
depth: Number of price levels to return (1-50, default: 10)
Returns:
APIResponse containing the order book snapshot or error details
"""
return self._make_request(
method="GET",
endpoint="/market/depth",
params={"symbol": symbol, "depth": depth}
)
def get_historical_klines(
self,
symbol: str,
interval: str,
start_time: Optional[int] = None,
end_time: Optional[int] = None,
limit: int = 300
) -> APIResponse:
"""
Fetch historical OHLCV candlestick data for backtesting.
Args:
symbol: Market symbol in TICKER.EXCHANGE format
interval: Candlestick interval (1m, 5m, 15m, 1h, 4h, 1d, 1w)
start_time: Start time in Unix milliseconds
end_time: End time in Unix milliseconds
limit: Maximum candles to return (1-1000, default: 300)
Returns:
APIResponse containing OHLCV candles or error details
"""
params = {
"symbol": symbol,
"interval": interval,
"limit": limit
}
if start_time:
params["start_time"] = start_time
if end_time:
params["end_time"] = end_time
return self._make_request(
method="GET",
endpoint="/market/kline",
params=params
)
def get_available_symbols(
self,
exchange: Optional[str] = None,
asset_type: Optional[str] = None
) -> APIResponse:
"""
List all available symbols. Use this to verify symbol formats
before making targeted queries.
Args:
exchange: Filter by exchange (e.g., US, HK, Binance)
asset_type: Filter by asset type (equity, crypto, forex, commodity)
Returns:
APIResponse containing the symbol list
"""
params = {}
if exchange:
params["exchange"] = exchange
if asset_type:
params["type"] = asset_type
return self._make_request(
method="GET",
endpoint="/symbols/available",
params=params
)
# Example usage within an AI agent's function-calling loop
def ai_function_call_handler(tool_name: str, parameters: dict) -> dict:
"""
AI agent integration layer: receives function name and parameters
from the LLM's tool call, executes the corresponding API operation,
and returns structured results for the model to synthesize.
This function bridges the gap between the LLM's intent and the API's
actual response format, providing consistent error handling and
response normalization.
"""
client = TickDBClient()
handlers = {
"get_order_book_depth": lambda p: client.get_order_book_depth(**p),
"get_historical_klines": lambda p: client.get_historical_klines(**p),
"get_available_symbols": lambda p: client.get_available_symbols(**p),
}
handler = handlers.get(tool_name)
if not handler:
return {
"success": False,
"error": f"Unknown tool: {tool_name}. Available tools: {list(handlers.keys())}"
}
result = handler(parameters)
if result.success:
return {
"success": True,
"data": result.data
}
else:
return {
"success": False,
"error_code": result.error_code,
"error": result.error_message,
"retry_after": result.retry_after,
"recovery_hint": _get_recovery_hint(result.error_code, parameters)
}
def _get_recovery_hint(error_code: int, original_params: dict) -> str:
"""Generate AI-readable recovery hints based on error classification."""
hints = {
2002: (
f"Symbol '{original_params.get('symbol')}' was not found. "
"Try listing available symbols via get_available_symbols to find valid formats. "
"For US equities, use TICKER.US format (e.g., AAPL.US, not AAPL)."
),
3001: (
"Rate limit exceeded. Wait for the Retry-After duration before retrying. "
"Consider reducing query frequency or caching responses for repeated lookups."
),
1001: (
"Invalid API key. Verify that TICKDB_API_KEY is set correctly. "
"Generate a new key at tickdb.ai/dashboard if the current key has been rotated."
)
}
return hints.get(error_code, "Unknown error — manual investigation required.")
Implementing Function Calling with the SKILL Schema
With the client implementation in place, the final piece is wiring it into an AI agent's Function Calling loop. The integration follows a standard pattern regardless of which LLM platform you use (OpenAI, Anthropic, local models via Ollama, or cloud-hosted alternatives).
Step 1: Load the Tool Manifest
At agent initialization, load the SKILL's Function Calling schema and register it with the model:
import json
def load_tool_manifest(skill_md_path: str) -> list:
"""
Parse the skill.md file and extract the Function Calling schema.
In production, this file would be fetched from a versioned CDN URL
(e.g., https://cdn.tickdb.ai/skill/v1.2.0/manifest.json) to ensure
the agent always uses the latest specification.
"""
with open(skill_md_path, 'r') as f:
content = f.read()
# Extract JSON blocks from the Markdown
# In production, use a proper Markdown+JSON parser
json_blocks = []
in_code_block = False
current_block = []
block_lang = None
for line in content.split('\n'):
if line.startswith('```'):
if in_code_block:
# End of block
block_text = '\n'.join(current_block)
if block_lang == 'json':
try:
json_blocks.append(json.loads(block_text))
except json.JSONDecodeError:
pass
in_code_block = False
current_block = []
block_lang = None
else:
# Start of block
in_code_block = True
block_lang = line[3:].strip()
elif in_code_block:
current_block.append(line)
# Find the Function Calling schema among extracted JSON blocks
for block in json_blocks:
if 'tools' in block or 'function' in block:
return block.get('tools', [block])
raise ValueError("No Function Calling schema found in skill.md")
Step 2: Execute the Agent Loop
def run_market_data_agent(user_query: str, tool_manifest: list) -> str:
"""
Execute a multi-turn market data query through an AI agent with
function-calling capabilities.
The agent receives:
1. The user's natural-language query
2. The SKILL tool manifest (available functions + descriptions)
The agent responds by:
- Calling functions when needed (with validated parameters)
- Synthesizing results into natural-language responses
- Asking clarifying questions if the query is ambiguous
"""
messages = [
{
"role": "system",
"content": (
"You are a quantitative research assistant with access to real-time "
"and historical market data via TickDB. Use the provided tool manifest "
"to call functions when you need live or historical data. "
"Always verify symbol formats before querying. "
"When you encounter errors, use the recovery_hint to determine the next step. "
"Present quantitative results with appropriate precision (bps, percentages, etc.)."
)
},
{"role": "user", "content": user_query}
]
max_turns = 10
turn = 0
while turn < max_turns:
turn += 1
# Generate next response (model decides to call tools or respond)
response = model.generate(messages, tools=tool_manifest)
if not response.tool_calls:
# Model has finished — return the final response
return response.content
# Execute each tool call and append results
for tool_call in response.tool_calls:
function_name = tool_call.function.name
parameters = json.loads(tool_call.function.arguments)
logger.info(f"Tool call [{turn}]: {function_name} params={parameters}")
result = ai_function_call_handler(function_name, parameters)
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [tool_call]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": json.dumps(result)
})
return "Agent reached maximum turn limit. Please refine your query."
Comparing SKILL Protocol to Alternative Approaches
For teams evaluating how to integrate LLMs with market data systems, three broad approaches exist. Each has distinct trade-offs.
| Dimension | Raw API Documentation | OpenAPI + Custom Wrapper | SKILL Protocol |
|---|---|---|---|
| AI parseability | Low — requires manual instruction following | Medium — structured schema but ambiguous domain semantics | High — explicit intent mapping and recovery policies |
| Maintenance burden | High — prompts must be manually updated when APIs change | Medium — OpenAPI spec must be kept in sync | Low — spec and manifest are co-versioned |
| Error recovery | DIY — each integration implements its own retry logic | Partial — OpenAPI describes errors but not recovery policies | Full — errors include recovery_hint fields for AI agents |
| Multi-turn state | Not addressed | Not addressed | Defined caching TTLs and aggregation functions |
| Supported LLM platforms | Any (prompt-dependent) | OpenAI-compatible | Any Function Calling–compatible platform |
| Setup complexity | Low | Medium | Medium (requires understanding the protocol spec) |
The SKILL protocol's advantage is not raw capability — a skilled engineer could implement all of these features using OpenAPI and custom prompts. The protocol's value is standardization: it provides a shared vocabulary between the platform provider and the AI developer, reducing the iteration cycles required to get a new AI integration working correctly.
Practical Deployment Recommendations
Teams adopting the SKILL protocol should consider three deployment tiers:
| Deployment tier | Use case | SKILL version | Caching strategy |
|---|---|---|---|
| Individual researcher | Ad-hoc queries, strategy ideation | Latest stable | No caching — fresh data for each query |
| Quant team | Shared research notebooks, backtest validation | Pinned to validated version | 30-second TTL for real-time data; no TTL for historical |
| Production trading system | Live signal generation, automated execution | Pinned with hash verification | 5-second TTL with staleness alerts; historical data cached indefinitely |
For teams using AI coding assistants (Cursor, Copilot, Claude Desktop, etc.), the SKILL can be packaged as a downloadable tool manifest. Search for and install the tickdb-market-data SKILL in your AI tool's marketplace to enable instant function-calling access to real-time market data within your existing development workflow.
Closing
Structured agent protocols like the SKILL specification represent a practical answer to a real engineering problem: LLMs need domain-specific guidance to interact safely and correctly with specialized APIs. Without explicit endpoint documentation, parameter constraints, error recovery policies, and multi-turn state management, an AI assistant querying a market data API will produce unreliable results — hallucinated symbols, incorrect parameter formats, and unhandled rate-limit errors.
The SKILL protocol does not eliminate the need for careful engineering. The code you write to execute function calls must still handle timeouts, implement backoff and jitter, classify errors, and cache responses appropriately. What the protocol provides is a stable contract between the data platform and the AI layer — ensuring that both sides speak the same language when defining what data is available, how to request it, and what to do when something goes wrong.
This article does not constitute investment advice. Markets involve risk; past performance does not guarantee future results.