Appearance
URI
What is a Pinokio URI?
Requirements
Normally when we talk about APIs, we think of remote web servers that can be configured to expose one or more public routes, to which you can make requests.
However, Pinokio API was designed with a unique set of assumptions:
- Local First: Unlike traditional APIs that assume remote execution, Pinokio assumes the primary use case is interacting with the local machine to carry out tasks. This "local first" assumption is important for running AI tasks with privacy and without censorship.
- Zero Configuration: APIs should require zero configuration, and should "just work" out of the box.
- Globally unique resource identifiers for locally hosted APIs: This sounds like an oxymoron, but it is a unique challenge for autonomous AI engines, because while we assume that the actual task execution will happen locally, there needs to be some way to globally address these locally hosted endpoints. An example solution adopted by Pinokio is to use public git URIs for calling APIs.
Implementation
- JSON RPC: All API requests are JSON-RPC calls (instead of REST based)
- Automatic 1:1 mapping to local file paths: Unlike traditional APIs where you can only make requests to manually configured routes, Pinokio automatically creates an endpoint for EVERY file path under the Pinokio file system.
- Convention over Configuration: To automatically map JSON-RPC calls to local file paths, the Pinokio URI framework has a convention you can follow instead of manually specifying exposed routes.
Basically, Pinokio API URIs are unique resource identifiers that automatically map to locally installed folders via HTTP.
Instant URI
Convention over Configuration
Similar to how next.js automatically sets up routes based on convention, Pinokio lets you simply place your workspace folders under the ~/pinokio/api
top level folder, and the files inside your workspace folders will be instantly made available for API reqeusts, each available at URIs based on the file and folder paths.
URI Types
When making an API request to a Pinokio engine, you can use any of the following 3 URI schemes:
- Relative path
- HTTP path
Note that all of these URI schemes resolve to a local file path (even when using the HTTP path option).
Relative Path
A URI can be a file path. Let's imagine an example project:
~/pinokio
/test
run.json
/bin
install.json
Where the run.json
script looks like this:
json
{
"run": [{
"uri": "./bin/install.json",
"method": "run",
"params": {
...
}
}]
}
Note that the relative path ./bin/install.json
will be resolved against the path of the calling script (run.json
).
HTTP Path
An HTTP path exists ONLY for the folders you downloaded from a remote git repository.
Spec
The HTTP path is equivalent to the remote git URI, followed by the relative path within the git repository. It takes the following format:
<remote git URI>/<relative path>
For example, to reference a file at index.js
inside the https://github.com/cocktailpeanut/llama.git git repository, the HTTP path would look like:
https://github.com/cocktailpeanut/llama.git/index.js
How it works
Although the URL looks like a typical HTTP url, it's important to note that the you are not actually making an API request to https://github.com/cocktailpeanut/llama.git/index.js
- The URI is just a unique identifier used to identify the endpoint and the method to call
- The
remote git URI
andrelative path
combination is used to uniquely identify where the API files exist on your machine.
Where the files are stored
When you download an API from a git repository, Pinokio stores them under the hex version of the remote git URI.
For example, if the remote git URI you downloaded were https://github.com/cocktailpeanut/llama.git - Pinokio automatically creates a folder named 0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974
(This is the hex version of the URI) and downloads the remote git repository to that folder.
~
/pinokio
/api
/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974
Note that Pinokio automatically generates these folder names when you download APIs from git repositories, but when you're building your own app or api locally, you can name the folders whatever you want:
In above example, the first 4 folders with hex names are the downloaded APIs, whereas the next 2 (helloworld and test) are locally created folders.
Rules
Some rules:
- The
<remote git URI>
must end with.git
(This is the standard way to reference git repositories) - The URL info is derived from the
.git/config
file within the downloaded repository.
Example
This section will explain how the URI to local path resolution is actually carried out.
json
{
"run": [{
"uri": "https://github.com/cocktailpeanut/llama.git/index.js",
"method": "run",
"params": {
"message": {
"p": "### Instruction\n\nWrite a brief controversial opinion.\n\n### Response\n\n",
"m": "../models/stable-vicuna/13b_q4_0.bin",
"n": 256
}
}
}]
}
In above example,
- The
https://github.com/cocktailpeanut/llama.git
part is the<remote git URI>
- The
index.js
part is the<relative path>
Resolution algorithm:
- Begin URI parsing: Pinokio sees the uri
https://github.com/cocktailpeanut/llama.git/index.js
- Git uri extraction: the git repository uri is extracted from the full uri:
https://github.com/cocktailpeanut/llama.git
- Git config match: Pinokio checks if there is any top level folder under
~/pinokio/api
whose.git/config
includes a matching remote URL - Endpoint resolution: If there's a match (let's say it finds one at
~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974
), the resolution is complete, and the request is routed to the module inside the matched local folder (~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974
). - Route resolution: Now that the endpoint has been resolved, Pinokio looks at the
<relative path>
part of the full URI. In this case it'sindex.js
. Pinokio takes the resolved endpoint path from the previous step (~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974
) and resolves the rest of the file pathindex.js
, and ends up with the full local path~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974/index.js
. - Method resolution: Pinokio then looks at the JavaScript class file
~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974/index.js
and finds the methodrun
- Method Execution: Now that Pinokio knows which method inside which file needs to be executed, the only thing left is to actually execute the method by passing the
params
attribute.
The ~/pinokio/api/0x68747470733a2f2f6769746875622e636f6d2f636f636b7461696c7065616e75742f6c6c616d612e676974/index.js
must follow the API framework convention, and may look something like this:
javascript
class Llama {
async run (request, ondata, kernel) {
// do stuff with the request.params
}
}
module.exports = Llama
The request
parameter will contain:
json
{
uri: 'https://github.com/malfunctionize/llama.git/index.js',
method: 'run',
params: {
message: {
p: "### Instruction\n\nWrite a brief controversial opinion.\n\n### Response\n\n",
m: '../models/stable-vicuna/13b_q4_0.bin',
n: 256
}
},
dirname: '/Users/x/pinokio/api/llama',
cwd: '/Users/x/pinokio/api/llama/example',
root: 'https://github.com/malfunctionize/llama.git/example/stable-vicuna-13b-q4_0.json',
current: 0,
next: null
}
uri
: the full endpoint URI from the begin URI parsing step.method
: the RPC method passed in.params
: the RPC params passed in.dirname
: the resolved local path from the endpoint resolution step. This is the path under which the resolved module existscwd
: the current execution path. This is the folder that contains the script that is running currently.root
: the full path for the script file that is currently running.current
: The current instruction index within therun
array. In this case it's 0 since it's the first instruction in therun
array.next
: The next instruction index to be executed after the current request ends. In this case it'snull
since there is only one item in therun
array (the current instruction), andnull
means the program will halt after this step.