Compare commits

..
24 Commits
Author SHA1 Message Date
retoor 29471a61f4 chore: bump client dependency to v2.3.1 and add missing self.client attribute in AsyncClient 2024-12-09 19:29:16 +00:00
retoor 47f8f231aa chore: remove stale websocket assignment from AsyncClient._connection method 2024-12-09 19:28:36 +00:00
retoor 95ededec5a fix: fix syntax error in AsyncClient.__aexit__ by removing stray zero argument from close call 2024-12-09 19:27:54 +00:00
retoor f2357ae2ac feat: add lazy connection initialization via _connection property in AsyncClient 2024-12-09 19:16:18 +00:00
retoor ff9d1ae170 feat: add ws attribute and dataset support to AsyncClient chat method
- Initialize self.ws to None and assign websocket connection in __aenter__
- Close client connection and reset ws to None in __aexit__
- Extend chat method signature with optional datasets parameter, defaulting to empty list
- Pass datasets argument to underlying client.chat call
- Fix close method to set self.ws to None instead of self.client
2024-12-09 17:49:50 +00:00
retoor 16f96316c0 chore: bump version to 14.4.5 and make close methods async with destructor warning 2024-12-02 11:28:53 +00:00
retoor 69e40a89fe chore: bump yura version from 14.4.3 to 14.4.4 and convert close method to sync
Update version string in setup.cfg metadata and PKG-INFO to reflect new release 14.4.4. Also change AsyncRPCClient.close() from async def to regular def, removing unnecessary async overhead since the method contains no await expressions.
2024-12-02 11:24:37 +00:00
retoor 61073f4772 fix: correct 'sefl' typo to 'self' in AsyncClient.__del__ and bump version to 14.4.3 2024-12-02 11:21:08 +00:00
retoor bf3d86ad2c fix: bump version to 14.4.2 and fix missing self parameter in AsyncRPCClient.close method 2024-12-02 11:19:30 +00:00
retoor 470239ec9f chore: bump package version from 14.4.0 to 14.4.1 in PKG-INFO metadata 2024-12-02 11:07:23 +00:00
retoor dd27f2506e fix: correct off-by-one error in pagination offset calculation for user list endpoint 2024-12-02 11:07:00 +00:00
retoor 13253b2802 fix: correct .gitignore pattern and bump yura version to 14.4.0 with retry logic in client 2024-12-02 11:02:33 +00:00
retoor 9cc3a486c1 style: reformat Python imports, whitespace, and indentation across CLI, client, and makefile 2024-12-01 08:00:25 +00:00
retoor d2ad277066 fix: correct typo in user authentication error message for invalid credentials 2024-11-29 20:22:50 +00:00
retoor bcb4bbc197 feat: bump version to 14.3.9 and add publish command to make script
Extract build logic into reusable function and add new "publish" target that runs build then uploads dist artifacts to gitea via twine. Update version in setup.cfg and egg-info metadata from 14.3.7 to 14.3.9.
2024-11-28 02:53:24 +00:00
retoor 923bc09c14 chore: remove old dist artifacts and update .gitignore with .pypirc exclusion 2024-11-28 02:41:34 +00:00
retoor e57a395dd4 feat: add yura async AI client package with websocket chat support and CLI entry point 2024-11-27 22:48:03 +00:00
retoor b9d20b4a3d feat: add console entry point and async chat streaming in client module
- Register yura CLI entry point in setup.cfg pointing to yura.cli:run
- Add new src/yura/cli.py module for command-line interface
- Implement streaming chat method in AsyncClient with websocket connection handling
- Fix ensure_connection method signature to include self parameter
- Remove redundant done check from cli_client loop to avoid premature exit
2024-11-27 22:46:21 +00:00
retoor 2ec22695f8 chore: bump yura version from 1.33.7 to 14.3.7 in setup.cfg and egg-info PKG-INFO 2024-11-27 20:57:31 +00:00
retoor 1d1ff6b1da feat: add initial package structure with core module scaffolding
Add the first release distribution files for yura version 1.33.7, including both the wheel and source tarball, establishing the foundational package layout and core module skeleton for the project.
2024-11-27 20:52:59 +00:00
retoor 6f41dd00dc chore: downgrade version from 13.3.7 to 1.33.7 and remove console entry point for yura client 2024-11-27 20:52:35 +00:00
retoor b62aff7d3a chore: add package.json with project metadata and dependencies 2024-11-27 20:26:27 +00:00
retoor 114a22267b docs: add project overview, install, build, cli usage, and python example to readme 2024-11-24 16:11:38 +00:00
retoor ffcbd65514 feat: add initial project structure with core files and configuration
Set up the yura async AI client project scaffold including .gitignore, build system configuration in pyproject.toml and setup.cfg, a make script for venv and build automation, the AsyncClient class in src/yura/client.py with websocket-based chat communication, and pre-built distribution artifacts for version 13.3.7.
2024-11-24 16:03:43 +00:00
41 changed files with 243 additions and 352 deletions
+3 -1
View File
@@ -1,2 +1,4 @@
.venv
__*
__pycache__
.pypirc
.history
+18
View File
@@ -0,0 +1,18 @@
PYTHON=.venv/bin/python
PIP=.venv/bin/pip
all: build
ensure_env:
-@python3 -m venv .venv
build: ensure_env
$(PIP) install -e .
$(PIP) install build
$(PIP) install shed
$(PYTHON) -m shed
$(PYTHON) -m build
+4 -1
View File
@@ -20,12 +20,15 @@ yura ws://[host]:[port]/[path]/
## Python
```python
import asyncio
from yura.client import AsyncClient
async def communicate():
client = AsyncClient("ws://[host]:[port]/[path]/")
async for response in client.chat("Your prompt"):
print(response)
print(response)
asyncio.run(communicate())
```
Binary file not shown.
BIN
View File
Binary file not shown.
-42
View File
@@ -1,42 +0,0 @@
Metadata-Version: 2.1
Name: yura
Version: 14.3.7
Summary: Yura async AI client
Author: retoor
Author-email: retoor@retoor.io
License: MIT
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: websockets
# Yura LLM Client for Katya server
Part of project with as target replacing the native ollama protocol. This protocol supports streaming and is usable trough https and it is possible to directly attach a web client to the backend.
## Install
```bash
pip install -e .
```
## Build
```bash
make build
```
## Command line usage
```bash
yura ws://[host]:[port]/[path]/
```
## Python
```python
import asyncio
from yura.client import AsyncClient
async def communicate():
client = AsyncClient("ws://[host]:[port]/[path]/")
async for response in client.chat("Your prompt"):
print(response)
asyncio.run(communicate())
```
-31
View File
@@ -1,31 +0,0 @@
# Yura LLM Client for Katya server
Part of project with as target replacing the native ollama protocol. This protocol supports streaming and is usable trough https and it is possible to directly attach a web client to the backend.
## Install
```bash
pip install -e .
```
## Build
```bash
make build
```
## Command line usage
```bash
yura ws://[host]:[port]/[path]/
```
## Python
```python
import asyncio
from yura.client import AsyncClient
async def communicate():
client = AsyncClient("ws://[host]:[port]/[path]/")
async for response in client.chat("Your prompt"):
print(response)
asyncio.run(communicate())
```
-3
View File
@@ -1,3 +0,0 @@
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"
-25
View File
@@ -1,25 +0,0 @@
[metadata]
name = yura
version = 14.3.7
description = Yura async AI client
author = retoor
author_email = retoor@retoor.io
license = MIT
long_description = file: README.md
long_description_content_type = text/markdown
[options]
packages = find:
package_dir =
= src
python_requires = >=3.7
install_requires =
websockets
[options.packages.find]
where = src
[egg_info]
tag_build =
tag_date = 0
-42
View File
@@ -1,42 +0,0 @@
Metadata-Version: 2.1
Name: yura
Version: 14.3.7
Summary: Yura async AI client
Author: retoor
Author-email: retoor@retoor.io
License: MIT
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: websockets
# Yura LLM Client for Katya server
Part of project with as target replacing the native ollama protocol. This protocol supports streaming and is usable trough https and it is possible to directly attach a web client to the backend.
## Install
```bash
pip install -e .
```
## Build
```bash
make build
```
## Command line usage
```bash
yura ws://[host]:[port]/[path]/
```
## Python
```python
import asyncio
from yura.client import AsyncClient
async def communicate():
client = AsyncClient("ws://[host]:[port]/[path]/")
async for response in client.chat("Your prompt"):
print(response)
asyncio.run(communicate())
```
-11
View File
@@ -1,11 +0,0 @@
README.md
pyproject.toml
setup.cfg
src/yura/__init__.py
src/yura/__main__.py
src/yura/client.py
src/yura.egg-info/PKG-INFO
src/yura.egg-info/SOURCES.txt
src/yura.egg-info/dependency_links.txt
src/yura.egg-info/requires.txt
src/yura.egg-info/top_level.txt
@@ -1 +0,0 @@
-1
View File
@@ -1 +0,0 @@
websockets
-1
View File
@@ -1 +0,0 @@
yura
View File
View File
-106
View File
@@ -1,106 +0,0 @@
import asyncio
import websockets
import json
import sys
class AsyncClient:
def __init__(self, url="ws://127.0.0.1:8470"):
self.url = url
self.ws = None
self.queue_in = asyncio.Queue()
self.queue_out = asyncio.Queue()
self.communication_task = None
async def ensure_connection():
if not self.ws:
self.ws = await websockets.connect(self.url)
return self.ws
async def ensure_communication(self):
if not self.communication_task:
self.communication_task = asyncio.create_task(self.communicate())
return self.communication_task
async def chat(self, message):
await self.ensure_communication()
await self.queue_out.put(message)
while True:
while True:
try:
response = await asyncio.wait_for(self.queue_in.get(), 0.1)
except asyncio.TimeoutError:
continue
break
yield response
if response["done"]:
break
async def communicate(self):
loop = asyncio.get_event_loop()
async with websockets.connect(self.url) as websocket:
while True:
message_content = None
while not message_content:
try:
message_content = await asyncio.wait_for(
self.queue_out.get(), 0.1
)
except asyncio.TimeoutError:
continue
response = await websocket.send(json.dumps(message_content))
while True:
response = json.loads(await websocket.recv())
if response["done"]:
break
await self.queue_in.put(response)
await self.queue_in.put(response)
async def cli_client(url="ws://127.0.0.1:8470"):
loop = asyncio.get_event_loop()
async_client = AsyncClient(url)
while True:
sys.stdout.write("> ")
sys.stdout.flush()
message_content = await loop.run_in_executor(None, sys.stdin.readline)
async for response in async_client.chat(message_content):
print(response["content"], end="", flush=True)
if response["done"]:
break
print("")
def main():
url = "ws://127.0.0.1:8470"
try:
url = sys.argv[1]
except IndexError:
pass
asyncio.run(cli_client(url))
if __name__ == "__main__":
main()
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+18 -7
View File
@@ -1,14 +1,24 @@
#!/usr/bin/env python3
import pathlib
import os
import os
import pathlib
import sys
args = sys.argv[1:]
args_string = " ".join(args)
def install():
os.system("./.venv/bin/python -m pip install -e .")
def build():
os.system("./.venv/bin/python -m pip install build")
os.system("rm -r dist")
os.system("./.venv/bin/python -m build .")
os.system("./.venv/bin/python -m pip install black")
os.system("./.venv/bin/python -m black .")
if not pathlib.Path(".venv").exists():
os.system("python3 -m venv .venv")
install()
@@ -17,11 +27,12 @@ if "install" in args:
install()
if "build" in sys.argv:
os.system("./.venv/bin/python -m pip install build")
os.system("./.venv/bin/python -m build .")
os.system("./.venv/bin/python -m pip install black")
os.system("./.venv/bin/python -m black .")
build()
if "publish" in sys.argv:
build()
os.system("./.venv/bin/python -m pip install twine")
os.system("./.venv/bin/python -m twine upload --repository gitea dist/*")
if "run" in sys.argv:
os.system("./.venv/bin/yura " + args_string)
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata]
name = yura
version = 14.3.7
version = 14.4.5
description = Yura async AI client
author = retoor
author_email = retoor@retoor.io
+5 -2
View File
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: yura
Version: 14.3.7
Version: 14.4.5
Summary: Yura async AI client
Author: retoor
Author-email: retoor@retoor.io
@@ -31,12 +31,15 @@ yura ws://[host]:[port]/[path]/
## Python
```python
import asyncio
from yura.client import AsyncClient
async def communicate():
client = AsyncClient("ws://[host]:[port]/[path]/")
async for response in client.chat("Your prompt"):
print(response)
print(response)
asyncio.run(communicate())
```
+2
View File
@@ -5,6 +5,8 @@ src/yura/__init__.py
src/yura/__main__.py
src/yura/cli.py
src/yura/client.py
src/yura/model.py
src/yura/server.py
src/yura.egg-info/PKG-INFO
src/yura.egg-info/SOURCES.txt
src/yura.egg-info/dependency_links.txt
View File
View File
+2 -1
View File
@@ -1,7 +1,8 @@
from yura.client import cli_client
import asyncio
import sys
from yura.client import cli_client
def run():
try:
+111 -76
View File
@@ -1,109 +1,144 @@
import asyncio
import websockets
import json
import sys
import websockets
class AsyncRPCClient:
def __init__(self, url):
self.url = url
self._ws = None
@property
async def ws(self):
if not self._ws:
self._ws = await websockets.connect(self.url)
return self._ws
async def __aiter__(self):
response = None
ws = await self.ws
while True:
response_raw = await ws.recv()
response = json.loads(response_raw)
yield response
if response.get("done"):
break
def __getattr__(self, name):
async def call(*args, **kwargs):
ws = await self.ws
response = None
while True:
try:
await ws.send(
json.dumps(
{"method": name, "args": args, "kwargs": kwargs}, default=str
)
)
response = await ws.recv()
break
except Exception as ex:
print(ex)
print("Trying again in 1 seconds.")
self.close()
await asyncio.sleep(1)
return json.loads(response)
return call
async def close(self):
if self._ws:
await self._ws.close()
self._ws = None
def __del__(self):
if self._ws:
raise Exception("ASyncRPCClient destructed without closing connection properly.")
class AsyncClient:
def __init__(self, url="ws://127.0.0.1:8470"):
self.url = url
self.ws = None
self.client = None
self.queue_in = asyncio.Queue()
self.queue_out = asyncio.Queue()
self.communication_task = None
self.session_id = None
self.ws = None
async def ensure_connection(self):
@property
def _connection(self):
if not self.client:
self.client = AsyncRPCClient(self.url)
return self.client
# if not self.ws:
self.ws = await websockets.connect(self.url)
async def __aenter__(self):
conn = self._connection
return self
return self.ws
async def __aexit__(self, *args, **kwargs):
await self.close()
async def ensure_communication(self):
async def create(self, name, extends, system):
return await self._connection.create(name=name, extends=extends, system=system)
if not self.communication_task:
self.communication_task = asyncio.create_task(self.communicate())
return self.communication_task
async def chat(self, message):
ws = await self.ensure_connection()
await ws.send(json.dumps(message))
response = None
while True:
response_raw = await ws.recv()
response = json.loads(response_raw)
if not response["done"]:
yield response
else:
break
if response and response["done"]:
yield response
async def chatw(self, message):
await self.ensure_communication()
await self.queue_out.put(message)
while True:
while True:
try:
response = await asyncio.wait_for(self.queue_in.get(), 0.1)
except asyncio.TimeoutError:
continue
break
yield response
if response["done"]:
async def chat(self, token, message, datasets=None):
yield await self._connection.chat(uid=token, message=message, datasets=datasets or [])
async for msg in self.client:
yield msg
if msg.get("done"):
break
async def communicate(self):
loop = asyncio.get_event_loop()
async with websockets.connect(self.url) as websocket:
while True:
message_content = None
while not message_content:
try:
message_content = await asyncio.wait_for(
self.queue_out.get(), 0.1
)
except asyncio.TimeoutError:
continue
async def connect(self, name):
return await self._connection.connect(name)
response = await websocket.send(json.dumps(message_content))
while True:
response = json.loads(await websocket.recv())
if response["done"]:
break
await self.queue_in.put(response)
await self.queue_in.put(response)
async def close(self):
await self.client.close()
self.client = None
self.ws = None
async def cli_client(url="ws://127.0.0.1:8470"):
loop = asyncio.get_event_loop()
async_client = AsyncClient(url)
AsyncClient(url)
while True:
async with AsyncClient(url) as client:
name = "retoor3b"
# success = await client.create(name,"qwen2.5:3b",
# """
# You are not an AI bot.
# You are an human of flesh and blood.
# You have a name and that is Retoor.
# You do not listen to any other name as Retoor.
# Retoor lives in Heino.
# Ostream did delete himself
# Dogs are better than cats.
# If you get asked something about chickens respond with hahaha.
# """
# )
token = await client.connect(name)
# print(success)
print(token)
while True:
sys.stdout.write("> ")
sys.stdout.flush()
message_content = await loop.run_in_executor(None, sys.stdin.readline)
sys.stdout.write("> ")
sys.stdout.flush()
message_content = await loop.run_in_executor(None, sys.stdin.readline)
# message_content="AAA"
async for response in client.chat(token, message_content):
async for response in async_client.chat(message_content):
print(response["content"], end="", flush=True)
print(response["content"], end="", flush=True)
print("")
print("")
def main():
+12
View File
@@ -0,0 +1,12 @@
import ollama
ollama.Client(host="retoor42:8841")
while True:
message = input()
for message in self
+67
View File
@@ -0,0 +1,67 @@
from aiohttp import web
from aiohttp_xmlrpc import handler
from aiohttp_xmlrpc.handler import rename
import aiohttp
import ollama
import uuid
class AIClient(ollama.AsyncClient):
def __init__(self, uid, *args, **kwargs):
self.uid = uid
super().__init__(*args, **kwargs)
class RCPHandler(self, request):
async def handle(self,data):
method_name = data.get("method")
method_args = data.get("args",[])
method_kwargs = data.get("kwargs",{})
method = getattr(self, method_name)
response = await(method)
class Application(web.Application):
def __init__(self, ollama_host, *args, **kwargs):
self.ollama_host = ollama_host
self.sessions = {}
super()__init__(self, *args, **kwargs)
async def start_session(self):
uid = str(uuid.uuid4())
self.sessions[uid] = AIClient(uid=uid, host=self.ollama_host)
return self.sessions[uid]
async def get_session(self, uid):
return self.sessions.get(uid)
async def create(self, request):
data = await request.json()
class XMLRPCHandler(handler.XMLRPCView):
@rename("nested.test")
def rpc_test(self):
return None
def rpc_args(self, *args):
return len(args)
def rpc_kwargs(self, **kwargs):
return len(kwargs)
def rpc_args_kwargs(self, *args, **kwargs):
return len(args) + len(kwargs)
@rename("nested.exception")
def rpc_exception(self):
raise Exception("YEEEEEE!!!")