apia 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- apia/__init__.py +27 -0
- apia/exceptions.py +13 -0
- apia/manifest.py +196 -0
- apia/registry.py +176 -0
- apia-0.1.0.dist-info/METADATA +166 -0
- apia-0.1.0.dist-info/RECORD +7 -0
- apia-0.1.0.dist-info/WHEEL +4 -0
apia/__init__.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""
|
|
2
|
+
APIA Python SDK
|
|
3
|
+
~~~~~~~~~~~~~~~
|
|
4
|
+
Python client for the APIA standard — AI-native API manifest discovery.
|
|
5
|
+
|
|
6
|
+
>>> from apia import Registry
|
|
7
|
+
>>> registry = Registry()
|
|
8
|
+
>>> apis = registry.find("send telegram message")
|
|
9
|
+
>>> print(apis[0].name)
|
|
10
|
+
'Telegram Bot'
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .registry import Registry
|
|
14
|
+
from .manifest import Manifest, Capability, Service, Auth
|
|
15
|
+
from .exceptions import ApiaError, ManifestNotFoundError, RegistryError
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Registry",
|
|
20
|
+
"Manifest",
|
|
21
|
+
"Capability",
|
|
22
|
+
"Service",
|
|
23
|
+
"Auth",
|
|
24
|
+
"ApiaError",
|
|
25
|
+
"ManifestNotFoundError",
|
|
26
|
+
"RegistryError",
|
|
27
|
+
]
|
apia/exceptions.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""APIA exceptions."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ApiaError(Exception):
|
|
5
|
+
"""Base exception for all APIA errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RegistryError(ApiaError):
|
|
9
|
+
"""Raised when the registry cannot be loaded or is invalid."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ManifestNotFoundError(ApiaError):
|
|
13
|
+
"""Raised when a manifest cannot be found by the given id or criteria."""
|
apia/manifest.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Data models for APIA manifests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Auth:
|
|
10
|
+
type: str
|
|
11
|
+
anonymous_access: bool
|
|
12
|
+
how_to_get: str = ""
|
|
13
|
+
cost: str = ""
|
|
14
|
+
header: str = ""
|
|
15
|
+
param_name: str = ""
|
|
16
|
+
param_location: str = ""
|
|
17
|
+
token_url: str = ""
|
|
18
|
+
note: str = ""
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def from_dict(cls, data: dict[str, Any]) -> "Auth":
|
|
22
|
+
return cls(
|
|
23
|
+
type=data.get("type", ""),
|
|
24
|
+
anonymous_access=data.get("anonymous_access", False),
|
|
25
|
+
how_to_get=data.get("how_to_get", ""),
|
|
26
|
+
cost=data.get("cost", ""),
|
|
27
|
+
header=data.get("header", ""),
|
|
28
|
+
param_name=data.get("param_name", ""),
|
|
29
|
+
param_location=data.get("param_location", ""),
|
|
30
|
+
token_url=data.get("token_url", ""),
|
|
31
|
+
note=data.get("note", ""),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Service:
|
|
37
|
+
id: str
|
|
38
|
+
name: str
|
|
39
|
+
description_for_ai: str
|
|
40
|
+
category: str
|
|
41
|
+
geo: list[str]
|
|
42
|
+
language: str = "en"
|
|
43
|
+
url: str = ""
|
|
44
|
+
api_base: str = ""
|
|
45
|
+
docs: str = ""
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_dict(cls, data: dict[str, Any]) -> "Service":
|
|
49
|
+
return cls(
|
|
50
|
+
id=data.get("id", ""),
|
|
51
|
+
name=data.get("name", ""),
|
|
52
|
+
description_for_ai=data.get("description_for_ai", ""),
|
|
53
|
+
category=data.get("category", ""),
|
|
54
|
+
geo=data.get("geo", []),
|
|
55
|
+
language=data.get("language", "en"),
|
|
56
|
+
url=data.get("url", ""),
|
|
57
|
+
api_base=data.get("api_base", ""),
|
|
58
|
+
docs=data.get("docs", ""),
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class Capability:
|
|
64
|
+
id: str
|
|
65
|
+
description_for_ai: str
|
|
66
|
+
intent: list[str]
|
|
67
|
+
endpoint: str
|
|
68
|
+
input: dict[str, Any] = field(default_factory=dict)
|
|
69
|
+
output: dict[str, Any] = field(default_factory=dict)
|
|
70
|
+
realtime: bool = False
|
|
71
|
+
requires_auth: bool = True
|
|
72
|
+
rate_limit: str = ""
|
|
73
|
+
|
|
74
|
+
@classmethod
|
|
75
|
+
def from_dict(cls, data: dict[str, Any]) -> "Capability":
|
|
76
|
+
return cls(
|
|
77
|
+
id=data.get("id", ""),
|
|
78
|
+
description_for_ai=data.get("description_for_ai", ""),
|
|
79
|
+
intent=data.get("intent", []),
|
|
80
|
+
endpoint=data.get("endpoint", ""),
|
|
81
|
+
input=data.get("input", {}),
|
|
82
|
+
output=data.get("output", {}),
|
|
83
|
+
realtime=data.get("realtime", False),
|
|
84
|
+
requires_auth=data.get("requires_auth", True),
|
|
85
|
+
rate_limit=data.get("rate_limit", ""),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def matches_intent(self, task: str) -> bool:
|
|
89
|
+
"""Return True if any intent phrase matches the task string."""
|
|
90
|
+
task_lower = task.lower()
|
|
91
|
+
return any(
|
|
92
|
+
task_lower in phrase.lower() or phrase.lower() in task_lower
|
|
93
|
+
for phrase in self.intent
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def to_openai_tool(self) -> dict[str, Any]:
|
|
97
|
+
"""Convert this capability to an OpenAI function/tool definition."""
|
|
98
|
+
properties: dict[str, Any] = {}
|
|
99
|
+
required: list[str] = []
|
|
100
|
+
for name, spec in self.input.items():
|
|
101
|
+
if not isinstance(spec, dict):
|
|
102
|
+
continue
|
|
103
|
+
properties[name] = {
|
|
104
|
+
"type": spec.get("type", "string"),
|
|
105
|
+
"description": spec.get("description", ""),
|
|
106
|
+
}
|
|
107
|
+
if spec.get("enum"):
|
|
108
|
+
properties[name]["enum"] = spec["enum"]
|
|
109
|
+
if spec.get("required"):
|
|
110
|
+
required.append(name)
|
|
111
|
+
return {
|
|
112
|
+
"type": "function",
|
|
113
|
+
"function": {
|
|
114
|
+
"name": self.id,
|
|
115
|
+
"description": self.description_for_ai,
|
|
116
|
+
"parameters": {
|
|
117
|
+
"type": "object",
|
|
118
|
+
"properties": properties,
|
|
119
|
+
"required": required,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class Manifest:
|
|
127
|
+
service: Service
|
|
128
|
+
auth: Auth
|
|
129
|
+
capabilities: list[Capability]
|
|
130
|
+
agent_hints: dict[str, str] = field(default_factory=dict)
|
|
131
|
+
apia_version: str = "1.0"
|
|
132
|
+
raw: dict[str, Any] = field(default_factory=dict, repr=False)
|
|
133
|
+
|
|
134
|
+
# Convenience aliases
|
|
135
|
+
@property
|
|
136
|
+
def id(self) -> str:
|
|
137
|
+
return self.service.id
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def name(self) -> str:
|
|
141
|
+
return self.service.name
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def category(self) -> str:
|
|
145
|
+
return self.service.category
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def geo(self) -> list[str]:
|
|
149
|
+
return self.service.geo
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def is_free(self) -> bool:
|
|
153
|
+
return self.auth.anonymous_access
|
|
154
|
+
|
|
155
|
+
def find_capability(self, task: str) -> Capability | None:
|
|
156
|
+
"""Return the first capability whose intent matches the task."""
|
|
157
|
+
for cap in self.capabilities:
|
|
158
|
+
if cap.matches_intent(task):
|
|
159
|
+
return cap
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
def to_system_prompt(self) -> str:
|
|
163
|
+
"""Format this manifest as a system prompt section for an LLM."""
|
|
164
|
+
lines = [
|
|
165
|
+
f"## {self.service.name}",
|
|
166
|
+
f"{self.service.description_for_ai}",
|
|
167
|
+
f"Auth: {self.auth.type} | Cost: {self.auth.cost}",
|
|
168
|
+
f"Docs: {self.service.docs}",
|
|
169
|
+
"",
|
|
170
|
+
"### Capabilities",
|
|
171
|
+
]
|
|
172
|
+
for cap in self.capabilities:
|
|
173
|
+
lines.append(f"**[{cap.id}]** `{cap.endpoint}`")
|
|
174
|
+
lines.append(f"When: {cap.description_for_ai}")
|
|
175
|
+
lines.append(f"Intent: {', '.join(cap.intent[:5])}")
|
|
176
|
+
lines.append("")
|
|
177
|
+
if self.agent_hints:
|
|
178
|
+
lines.append("### Hints")
|
|
179
|
+
for k, v in self.agent_hints.items():
|
|
180
|
+
lines.append(f"- **{k}**: {v}")
|
|
181
|
+
return "\n".join(lines)
|
|
182
|
+
|
|
183
|
+
def to_openai_tools(self) -> list[dict[str, Any]]:
|
|
184
|
+
"""Convert all capabilities to OpenAI tool definitions."""
|
|
185
|
+
return [cap.to_openai_tool() for cap in self.capabilities]
|
|
186
|
+
|
|
187
|
+
@classmethod
|
|
188
|
+
def from_dict(cls, data: dict[str, Any]) -> "Manifest":
|
|
189
|
+
return cls(
|
|
190
|
+
service=Service.from_dict(data.get("service", {})),
|
|
191
|
+
auth=Auth.from_dict(data.get("auth", {})),
|
|
192
|
+
capabilities=[Capability.from_dict(c) for c in data.get("capabilities", [])],
|
|
193
|
+
agent_hints=data.get("agent_hints", {}),
|
|
194
|
+
apia_version=data.get("apia", "1.0"),
|
|
195
|
+
raw=data,
|
|
196
|
+
)
|
apia/registry.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""
|
|
2
|
+
APIA Registry — loads and searches the manifest index.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from .manifest import Manifest
|
|
11
|
+
from .exceptions import RegistryError, ManifestNotFoundError
|
|
12
|
+
|
|
13
|
+
REGISTRY_URL = (
|
|
14
|
+
"https://raw.githubusercontent.com/Komsomol39/apia-standard/main/registry.json"
|
|
15
|
+
)
|
|
16
|
+
MANIFEST_BASE_URL = (
|
|
17
|
+
"https://raw.githubusercontent.com/Komsomol39/apia-standard/main/manifests"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Registry:
|
|
22
|
+
"""
|
|
23
|
+
APIA manifest registry.
|
|
24
|
+
|
|
25
|
+
Usage::
|
|
26
|
+
|
|
27
|
+
registry = Registry()
|
|
28
|
+
apis = registry.find("send telegram message")
|
|
29
|
+
manifest = registry.get("telegram-bot")
|
|
30
|
+
tools = manifest.to_openai_tools()
|
|
31
|
+
prompt = manifest.to_system_prompt()
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, registry_url: str = REGISTRY_URL) -> None:
|
|
35
|
+
self._url = registry_url
|
|
36
|
+
self._index: list[dict[str, Any]] = []
|
|
37
|
+
self._cache: dict[str, Manifest] = {}
|
|
38
|
+
|
|
39
|
+
def _ensure_loaded(self) -> None:
|
|
40
|
+
if self._index:
|
|
41
|
+
return
|
|
42
|
+
try:
|
|
43
|
+
response = httpx.get(self._url, timeout=15.0)
|
|
44
|
+
response.raise_for_status()
|
|
45
|
+
data = response.json()
|
|
46
|
+
self._index = data.get("manifests", [])
|
|
47
|
+
except Exception as exc:
|
|
48
|
+
raise RegistryError(f"Failed to load APIA registry: {exc}") from exc
|
|
49
|
+
|
|
50
|
+
def list(
|
|
51
|
+
self,
|
|
52
|
+
category: str | None = None,
|
|
53
|
+
geo: str | None = None,
|
|
54
|
+
free_only: bool = False,
|
|
55
|
+
language: str | None = None,
|
|
56
|
+
) -> list[dict[str, Any]]:
|
|
57
|
+
"""
|
|
58
|
+
List registry entries with optional filters.
|
|
59
|
+
|
|
60
|
+
:param category: One of the 26 APIA categories e.g. "ai", "finance", "maps".
|
|
61
|
+
:param geo: ISO country code or "GLOBAL" e.g. "RU", "US", "GLOBAL".
|
|
62
|
+
:param free_only: Only return APIs with anonymous_access=True.
|
|
63
|
+
:param language: Primary language e.g. "ru", "en".
|
|
64
|
+
:returns: List of lightweight registry entries (not full manifests).
|
|
65
|
+
"""
|
|
66
|
+
self._ensure_loaded()
|
|
67
|
+
results = self._index
|
|
68
|
+
if category:
|
|
69
|
+
results = [m for m in results if m.get("category") == category]
|
|
70
|
+
if geo:
|
|
71
|
+
results = [m for m in results if geo in m.get("geo", []) or "GLOBAL" in m.get("geo", [])]
|
|
72
|
+
if free_only:
|
|
73
|
+
results = [m for m in results if m.get("anonymous_access")]
|
|
74
|
+
if language:
|
|
75
|
+
results = [m for m in results if m.get("language") == language]
|
|
76
|
+
return results
|
|
77
|
+
|
|
78
|
+
def categories(self) -> dict[str, int]:
|
|
79
|
+
"""Return a dict of {category: count} for all manifests."""
|
|
80
|
+
self._ensure_loaded()
|
|
81
|
+
from collections import Counter
|
|
82
|
+
return dict(Counter(m.get("category", "") for m in self._index))
|
|
83
|
+
|
|
84
|
+
def find(
|
|
85
|
+
self,
|
|
86
|
+
task: str,
|
|
87
|
+
category: str | None = None,
|
|
88
|
+
geo: str | None = None,
|
|
89
|
+
top_k: int = 3,
|
|
90
|
+
) -> list[Manifest]:
|
|
91
|
+
"""
|
|
92
|
+
Find the most relevant APIs for a natural language task.
|
|
93
|
+
|
|
94
|
+
Searches intent phrases in capabilities. Returns full Manifest objects.
|
|
95
|
+
|
|
96
|
+
:param task: Natural language description e.g. "send a telegram message".
|
|
97
|
+
:param category: Narrow to a specific category.
|
|
98
|
+
:param geo: Narrow to a specific geography.
|
|
99
|
+
:param top_k: Maximum number of results.
|
|
100
|
+
:returns: List of Manifest objects sorted by relevance.
|
|
101
|
+
"""
|
|
102
|
+
self._ensure_loaded()
|
|
103
|
+
task_lower = task.lower()
|
|
104
|
+
scored: list[tuple[int, dict[str, Any]]] = []
|
|
105
|
+
|
|
106
|
+
candidates = self._index
|
|
107
|
+
if category:
|
|
108
|
+
candidates = [m for m in candidates if m.get("category") == category]
|
|
109
|
+
if geo:
|
|
110
|
+
candidates = [m for m in candidates if geo in m.get("geo", []) or "GLOBAL" in m.get("geo", [])]
|
|
111
|
+
|
|
112
|
+
for entry in candidates:
|
|
113
|
+
score = 0
|
|
114
|
+
# Match description
|
|
115
|
+
if any(w in entry.get("description_for_ai", "").lower() for w in task_lower.split()):
|
|
116
|
+
score += 1
|
|
117
|
+
# Match capability intents (stronger signal)
|
|
118
|
+
for cap in entry.get("capabilities", []):
|
|
119
|
+
for intent in cap.get("intent", []):
|
|
120
|
+
if task_lower in intent.lower() or intent.lower() in task_lower:
|
|
121
|
+
score += 3
|
|
122
|
+
break
|
|
123
|
+
if score > 0:
|
|
124
|
+
scored.append((score, entry))
|
|
125
|
+
|
|
126
|
+
scored.sort(key=lambda x: -x[0])
|
|
127
|
+
top_entries = [e for _, e in scored[:top_k]]
|
|
128
|
+
|
|
129
|
+
# Load full manifests for top results
|
|
130
|
+
return [self.get(e["id"]) for e in top_entries]
|
|
131
|
+
|
|
132
|
+
def get(self, api_id: str) -> Manifest:
|
|
133
|
+
"""
|
|
134
|
+
Load a full manifest by API id.
|
|
135
|
+
|
|
136
|
+
:param api_id: The manifest id e.g. "openai", "telegram-bot", "stripe".
|
|
137
|
+
:raises ManifestNotFoundError: If the manifest cannot be found.
|
|
138
|
+
:returns: Full Manifest object.
|
|
139
|
+
"""
|
|
140
|
+
if api_id in self._cache:
|
|
141
|
+
return self._cache[api_id]
|
|
142
|
+
url = f"{MANIFEST_BASE_URL}/{api_id}/apia.json"
|
|
143
|
+
try:
|
|
144
|
+
response = httpx.get(url, timeout=10.0)
|
|
145
|
+
if response.status_code == 404:
|
|
146
|
+
raise ManifestNotFoundError(f"Manifest not found: {api_id!r}")
|
|
147
|
+
response.raise_for_status()
|
|
148
|
+
manifest = Manifest.from_dict(response.json())
|
|
149
|
+
self._cache[api_id] = manifest
|
|
150
|
+
return manifest
|
|
151
|
+
except ManifestNotFoundError:
|
|
152
|
+
raise
|
|
153
|
+
except Exception as exc:
|
|
154
|
+
raise RegistryError(f"Failed to load manifest {api_id!r}: {exc}") from exc
|
|
155
|
+
|
|
156
|
+
def build_system_prompt(
|
|
157
|
+
self,
|
|
158
|
+
apis: list[Manifest],
|
|
159
|
+
header: str = "You are an AI agent with access to the following APIs:",
|
|
160
|
+
) -> str:
|
|
161
|
+
"""
|
|
162
|
+
Build a system prompt containing multiple API manifests.
|
|
163
|
+
|
|
164
|
+
:param apis: List of Manifest objects to include.
|
|
165
|
+
:param header: Opening sentence for the system prompt.
|
|
166
|
+
:returns: Complete system prompt string ready to pass to an LLM.
|
|
167
|
+
"""
|
|
168
|
+
parts = [header, ""]
|
|
169
|
+
for manifest in apis:
|
|
170
|
+
parts.append(manifest.to_system_prompt())
|
|
171
|
+
parts.append("---")
|
|
172
|
+
parts.append(
|
|
173
|
+
"\nWhen the user asks something, identify which API and capability to use, "
|
|
174
|
+
"explain your reasoning, and provide the exact API call."
|
|
175
|
+
)
|
|
176
|
+
return "\n".join(parts)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: apia
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for APIA — AI-native API manifests standard
|
|
5
|
+
Project-URL: Homepage, https://github.com/Komsomol39/apia-standard
|
|
6
|
+
Project-URL: Repository, https://github.com/Komsomol39/apia-py
|
|
7
|
+
Project-URL: Issues, https://github.com/Komsomol39/apia-py/issues
|
|
8
|
+
Author: APIA Community
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: agents,ai,api,apia,llm,manifests
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Requires-Dist: httpx>=0.24.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: mypy; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# apia-py
|
|
31
|
+
|
|
32
|
+
Python SDK for [APIA](https://github.com/Komsomol39/apia-standard) — the open standard for AI-native API manifests.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install apia
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from apia import Registry
|
|
42
|
+
|
|
43
|
+
registry = Registry()
|
|
44
|
+
|
|
45
|
+
# Find APIs for a task
|
|
46
|
+
apis = registry.find("send a telegram message")
|
|
47
|
+
print(apis[0].name) # → Telegram Bot API
|
|
48
|
+
print(apis[0].category) # → social
|
|
49
|
+
|
|
50
|
+
# Get a specific manifest
|
|
51
|
+
manifest = registry.get("stripe")
|
|
52
|
+
print(manifest.service.description_for_ai)
|
|
53
|
+
|
|
54
|
+
# Convert to OpenAI tools
|
|
55
|
+
tools = manifest.to_openai_tools()
|
|
56
|
+
|
|
57
|
+
# Build a system prompt for an LLM
|
|
58
|
+
prompt = registry.build_system_prompt(apis)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Core API
|
|
62
|
+
|
|
63
|
+
### `Registry`
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from apia import Registry
|
|
67
|
+
|
|
68
|
+
r = Registry()
|
|
69
|
+
|
|
70
|
+
# Search by intent (natural language)
|
|
71
|
+
r.find("track DHL package") # → [Manifest, ...]
|
|
72
|
+
r.find("crypto price", category="finance") # → filtered
|
|
73
|
+
|
|
74
|
+
# Load a specific manifest
|
|
75
|
+
r.get("openai") # → Manifest
|
|
76
|
+
|
|
77
|
+
# List with filters
|
|
78
|
+
r.list(category="ai") # all AI APIs
|
|
79
|
+
r.list(geo="RU", free_only=True) # free Russian APIs
|
|
80
|
+
r.list(language="ru") # Russian-language APIs
|
|
81
|
+
|
|
82
|
+
# Categories overview
|
|
83
|
+
r.categories() # → {"ai": 25, "finance": 17, ...}
|
|
84
|
+
|
|
85
|
+
# Build LLM system prompt from multiple manifests
|
|
86
|
+
prompt = r.build_system_prompt(apis)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `Manifest`
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
m = r.get("stripe")
|
|
93
|
+
|
|
94
|
+
m.id # "stripe"
|
|
95
|
+
m.name # "Stripe"
|
|
96
|
+
m.category # "finance"
|
|
97
|
+
m.geo # ["GLOBAL"]
|
|
98
|
+
m.is_free # False
|
|
99
|
+
|
|
100
|
+
# Find capability by task
|
|
101
|
+
cap = m.find_capability("charge a customer")
|
|
102
|
+
cap.id # "create_payment_intent"
|
|
103
|
+
cap.endpoint # "POST https://api.stripe.com/v1/payment_intents"
|
|
104
|
+
|
|
105
|
+
# Export
|
|
106
|
+
m.to_openai_tools() # list of OpenAI function definitions
|
|
107
|
+
m.to_system_prompt() # formatted string for LLM system prompt
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Use with LLMs
|
|
111
|
+
|
|
112
|
+
### Anthropic Claude
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from apia import Registry
|
|
116
|
+
import anthropic
|
|
117
|
+
|
|
118
|
+
r = Registry()
|
|
119
|
+
apis = r.find("send telegram message")
|
|
120
|
+
system = r.build_system_prompt(apis)
|
|
121
|
+
|
|
122
|
+
client = anthropic.Anthropic()
|
|
123
|
+
response = client.messages.create(
|
|
124
|
+
model="claude-haiku-4-5",
|
|
125
|
+
max_tokens=1024,
|
|
126
|
+
system=system,
|
|
127
|
+
messages=[{"role": "user", "content": "Send 'Hello!' to chat 123456"}]
|
|
128
|
+
)
|
|
129
|
+
print(response.content[0].text)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### OpenAI function calling
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from apia import Registry
|
|
136
|
+
import openai
|
|
137
|
+
|
|
138
|
+
r = Registry()
|
|
139
|
+
manifest = r.get("openweathermap")
|
|
140
|
+
tools = manifest.to_openai_tools()
|
|
141
|
+
|
|
142
|
+
client = openai.OpenAI()
|
|
143
|
+
response = client.chat.completions.create(
|
|
144
|
+
model="gpt-4o-mini",
|
|
145
|
+
messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
|
|
146
|
+
tools=tools,
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/Komsomol39/apia-py
|
|
154
|
+
cd apia-py
|
|
155
|
+
pip install -e ".[dev]"
|
|
156
|
+
pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Related
|
|
160
|
+
|
|
161
|
+
- [apia-standard](https://github.com/Komsomol39/apia-standard) — manifest registry (257 APIs)
|
|
162
|
+
- [apia-js](https://github.com/Komsomol39/apia-js) — JavaScript/TypeScript SDK
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
apia/__init__.py,sha256=-aC12FBNJ8tjje_W6hobrHfVsgIgIfDZCnUQxcX3BFM,623
|
|
2
|
+
apia/exceptions.py,sha256=Xp2pnG93NtagVViz0lDLQZcTjBjvVG0g7TjYPxc3BIw,320
|
|
3
|
+
apia/manifest.py,sha256=HUqoTR_kPXddA3BnpbumBL6bGC4-brgWhM9Zm7mIydU,6176
|
|
4
|
+
apia/registry.py,sha256=6ZCJlZrjZIW9XLdi2wAkAxPn3d6KhFa-xI8iL9qzRgc,6324
|
|
5
|
+
apia-0.1.0.dist-info/METADATA,sha256=1votxv_t7bbArMALlTfkpPXKAz-DlpUbEsi8Y22z_GQ,4231
|
|
6
|
+
apia-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
apia-0.1.0.dist-info/RECORD,,
|