AI-Dev/qortal_api_toolkit.py

147 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
title: Qortal API Toolkit
author: crowetic (with assistance from chatgpt)
git_url: https://gitea.qortal.link/crowetic/AI-Dev
version: 0.3.0
license: MIT
description: Query any Qortal Core HTTP endpoint directly from Open WebUI.
HOW-TO
------
1. Add this in 'tools' undder 'workspaces' to create the tool function
2. Add the tool to your model
3. In any model/preset: *Chat → ⚙️ Tools* → enable **Qortal API Toolkit**.
4. Ask something like:
“Get the balance of address Q...”
The LLM will auto-call `qortal_get`.
________ __ .__ _____ __________.___ _____ __ .__
\_____ \ ____________/ |______ | | / _ \\______ \ |/ ____\_ __ ____ _____/ |_|__| ____ ____
/ / \ \ / _ \_ __ \ __\__ \ | | / /_\ \| ___/ \ __\ | \/ \_/ ___\ __\ |/ _ \ / \
/ \_/. ( <_> ) | \/| | / __ \| |_/ | \ | | || | | | / | \ \___| | | ( <_> ) | \
\_____\ \_/\____/|__| |__| (____ /____|____|__ /____| |___||__| |____/|___| /\___ >__| |__|\____/|___| /
\__> \/ \/ \/ \/ \/
___. __ .__
\_ |__ ___.__. /\ ___________ ______ _ __ _____/ |_|__| ____
| __ < | | \/ _/ ___\_ __ \/ _ \ \/ \/ // __ \ __\ |/ ___\
| \_\ \___ | /\ \ \___| | \( <_> ) /\ ___/| | | \ \___
|___ / ____| \/ \___ >__| \____/ \/\_/ \___ >__| |__|\___ >
\/\/ \/ \/ \/
"""
import os, json, requests
from typing import Optional
from pydantic import BaseModel, Field, ConfigDict
# ──────────────────────────────────────────────────────────── helpers ──────
class _EventEmitter:
def __init__(self, cb): # cb is Open WebUIs internal emitter
self.cb = cb
async def emit(self, msg, status="in_progress", done=False):
if self.cb:
await self.cb(
{
"type": "status",
"data": {
"status": status,
"description": msg,
"done": done,
},
}
)
# ──────────────────────────────────────────────────────────── TOOL ─────────
class Tools:
# user-tweakable knobs appear in Settings → Tools
class Valves(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)
QORTAL_URL: str = Field(
default=os.getenv("QORTAL_API_URL", "https://api.qortal.org"),
description="Base URL of your Qortal node",
)
HTTP_TIMEOUT: int = Field(default=30, description="Request timeout in seconds")
ALLOW_POST: bool = Field(
default=False,
description="Enable POST endpoints (node must allow them)",
)
def __init__(self):
self.valves = self.Valves()
self.headers = {"User-Agent": "Open-WebUI Qortal Toolkit (+https://qortal.org)"}
# ─────────────────────────── single public function ────────────────────
async def qortal_call(
self,
endpoint: str,
method: str = "GET",
path_params_json: str = "{}",
query_params_json: str = "{}",
json_body_json: str = "{}",
__event_emitter__=None,
) -> str:
"""
Generic Qortal HTTP call.
• endpoint (str) e.g. "/addresses/balance/{address}"
• method (str) "GET" or "POST"
• path_params_json (str) JSON dict of tokens to replace
• query_params_json (str) JSON dict for URL ?key=value
• json_body_json (str) JSON dict for POST body
"""
emitter = _EventEmitter(__event_emitter__)
await emitter.emit(f"{method.upper()} {endpoint}")
# parse the JSON strings the LLM supplies
try:
path_params_json = path_params_json or "{}"
query_params_json = query_params_json or "{}"
json_body_json = json_body_json or "{}"
except json.JSONDecodeError as e:
await emitter.emit("Bad JSON in arguments", "error", True)
return json.dumps({"success": False, "error": str(e)})
# build full URL
url = self.valves.QORTAL_URL.rstrip("/") + "/" + endpoint.lstrip("/")
for k, v in path_params.items():
url = url.replace("{" + k + "}", str(v))
# block POST if valve is off
if method.upper() == "POST" and not self.valves.ALLOW_POST:
await emitter.emit("POST disabled", "error", True)
return json.dumps({"success": False, "error": "post_disabled"})
# make the request
try:
r = requests.request(
method.upper(),
url,
params=query_params,
json=json_body if method.upper() == "POST" else None,
headers=self.headers,
timeout=self.valves.HTTP_TIMEOUT,
)
try:
data = r.json()
except ValueError:
data = r.text
await emitter.emit(f"{r.status_code}", "complete", True)
return json.dumps(
{
"success": r.ok,
"status_code": r.status_code,
"url": r.url,
"data": data,
},
ensure_ascii=False,
)
except Exception as e:
await emitter.emit(str(e), "error", True)
return json.dumps({"success": False, "error": str(e)})