-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Python support to calling the API
- Loading branch information
1 parent
7a64385
commit a744653
Showing
2 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
|
||
from . api import * | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,336 @@ | ||
|
||
import requests | ||
import json | ||
import dataclasses | ||
import base64 | ||
|
||
from trustgraph.knowledge import hash | ||
|
||
class ProtocolException(Exception): | ||
pass | ||
|
||
class ApplicationException(Exception): | ||
pass | ||
|
||
class Uri(str): | ||
def is_uri(self): return True | ||
def is_literal(self): return False | ||
|
||
class Literal(str): | ||
def is_uri(self): return False | ||
def is_literal(self): return True | ||
|
||
@dataclasses.dataclass | ||
class Triple: | ||
s : str | ||
p : str | ||
o : str | ||
|
||
class Api: | ||
|
||
def __init__(self, url="http://localhost:8088/"): | ||
|
||
self.url = url | ||
|
||
if not url.endswith("/"): | ||
self.url += "/" | ||
|
||
self.url += "api/v1/" | ||
|
||
def check_error(self, response): | ||
|
||
if "error" in response: | ||
|
||
try: | ||
msg = response["error"]["message"] | ||
tp = response["error"]["message"] | ||
except: | ||
raise ApplicationException( | ||
"Error, but the error object is broken" | ||
) | ||
|
||
raise ApplicationException(f"{tp}: {msg}") | ||
|
||
def text_completion(self, system, prompt): | ||
|
||
# The input consists of system and prompt strings | ||
input = { | ||
"system": system, | ||
"prompt": prompt | ||
} | ||
|
||
url = f"{self.url}text-completion" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException(f"Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
try: | ||
return object["response"] | ||
except: | ||
raise ProtocolException(f"Response not formatted correctly") | ||
|
||
def agent(self, question): | ||
|
||
# The input consists of a question | ||
input = { | ||
"question": question | ||
} | ||
|
||
url = f"{self.url}agent" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException(f"Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
try: | ||
return object["answer"] | ||
except: | ||
raise ProtocolException(f"Response not formatted correctly") | ||
|
||
def graph_rag(self, question): | ||
|
||
# The input consists of a question | ||
input = { | ||
"query": question | ||
} | ||
|
||
url = f"{self.url}graph-rag" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException(f"Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
try: | ||
return object["response"] | ||
except: | ||
raise ProtocolException(f"Response not formatted correctly") | ||
|
||
def embeddings(self, text): | ||
|
||
# The input consists of a text block | ||
input = { | ||
"text": text | ||
} | ||
|
||
url = f"{self.url}embeddings" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException(f"Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
try: | ||
return object["vectors"] | ||
except: | ||
raise ProtocolException(f"Response not formatted correctly") | ||
|
||
def prompt(self, id, variables): | ||
|
||
# The input consists of system and prompt strings | ||
input = { | ||
"id": id, | ||
"variables": variables | ||
} | ||
|
||
url = f"{self.url}prompt" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException("Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
if "text" in object: | ||
return object["text"] | ||
|
||
if "object" in object: | ||
try: | ||
return json.loads(object["object"]) | ||
except Exception as e: | ||
raise ProtocolException( | ||
"Returned object not well-formed JSON" | ||
) | ||
|
||
raise ProtocolException("Response not formatted correctly") | ||
|
||
def triples_query(self, s=None, p=None, o=None, limit=10000): | ||
|
||
# The input consists of system and prompt strings | ||
input = { | ||
"limit": limit | ||
} | ||
|
||
if s: input["s"] = s | ||
if p: input["p"] = p | ||
if o: input["o"] = o | ||
|
||
url = f"{self.url}triples-query" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
try: | ||
# Parse the response as JSON | ||
object = resp.json() | ||
except: | ||
raise ProtocolException("Expected JSON response") | ||
|
||
self.check_error(resp) | ||
|
||
if "response" not in object: | ||
raise ProtocolException("Response not formatted correctly") | ||
|
||
def to_value(x): | ||
if x["e"]: return Uri(x["v"]) | ||
return Literal(x["v"]) | ||
|
||
return [ | ||
Triple( | ||
s=to_value(t["s"]), | ||
p=to_value(t["p"]), | ||
o=to_value(t["o"]) | ||
) | ||
for t in object["response"] | ||
] | ||
|
||
return object["response"] | ||
|
||
def load_document(self, document, id=None, metadata=None): | ||
|
||
if id is None: | ||
|
||
if metadata is not None: | ||
|
||
# Situation makes no sense. What can the metadata possibly | ||
# mean if the caller doesn't know the document ID. | ||
# Metadata should relate to the document by ID | ||
raise RuntimeError("Can't specify metadata without id") | ||
|
||
id = hash(document) | ||
|
||
triples = [] | ||
|
||
def emit(t): | ||
triples.append(t) | ||
|
||
if metadata: | ||
metadata.emit( | ||
lambda t: triples.append({ | ||
"s": t.s.value, | ||
"p": t.p.value, | ||
"o": t.o.value | ||
}) | ||
) | ||
|
||
input = { | ||
"id": id, | ||
"metadata": triples, | ||
"data": base64.b64encode(document).decode("utf-8"), | ||
} | ||
|
||
url = f"{self.url}load/document" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|
||
def load_text(self, text, id=None, metadata=None, charset="utf-8"): | ||
|
||
if id is None: | ||
|
||
if metadata is not None: | ||
|
||
# Situation makes no sense. What can the metadata possibly | ||
# mean if the caller doesn't know the document ID. | ||
# Metadata should relate to the document by ID | ||
raise RuntimeError("Can't specify metadata without id") | ||
|
||
id = hash(text) | ||
|
||
triples = [] | ||
|
||
if metadata: | ||
metadata.emit( | ||
lambda t: triples.append({ | ||
"s": t.s.value, | ||
"p": t.p.value, | ||
"o": t.o.value | ||
}) | ||
) | ||
|
||
input = { | ||
"id": id, | ||
"metadata": triples, | ||
"charset": charset, | ||
"text": base64.b64encode(text).decode("utf-8"), | ||
} | ||
|
||
url = f"{self.url}load/text" | ||
|
||
# Invoke the API, input is passed as JSON | ||
resp = requests.post(url, json=input) | ||
|
||
# Should be a 200 status code | ||
if resp.status_code != 200: | ||
raise ProtocolException(f"Status code {resp.status_code}") | ||
|