Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
Binary file added hyperliquid_api_example/.DS_Store
Binary file not shown.
15 changes: 15 additions & 0 deletions hyperliquid_api_example/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "firstproject"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.95"
chrono = "0.4.39"
dotenv = "0.15.0"
reqwest = { version = "0.12.12", features = ["json"] }
rig-core = "0.6.0"
serde = "1.0.217"
serde_json = "1.0.134"
thiserror = "2.0.9"
tokio = { version = "1.42.0", features = ["full"] }
172 changes: 172 additions & 0 deletions hyperliquid_api_example/src/api_tool_template.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
use serde::{Deserialize, Serialize};
use serde_json::json;
use reqwest;
use rig::completion::ToolDefinition;
use rig::tool::Tool;

/// Arguments required for the API call
/// Add all the fields your API endpoint needs
#[derive(Debug, Serialize, Deserialize)]
pub struct TemplateArgs {
// Required fields should not have Option
required_field: String,
// Optional fields should use Option
#[serde(skip_serializing_if = "Option::is_none")]
optional_field: Option<String>,
}

/// Response structure matching your API's JSON response
/// Use serde attributes to handle field naming differences
#[derive(Debug, Serialize, Deserialize)]
struct ApiResponse {
// Example of renaming a field from snake_case to camelCase
#[serde(rename = "someField")]
some_field: String,

// Example of an optional field
#[serde(default)]
optional_data: Option<String>,

// Example of a nested structure
#[serde(rename = "nestedData")]
nested: NestedData,
}

/// Example of a nested data structure in the response
#[derive(Debug, Serialize, Deserialize)]
struct NestedData {
// Example of a numeric field
#[serde(rename = "numericValue")]
numeric_value: f64,

// Example of an array
#[serde(rename = "arrayField")]
array_field: Vec<String>,
}

/// Error types specific to your API
#[derive(Debug, thiserror::Error)]
pub enum TemplateError {
#[error("HTTP request failed: {0}")]
HttpRequestFailed(String),

#[error("API error: {0}")]
ApiError(String),

#[error("Invalid response structure")]
InvalidResponse,

#[error("Resource not found: {0}")]
NotFound(String),

// Add more error types as needed
}

/// The main tool struct
pub struct TemplateApiTool;

impl Tool for TemplateApiTool {
// Define the tool's name - this should be unique
const NAME: &'static str = "template_api_search";

// Link the argument, output, and error types
type Args = TemplateArgs;
type Output = String;
type Error = TemplateError;

/// Define the tool's interface for the AI
async fn definition(&self, _prompt: String) -> ToolDefinition {
ToolDefinition {
name: Self::NAME.to_string(),
description: "Description of what this tool does and when to use it".to_string(),
parameters: json!({
"type": "object",
"properties": {
"required_field": {
"type": "string",
"description": "Description of what this field is for"
},
"optional_field": {
"type": "string",
"description": "Description of this optional field"
}
},
"required": ["required_field"]
}),
}
}

/// Implement the actual API call and response handling
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
// Create an HTTP client
let client = reqwest::Client::new();

// Build the API request
let url = "https://api.example.com/endpoint";

// Example of a POST request with JSON body
let response = client
.post(url)
.header("Content-Type", "application/json")
// Add any required headers
// .header("Authorization", "Bearer YOUR_TOKEN")
.json(&json!({
"field": args.required_field,
"optionalField": args.optional_field
}))
.send()
.await
.map_err(|e| TemplateError::HttpRequestFailed(e.to_string()))?;

// Handle non-200 responses
if !response.status().is_success() {
let status = response.status();
let error_text = response.text().await.unwrap_or_default();
return Err(TemplateError::ApiError(format!(
"API returned status: {} - {}",
status,
error_text
)));
}

// Parse the response
let api_response: ApiResponse = response
.json()
.await
.map_err(|_| TemplateError::InvalidResponse)?;

// Format the output
let mut output = String::new();
output.push_str(&format!("Field: {}\n", api_response.some_field));

if let Some(optional) = api_response.optional_data {
output.push_str(&format!("Optional: {}\n", optional));
}

output.push_str(&format!("Numeric Value: {}\n", api_response.nested.numeric_value));
output.push_str("Array Values:\n");
for item in api_response.nested.array_field {
output.push_str(&format!("- {}\n", item));
}

Ok(output)
}
}

// Optional: Add tests for your tool
#[cfg(test)]
mod tests {
use super::*;

#[tokio::test]
async fn test_api_call() {
let tool = TemplateApiTool;
let args = TemplateArgs {
required_field: "test".to_string(),
optional_field: None,
};

let result = tool.call(args).await;
assert!(result.is_ok());
}
}
174 changes: 174 additions & 0 deletions hyperliquid_api_example/src/art_search_tool.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use rig::completion::ToolDefinition;
use rig::tool::Tool;
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::env;

// 1. First, let's define our input arguments structure
#[derive(Deserialize)]
pub struct ArtSearchArgs {
// Required
query: String,
// Optional parameters
limit: Option<u32>,
page: Option<u32>,
fields: Option<String>,
sort: Option<String>,
}

// 2. Define the artwork response structure
#[derive(Deserialize, Serialize)]
pub struct Artwork {
id: String,
title: String,
artist_display: Option<String>,
date_display: Option<String>,
medium_display: Option<String>,
dimensions: Option<String>,
image_id: Option<String>,
thumbnail: Option<serde_json::Value>,
description: Option<String>,
}

// 3. Define possible errors
#[derive(Debug, thiserror::Error)]
pub enum ArtSearchError {
#[error("HTTP request failed: {0}")]
HttpRequestFailed(String),
#[error("Invalid response structure")]
InvalidResponse,
#[error("API error: {0}")]
ApiError(String),
}

pub struct ArtSearchTool;

impl Tool for ArtSearchTool {
const NAME: &'static str = "search_art";
type Args = ArtSearchArgs;
type Output = String;
type Error = ArtSearchError;

async fn definition(&self, _prompt: String) -> ToolDefinition {
ToolDefinition {
name: "search_art".to_string(),
description: "Search for artworks in the Art Institute of Chicago collection".to_string(),
parameters: json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search term for artwork"
},
"limit": {
"type": "integer",
"description": "Number of results to return (default: 10)"
},
"page": {
"type": "integer",
"description": "Page number for pagination"
},
"fields": {
"type": "string",
"description": "Comma-separated list of fields to return"
},
"sort": {
"type": "string",
"description": "Sort order (e.g., '_score', 'title')"
}
},
"required": ["query"]
}),
}
}

async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
let client = reqwest::Client::new();

// Use the correct endpoint for searching artworks
let mut url = format!(
"https://api.artic.edu/api/v1/artworks?fields=id,title,artist_display,date_display,description&q={}",
args.query
);

// Add optional parameters
if let Some(limit) = args.limit {
url.push_str(&format!("&limit={}", limit));
}
if let Some(page) = args.page {
url.push_str(&format!("&page={}", page));
}
if let Some(sort) = args.sort {
url.push_str(&format!("&sort={}", sort));
}

println!("Requesting URL: {}", url); // Debug print

// Make the API request
let response = client
.get(&url)
.header("Accept", "application/json")
.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36")
.header("Origin", "https://api.artic.edu")
.header("Referer", "https://api.artic.edu/")
.send()
.await
.map_err(|e| ArtSearchError::HttpRequestFailed(e.to_string()))?;

// Debug print the response status
println!("Response status: {}", response.status());

// Check if the request was successful
if !response.status().is_success() {
let status = response.status(); // Get status first
let error_text = response.text().await.unwrap_or_default();
return Err(ArtSearchError::ApiError(format!(
"API returned status: {} - {}",
status,
error_text
)));
}

// Parse the response
let data: serde_json::Value = response
.json()
.await
.map_err(|e| ArtSearchError::InvalidResponse)?;

// Format the results
let mut output = String::new();

if let Some(data) = data.get("data") {
if let Some(artworks) = data.as_array() {
output.push_str("Found artworks:\n\n");

for (i, artwork) in artworks.iter().enumerate() {
let title = artwork.get("title").and_then(|v| v.as_str()).unwrap_or("Untitled");
let artist = artwork
.get("artist_display")
.and_then(|v| v.as_str())
.unwrap_or("Unknown Artist");

output.push_str(&format!("{}. **{}**\n", i + 1, title));
output.push_str(&format!(" Artist: {}\n", artist));

if let Some(date) = artwork.get("date_display").and_then(|v| v.as_str()) {
output.push_str(&format!(" Date: {}\n", date));
}

if let Some(desc) = artwork.get("description").and_then(|v| v.as_str()) {
output.push_str(&format!(" Description: {}\n", desc));
}

output.push_str("\n");
}
}
}

if output.is_empty() {
output = "No artworks found.".to_string();
}

Ok(output)
}
}
Loading