Skip to content

Commit

Permalink
Merge pull request #11 from ainft-team/release/0.1.0
Browse files Browse the repository at this point in the history
Release/0.1.0
  • Loading branch information
jiyoung-an authored Mar 26, 2024
2 parents 44588f8 + d43f178 commit 2f1f9e2
Show file tree
Hide file tree
Showing 20 changed files with 2,057 additions and 1 deletion.
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11.8
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,29 @@
# ainft-py
# ainft-py

[![PyPi Version](https://img.shields.io/pypi/v/ainft-py.svg)](https://pypi.python.org/pypi/ainft-py/)

A python version of [ainft-js](https://github.com/ainft-team/ainft-js).

## Installation

```sh
pip install ainft-py
```

## Usage

```python
import os
from ainft import Ainft

ainft = Ainft(
private_key=os.environ.get("AIN_PRIVATE_KEY"),
api_url="https://ainft-api-dev.ainetwork.ai",
blockchain_url="https://testnet-api.ainetwork.ai",
chain_id=0,
)
```

## Requirements

Python version should be at least 3.8 but less than 3.12.
21 changes: 21 additions & 0 deletions ainft/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations

from .client import Ainft
from .types import (
Thread,
Message,
TransactionResult,
ThreadTransactionResult,
MessageTransactionResult,
)
from .utils import truncate_text

__all__ = [
"Ainft",
"Thread",
"Message",
"TransactionResult",
"ThreadTransactionResult",
"MessageTransactionResult",
"truncate_text",
]
7 changes: 7 additions & 0 deletions ainft/chat/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .chat import Chat
from .threads import Threads

__all__ = [
"Chat",
"Threads",
]
21 changes: 21 additions & 0 deletions ainft/chat/chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations

from functools import cached_property

from ain.ain import Ain

from .threads import Threads
from .messages import Messages


class Chat:
def __init__(self, ain: Ain) -> None:
self._ain = ain

@cached_property
def threads(self) -> Threads:
return Threads(self._ain)

@cached_property
def messages(self) -> Messages:
return Messages(self._ain)
121 changes: 121 additions & 0 deletions ainft/chat/messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from __future__ import annotations

import json
from typing import List

from ain.ain import Ain

from ..types import (
SetOperation,
SetMultiOperation,
TransactionInput,
Message,
MessageTransactionResult,
)
from ..utils import *


class Messages:
def __init__(self, ain: Ain):
self._ain = ain

async def store(
self,
*,
messages: List[Message],
object_id: str,
token_id: str,
) -> MessageTransactionResult:
"""
Store a list of messages.
Args:
messages: The list of messages.
object_id: The ID of the AINFT object.
token_id: The ID of the AINFT token.
"""
app_id = get_app_id(object_id)
user_addr = validate_user_address(self._ain.wallet)

await self._validate(app_id, token_id, user_addr, messages)

return await self._send_tx_for_store_message(**dict(
messages=messages,
app_id=app_id,
token_id=token_id,
address=user_addr,
))

async def _validate(
self, app_id: str, token_id: str, address: str, messages: List[Message]
):
await validate_app(app_id, self._ain.db)
await validate_token(app_id, token_id, self._ain.db)
for message in messages:
await validate_thread(
app_id, token_id, address, message.thread_id, self._ain.db
)
validate_message_id(message.id)

async def _send_tx_for_store_message(self, **kwargs) -> MessageTransactionResult:
timestamp = int(now())
tx_body = self._build_tx_body_for_store_message(timestamp=timestamp, **kwargs)
tx_result = await self._ain.sendTransaction(tx_body)

if not is_tx_success(tx_result):
raise RuntimeError(f"Failed to send transaction: {json.dumps(tx_result)}")

return self._format_tx_result(tx_result=tx_result, **kwargs)

def _build_tx_body_for_store_message(
self,
app_id: str,
token_id: str,
messages: List[Message],
address: str,
timestamp: int,
) -> TransactionInput:
ops = []
for msg in messages:
path = join_paths(
[
"apps",
app_id,
"tokens",
token_id,
"ai",
"ainize_openai",
"history",
address,
"threads",
msg.thread_id,
"messages",
str(msg.created_at),
]
)
content = truncate_text(msg.content, 200)
content = content.encode("unicode-escape").decode("ASCII")
message = {
"id": msg.id,
"role": msg.role,
"content": content,
**({"metadata": msg.metadata} if msg.metadata else {}),
}
op = SetOperation(type="SET_VALUE", ref=path, value=message)
ops.append(op)

multi_op = SetMultiOperation(type="SET", op_list=ops)
return TransactionInput(
operation=multi_op,
timestamp=timestamp,
nonce=-1,
address=address,
gas_price=500,
)

def _format_tx_result(
self, tx_result: dict, messages: List[Message], **kwargs
) -> MessageTransactionResult:
return MessageTransactionResult(messages=messages, **tx_result)
113 changes: 113 additions & 0 deletions ainft/chat/threads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from __future__ import annotations

import json
from typing import Optional

from ain.ain import Ain

from ..types import SetOperation, TransactionInput, Thread, ThreadTransactionResult
from ..utils import *


class Threads:
def __init__(self, ain: Ain):
self._ain = ain

async def store(
self,
*,
thread_id: str,
object_id: str,
token_id: str,
metadata: Optional[dict] = None,
) -> ThreadTransactionResult:
"""
Store a thread.
Args:
thread_id: The ID of the thread,
must be a 20-character alphanumeric starting with 'thread_' or a uuid4.
object_id: The ID of the AINFT object.
token_id: The ID of the AINFT token.
metadata: The metadata can contain up to 16 key-value pairs,
with the keys limited to 64 characters and the values to 512 characters.
"""
app_id = get_app_id(object_id)
user_addr = validate_user_address(self._ain.wallet)

await self._validate(app_id, token_id, thread_id)

return await self._send_tx_for_store_thread(**dict(
thread_id=thread_id,
app_id=app_id,
token_id=token_id,
address=user_addr,
metadata=metadata,
))

async def _validate(self, app_id: str, token_id: str, thread_id: str):
await validate_app(app_id, self._ain.db)
await validate_token(app_id, token_id, self._ain.db)
validate_thread_id(thread_id)

async def _send_tx_for_store_thread(self, **kwargs) -> ThreadTransactionResult:
timestamp = int(now())
tx_body = self._build_tx_body_for_store_thread(timestamp=timestamp, **kwargs)
tx_result = await self._ain.sendTransaction(tx_body)

if not is_tx_success(tx_result):
raise RuntimeError(f"Failed to send transaction: {json.dumps(tx_result)}")

return self._format_tx_result(
tx_result=tx_result, timestamp=timestamp, **kwargs
)

def _build_tx_body_for_store_thread(
self,
app_id: str,
token_id: str,
thread_id: str,
address: str,
timestamp: int,
metadata: Optional[dict],
) -> TransactionInput:
thread_path = join_paths(
[
"apps",
app_id,
"tokens",
token_id,
"ai",
"ainize_openai",
"history",
address,
"threads",
thread_id,
]
)
thread = {
"messages": True,
**({"metadata": metadata} if metadata else {}),
}
op = SetOperation(type="SET_VALUE", ref=thread_path, value=thread)
return TransactionInput(
operation=op,
timestamp=timestamp,
nonce=-1,
address=address,
gas_price=500,
)

def _format_tx_result(
self,
tx_result: dict,
timestamp: int,
thread_id: str,
**kwargs,
) -> ThreadTransactionResult:
metadata = kwargs.get("metadata", {})
thread = Thread(id=thread_id, metadata=metadata, created_at=timestamp)
return ThreadTransactionResult(thread=thread, **tx_result)
30 changes: 30 additions & 0 deletions ainft/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from __future__ import annotations

from typing import Literal

from ain.ain import Ain

from .chat import Chat


class Ainft:
def __init__(
self,
*,
private_key: str,
api_url: str,
blockchain_url: str,
chain_id: Literal[0, 1],
):
self._base_url = api_url
self._blockchain_url = blockchain_url
self._chain_id = chain_id

self._ain = Ain(self._blockchain_url, self._chain_id)
self._set_default_account(private_key)

self.chat = Chat(self._ain)

def _set_default_account(self, private_key: str):
self._ain.wallet.clear()
self._ain.wallet.addAndSetDefaultAccount(private_key)
4 changes: 4 additions & 0 deletions ainft/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from __future__ import annotations

class AinftError(Exception):
pass
Loading

0 comments on commit 2f1f9e2

Please sign in to comment.