revengelibrary 0.1.0__tar.gz → 0.1.5__tar.gz
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.
- revengelibrary-0.1.5/MANIFEST.in +15 -0
- revengelibrary-0.1.5/PKG-INFO +36 -0
- revengelibrary-0.1.5/README.md +19 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/pyproject.toml +5 -2
- revengelibrary-0.1.5/revengelibrary/__init__.py +16 -0
- revengelibrary-0.1.5/revengelibrary/chat.py +207 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary/cli.py +29 -9
- revengelibrary-0.1.5/revengelibrary/memory_store.json +1 -0
- revengelibrary-0.1.5/revengelibrary.egg-info/PKG-INFO +36 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary.egg-info/SOURCES.txt +2 -0
- revengelibrary-0.1.0/PKG-INFO +0 -88
- revengelibrary-0.1.0/README.md +0 -71
- revengelibrary-0.1.0/revengelibrary/__init__.py +0 -4
- revengelibrary-0.1.0/revengelibrary/chat.py +0 -76
- revengelibrary-0.1.0/revengelibrary.egg-info/PKG-INFO +0 -88
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/LICENSE +0 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary.egg-info/dependency_links.txt +0 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary.egg-info/entry_points.txt +0 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary.egg-info/requires.txt +0 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/revengelibrary.egg-info/top_level.txt +0 -0
- {revengelibrary-0.1.0 → revengelibrary-0.1.5}/setup.cfg +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
|
|
5
|
+
recursive-include revengelibrary *.py *.json
|
|
6
|
+
|
|
7
|
+
exclude .env
|
|
8
|
+
exclude .revengelibrary_memory.json
|
|
9
|
+
|
|
10
|
+
prune venv
|
|
11
|
+
prune build
|
|
12
|
+
prune dist
|
|
13
|
+
|
|
14
|
+
global-exclude __pycache__
|
|
15
|
+
global-exclude *.py[cod]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: revengelibrary
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Python chat library and CLI for free LLM models via OpenRouter.
|
|
5
|
+
Author: revengebibliotek contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/example/revengelibrary
|
|
8
|
+
Keywords: chat,llm,openrouter,api,cli
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# revengelibrary
|
|
19
|
+
|
|
20
|
+
Не нейросеть
|
|
21
|
+
|
|
22
|
+
## Установка
|
|
23
|
+
|
|
24
|
+
Из PyPI (после публикации):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install revengelibrary
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Запуск из терминала
|
|
31
|
+
|
|
32
|
+
После установки доступна команда:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
revengelibrary "text"
|
|
36
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# revengelibrary
|
|
2
|
+
|
|
3
|
+
Не нейросеть
|
|
4
|
+
|
|
5
|
+
## Установка
|
|
6
|
+
|
|
7
|
+
Из PyPI (после публикации):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install revengelibrary
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Запуск из терминала
|
|
14
|
+
|
|
15
|
+
После установки доступна команда:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
revengelibrary "text"
|
|
19
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "revengelibrary"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Python chat library and CLI for free LLM models via OpenRouter."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -29,7 +29,10 @@ Homepage = "https://github.com/example/revengelibrary"
|
|
|
29
29
|
revengelibrary = "revengelibrary.cli:main"
|
|
30
30
|
|
|
31
31
|
[tool.setuptools]
|
|
32
|
-
include-package-data =
|
|
32
|
+
include-package-data = true
|
|
33
33
|
|
|
34
34
|
[tool.setuptools.packages.find]
|
|
35
35
|
include = ["revengelibrary*"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
revengelibrary = ["memory_store.json"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from .chat import (
|
|
2
|
+
APIError,
|
|
3
|
+
DEFAULT_MEMORY_FILE,
|
|
4
|
+
DEFAULT_MODEL,
|
|
5
|
+
DEFAULT_OPENROUTER_API_KEY,
|
|
6
|
+
FreeNeuroChatClient,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"FreeNeuroChatClient",
|
|
11
|
+
"APIError",
|
|
12
|
+
"DEFAULT_OPENROUTER_API_KEY",
|
|
13
|
+
"DEFAULT_MODEL",
|
|
14
|
+
"DEFAULT_MEMORY_FILE",
|
|
15
|
+
]
|
|
16
|
+
__version__ = "0.1.5"
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
DEFAULT_OPENROUTER_API_KEY = (
|
|
11
|
+
"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"
|
|
12
|
+
)
|
|
13
|
+
DEFAULT_MODEL = "meta-llama/llama-3.1-8b-instruct:free"
|
|
14
|
+
DEFAULT_MEMORY_FILE = str(Path(__file__).resolve().with_name("memory_store.json"))
|
|
15
|
+
_MODEL_UNAVAILABLE_MARKER = "No endpoints found"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class APIError(RuntimeError):
|
|
19
|
+
"""Raised when the upstream API returns an invalid response."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class FreeNeuroChatClient:
|
|
24
|
+
"""Small wrapper around OpenRouter's OpenAI-compatible chat endpoint."""
|
|
25
|
+
|
|
26
|
+
api_key: str = DEFAULT_OPENROUTER_API_KEY
|
|
27
|
+
model: str = DEFAULT_MODEL
|
|
28
|
+
base_url: str = "https://openrouter.ai/api/v1"
|
|
29
|
+
timeout: int = 60
|
|
30
|
+
system_prompt: str | None = "You are a helpful assistant."
|
|
31
|
+
memory_file: str | None = DEFAULT_MEMORY_FILE
|
|
32
|
+
autosave_memory: bool = True
|
|
33
|
+
messages: list[dict[str, str]] = field(default_factory=list)
|
|
34
|
+
|
|
35
|
+
def __post_init__(self) -> None:
|
|
36
|
+
if not self.api_key:
|
|
37
|
+
self.api_key = DEFAULT_OPENROUTER_API_KEY
|
|
38
|
+
|
|
39
|
+
if self.memory_file:
|
|
40
|
+
self.load_memory()
|
|
41
|
+
|
|
42
|
+
if not self.messages and self.system_prompt:
|
|
43
|
+
self.messages.append({"role": "system", "content": self.system_prompt})
|
|
44
|
+
elif self.system_prompt and not self._has_system_message(self.messages):
|
|
45
|
+
self.messages.insert(0, {"role": "system", "content": self.system_prompt})
|
|
46
|
+
|
|
47
|
+
self._save_memory_if_needed()
|
|
48
|
+
|
|
49
|
+
def send(self, user_message: str) -> str:
|
|
50
|
+
"""Send user text and return assistant reply."""
|
|
51
|
+
user_message = user_message.strip()
|
|
52
|
+
if not user_message:
|
|
53
|
+
raise ValueError("user_message must not be empty")
|
|
54
|
+
|
|
55
|
+
self.messages.append({"role": "user", "content": user_message})
|
|
56
|
+
payload = {"model": self.model, "messages": self.messages}
|
|
57
|
+
|
|
58
|
+
response = self._chat_completion(payload)
|
|
59
|
+
if self._is_model_unavailable(response):
|
|
60
|
+
fallback_model = self._discover_fallback_free_model(exclude={self.model})
|
|
61
|
+
if fallback_model:
|
|
62
|
+
self.model = fallback_model
|
|
63
|
+
payload["model"] = fallback_model
|
|
64
|
+
response = self._chat_completion(payload)
|
|
65
|
+
|
|
66
|
+
if response.status_code >= 400:
|
|
67
|
+
raise APIError(
|
|
68
|
+
f"API returned {response.status_code}: {response.text[:500]}"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
data = response.json()
|
|
72
|
+
content = self._extract_content(data)
|
|
73
|
+
self.messages.append({"role": "assistant", "content": content})
|
|
74
|
+
self._save_memory_if_needed()
|
|
75
|
+
return content
|
|
76
|
+
|
|
77
|
+
def reset(self) -> None:
|
|
78
|
+
"""Clear dialog history but keep initial system prompt."""
|
|
79
|
+
self.messages = []
|
|
80
|
+
if self.system_prompt:
|
|
81
|
+
self.messages.append({"role": "system", "content": self.system_prompt})
|
|
82
|
+
self._save_memory_if_needed()
|
|
83
|
+
|
|
84
|
+
def save_memory(self, file_path: str | None = None) -> None:
|
|
85
|
+
"""Persist full dialog history to JSON file."""
|
|
86
|
+
path = self._resolve_memory_path(file_path)
|
|
87
|
+
if path is None:
|
|
88
|
+
return
|
|
89
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
90
|
+
path.write_text(
|
|
91
|
+
json.dumps(self.messages, ensure_ascii=False, indent=2),
|
|
92
|
+
encoding="utf-8",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def load_memory(self, file_path: str | None = None) -> None:
|
|
96
|
+
"""Load dialog history from JSON file if it exists."""
|
|
97
|
+
path = self._resolve_memory_path(file_path)
|
|
98
|
+
if path is None or not path.exists():
|
|
99
|
+
return
|
|
100
|
+
try:
|
|
101
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
102
|
+
except (OSError, json.JSONDecodeError):
|
|
103
|
+
return
|
|
104
|
+
self.messages = self._normalize_messages(data)
|
|
105
|
+
|
|
106
|
+
def clear_memory_file(self, file_path: str | None = None) -> None:
|
|
107
|
+
"""Delete persisted history file."""
|
|
108
|
+
path = self._resolve_memory_path(file_path)
|
|
109
|
+
if path is None:
|
|
110
|
+
return
|
|
111
|
+
if path.exists():
|
|
112
|
+
path.unlink()
|
|
113
|
+
|
|
114
|
+
def _headers(self) -> dict[str, str]:
|
|
115
|
+
return {
|
|
116
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
117
|
+
"Content-Type": "application/json",
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def _chat_completion(self, payload: dict[str, Any]) -> requests.Response:
|
|
121
|
+
return requests.post(
|
|
122
|
+
f"{self.base_url}/chat/completions",
|
|
123
|
+
headers=self._headers(),
|
|
124
|
+
json=payload,
|
|
125
|
+
timeout=self.timeout,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def _is_model_unavailable(self, response: requests.Response) -> bool:
|
|
129
|
+
if response.status_code != 404:
|
|
130
|
+
return False
|
|
131
|
+
try:
|
|
132
|
+
data = response.json()
|
|
133
|
+
except ValueError:
|
|
134
|
+
return False
|
|
135
|
+
message = str(data.get("error", {}).get("message", ""))
|
|
136
|
+
return _MODEL_UNAVAILABLE_MARKER in message
|
|
137
|
+
|
|
138
|
+
def _discover_fallback_free_model(self, exclude: set[str]) -> str | None:
|
|
139
|
+
try:
|
|
140
|
+
response = requests.get(
|
|
141
|
+
f"{self.base_url}/models",
|
|
142
|
+
headers=self._headers(),
|
|
143
|
+
timeout=self.timeout,
|
|
144
|
+
)
|
|
145
|
+
if response.status_code >= 400:
|
|
146
|
+
return None
|
|
147
|
+
data = response.json()
|
|
148
|
+
except Exception: # noqa: BLE001
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
model_ids = self._extract_free_model_ids(data.get("data"))
|
|
152
|
+
for model_id in model_ids:
|
|
153
|
+
if model_id not in exclude:
|
|
154
|
+
return model_id
|
|
155
|
+
return None
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _extract_free_model_ids(data: Any) -> list[str]:
|
|
159
|
+
if not isinstance(data, list):
|
|
160
|
+
return []
|
|
161
|
+
|
|
162
|
+
result: list[str] = []
|
|
163
|
+
for item in data:
|
|
164
|
+
if not isinstance(item, dict):
|
|
165
|
+
continue
|
|
166
|
+
model_id = item.get("id")
|
|
167
|
+
if isinstance(model_id, str) and model_id.endswith(":free"):
|
|
168
|
+
result.append(model_id)
|
|
169
|
+
return result
|
|
170
|
+
|
|
171
|
+
def _save_memory_if_needed(self) -> None:
|
|
172
|
+
if self.autosave_memory:
|
|
173
|
+
self.save_memory()
|
|
174
|
+
|
|
175
|
+
def _resolve_memory_path(self, file_path: str | None = None) -> Path | None:
|
|
176
|
+
value = file_path if file_path is not None else self.memory_file
|
|
177
|
+
if not value:
|
|
178
|
+
return None
|
|
179
|
+
return Path(value).expanduser()
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _normalize_messages(data: Any) -> list[dict[str, str]]:
|
|
183
|
+
if not isinstance(data, list):
|
|
184
|
+
return []
|
|
185
|
+
normalized: list[dict[str, str]] = []
|
|
186
|
+
for item in data:
|
|
187
|
+
if not isinstance(item, dict):
|
|
188
|
+
continue
|
|
189
|
+
role = item.get("role")
|
|
190
|
+
content = item.get("content")
|
|
191
|
+
if isinstance(role, str) and isinstance(content, str):
|
|
192
|
+
normalized.append({"role": role, "content": content})
|
|
193
|
+
return normalized
|
|
194
|
+
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _has_system_message(messages: list[dict[str, str]]) -> bool:
|
|
197
|
+
return any(item.get("role") == "system" for item in messages)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _extract_content(data: dict[str, Any]) -> str:
|
|
201
|
+
try:
|
|
202
|
+
content = data["choices"][0]["message"]["content"]
|
|
203
|
+
if not isinstance(content, str) or not content.strip():
|
|
204
|
+
raise KeyError
|
|
205
|
+
return content
|
|
206
|
+
except (KeyError, TypeError, IndexError):
|
|
207
|
+
raise APIError(f"Unexpected API response: {data}") from None
|
|
@@ -3,7 +3,13 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
|
-
from .chat import
|
|
6
|
+
from .chat import (
|
|
7
|
+
APIError,
|
|
8
|
+
DEFAULT_MEMORY_FILE,
|
|
9
|
+
DEFAULT_MODEL,
|
|
10
|
+
DEFAULT_OPENROUTER_API_KEY,
|
|
11
|
+
FreeNeuroChatClient,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
|
|
9
15
|
def _build_parser() -> argparse.ArgumentParser:
|
|
@@ -13,12 +19,12 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
13
19
|
)
|
|
14
20
|
parser.add_argument(
|
|
15
21
|
"--api-key",
|
|
16
|
-
default=os.getenv("OPENROUTER_API_KEY",
|
|
17
|
-
help="OpenRouter API key. Fallback: OPENROUTER_API_KEY
|
|
22
|
+
default=os.getenv("OPENROUTER_API_KEY", DEFAULT_OPENROUTER_API_KEY),
|
|
23
|
+
help="OpenRouter API key. Fallback: OPENROUTER_API_KEY, then built-in key.",
|
|
18
24
|
)
|
|
19
25
|
parser.add_argument(
|
|
20
26
|
"--model",
|
|
21
|
-
default=
|
|
27
|
+
default=DEFAULT_MODEL,
|
|
22
28
|
help="Model name on OpenRouter (default uses a free model).",
|
|
23
29
|
)
|
|
24
30
|
parser.add_argument(
|
|
@@ -26,6 +32,14 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
26
32
|
default="You are a helpful assistant.",
|
|
27
33
|
help="System prompt.",
|
|
28
34
|
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--memory-file",
|
|
37
|
+
default=os.getenv("REVENGELIBRARY_MEMORY_FILE", DEFAULT_MEMORY_FILE),
|
|
38
|
+
help=(
|
|
39
|
+
"JSON file for chat memory. "
|
|
40
|
+
"Fallback: REVENGELIBRARY_MEMORY_FILE, then built-in package memory file."
|
|
41
|
+
),
|
|
42
|
+
)
|
|
29
43
|
return parser
|
|
30
44
|
|
|
31
45
|
|
|
@@ -33,16 +47,17 @@ def main() -> int:
|
|
|
33
47
|
parser = _build_parser()
|
|
34
48
|
args = parser.parse_args()
|
|
35
49
|
|
|
36
|
-
if not args.api_key:
|
|
37
|
-
parser.error("API key is required: pass --api-key or set OPENROUTER_API_KEY.")
|
|
38
|
-
|
|
39
50
|
client = FreeNeuroChatClient(
|
|
40
|
-
api_key=args.api_key,
|
|
51
|
+
api_key=args.api_key or DEFAULT_OPENROUTER_API_KEY,
|
|
41
52
|
model=args.model,
|
|
42
53
|
system_prompt=args.system,
|
|
54
|
+
memory_file=args.memory_file,
|
|
43
55
|
)
|
|
44
56
|
|
|
45
|
-
print(
|
|
57
|
+
print(
|
|
58
|
+
"Interactive chat started. "
|
|
59
|
+
"Type 'exit' to quit, 'reset' to clear history, 'forget' to delete memory file."
|
|
60
|
+
)
|
|
46
61
|
while True:
|
|
47
62
|
try:
|
|
48
63
|
text = input("you> ").strip()
|
|
@@ -59,6 +74,11 @@ def main() -> int:
|
|
|
59
74
|
client.reset()
|
|
60
75
|
print("history reset")
|
|
61
76
|
continue
|
|
77
|
+
if text.lower() == "forget":
|
|
78
|
+
client.reset()
|
|
79
|
+
client.clear_memory_file()
|
|
80
|
+
print("history reset and memory file deleted")
|
|
81
|
+
continue
|
|
62
82
|
|
|
63
83
|
try:
|
|
64
84
|
answer = client.send(text)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: revengelibrary
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Python chat library and CLI for free LLM models via OpenRouter.
|
|
5
|
+
Author: revengebibliotek contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/example/revengelibrary
|
|
8
|
+
Keywords: chat,llm,openrouter,api,cli
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# revengelibrary
|
|
19
|
+
|
|
20
|
+
Не нейросеть
|
|
21
|
+
|
|
22
|
+
## Установка
|
|
23
|
+
|
|
24
|
+
Из PyPI (после публикации):
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install revengelibrary
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Запуск из терминала
|
|
31
|
+
|
|
32
|
+
После установки доступна команда:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
revengelibrary "text"
|
|
36
|
+
```
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
LICENSE
|
|
2
|
+
MANIFEST.in
|
|
2
3
|
README.md
|
|
3
4
|
pyproject.toml
|
|
4
5
|
revengelibrary/__init__.py
|
|
5
6
|
revengelibrary/chat.py
|
|
6
7
|
revengelibrary/cli.py
|
|
8
|
+
revengelibrary/memory_store.json
|
|
7
9
|
revengelibrary.egg-info/PKG-INFO
|
|
8
10
|
revengelibrary.egg-info/SOURCES.txt
|
|
9
11
|
revengelibrary.egg-info/dependency_links.txt
|
revengelibrary-0.1.0/PKG-INFO
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: revengelibrary
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python chat library and CLI for free LLM models via OpenRouter.
|
|
5
|
-
Author: revengebibliotek contributors
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/example/revengelibrary
|
|
8
|
-
Keywords: chat,llm,openrouter,api,cli
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.9
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: requests>=2.31.0
|
|
16
|
-
Dynamic: license-file
|
|
17
|
-
|
|
18
|
-
# revengelibrary
|
|
19
|
-
|
|
20
|
-
Небольшая Python-библиотека и CLI для чата с нейросетью через бесплатные модели OpenRouter.
|
|
21
|
-
|
|
22
|
-
## Установка
|
|
23
|
-
|
|
24
|
-
Из PyPI (после публикации):
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pip install revengelibrary
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Локально из папки проекта:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
pip install .
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Для разработки:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pip install -e .
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Подготовка API ключа
|
|
43
|
-
|
|
44
|
-
1. Создай бесплатный API key в OpenRouter.
|
|
45
|
-
2. Экспортируй ключ:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
export OPENROUTER_API_KEY="your_key"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Использование в Python
|
|
52
|
-
|
|
53
|
-
```python
|
|
54
|
-
from revengelibrary import FreeNeuroChatClient
|
|
55
|
-
|
|
56
|
-
client = FreeNeuroChatClient(api_key="your_key")
|
|
57
|
-
reply = client.send("Привет! Расскажи коротко анекдот.")
|
|
58
|
-
print(reply)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Запуск из терминала
|
|
62
|
-
|
|
63
|
-
После установки доступна команда:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
revengelibrary --model meta-llama/llama-3.1-8b-instruct:free
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Если ключ лежит в `OPENROUTER_API_KEY`, то `--api-key` можно не передавать.
|
|
70
|
-
|
|
71
|
-
## Минимальный API библиотеки
|
|
72
|
-
|
|
73
|
-
- `FreeNeuroChatClient.send(text: str) -> str`
|
|
74
|
-
- `FreeNeuroChatClient.reset() -> None`
|
|
75
|
-
|
|
76
|
-
## Публикация в PyPI
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
python3 -m pip install --upgrade build twine
|
|
80
|
-
python3 -m build
|
|
81
|
-
python3 -m twine upload dist/*
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
После этого любой человек сможет установить библиотеку:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
pip install revengelibrary
|
|
88
|
-
```
|
revengelibrary-0.1.0/README.md
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
# revengelibrary
|
|
2
|
-
|
|
3
|
-
Небольшая Python-библиотека и CLI для чата с нейросетью через бесплатные модели OpenRouter.
|
|
4
|
-
|
|
5
|
-
## Установка
|
|
6
|
-
|
|
7
|
-
Из PyPI (после публикации):
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pip install revengelibrary
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
Локально из папки проекта:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pip install .
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Для разработки:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pip install -e .
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Подготовка API ключа
|
|
26
|
-
|
|
27
|
-
1. Создай бесплатный API key в OpenRouter.
|
|
28
|
-
2. Экспортируй ключ:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
export OPENROUTER_API_KEY="your_key"
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Использование в Python
|
|
35
|
-
|
|
36
|
-
```python
|
|
37
|
-
from revengelibrary import FreeNeuroChatClient
|
|
38
|
-
|
|
39
|
-
client = FreeNeuroChatClient(api_key="your_key")
|
|
40
|
-
reply = client.send("Привет! Расскажи коротко анекдот.")
|
|
41
|
-
print(reply)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Запуск из терминала
|
|
45
|
-
|
|
46
|
-
После установки доступна команда:
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
revengelibrary --model meta-llama/llama-3.1-8b-instruct:free
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Если ключ лежит в `OPENROUTER_API_KEY`, то `--api-key` можно не передавать.
|
|
53
|
-
|
|
54
|
-
## Минимальный API библиотеки
|
|
55
|
-
|
|
56
|
-
- `FreeNeuroChatClient.send(text: str) -> str`
|
|
57
|
-
- `FreeNeuroChatClient.reset() -> None`
|
|
58
|
-
|
|
59
|
-
## Публикация в PyPI
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
python3 -m pip install --upgrade build twine
|
|
63
|
-
python3 -m build
|
|
64
|
-
python3 -m twine upload dist/*
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
После этого любой человек сможет установить библиотеку:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
pip install revengelibrary
|
|
71
|
-
```
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass, field
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
import requests
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class APIError(RuntimeError):
|
|
10
|
-
"""Raised when the upstream API returns an invalid response."""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@dataclass
|
|
14
|
-
class FreeNeuroChatClient:
|
|
15
|
-
"""Small wrapper around OpenRouter's OpenAI-compatible chat endpoint."""
|
|
16
|
-
|
|
17
|
-
api_key: str
|
|
18
|
-
model: str = "meta-llama/llama-3.1-8b-instruct:free"
|
|
19
|
-
base_url: str = "https://openrouter.ai/api/v1"
|
|
20
|
-
timeout: int = 60
|
|
21
|
-
system_prompt: str | None = "You are a helpful assistant."
|
|
22
|
-
messages: list[dict[str, str]] = field(default_factory=list)
|
|
23
|
-
|
|
24
|
-
def __post_init__(self) -> None:
|
|
25
|
-
if not self.api_key:
|
|
26
|
-
raise ValueError("api_key is required")
|
|
27
|
-
if self.system_prompt and not self.messages:
|
|
28
|
-
self.messages.append({"role": "system", "content": self.system_prompt})
|
|
29
|
-
|
|
30
|
-
def send(self, user_message: str) -> str:
|
|
31
|
-
"""Send user text and return assistant reply."""
|
|
32
|
-
user_message = user_message.strip()
|
|
33
|
-
if not user_message:
|
|
34
|
-
raise ValueError("user_message must not be empty")
|
|
35
|
-
|
|
36
|
-
self.messages.append({"role": "user", "content": user_message})
|
|
37
|
-
payload = {"model": self.model, "messages": self.messages}
|
|
38
|
-
|
|
39
|
-
response = requests.post(
|
|
40
|
-
f"{self.base_url}/chat/completions",
|
|
41
|
-
headers=self._headers(),
|
|
42
|
-
json=payload,
|
|
43
|
-
timeout=self.timeout,
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
if response.status_code >= 400:
|
|
47
|
-
raise APIError(
|
|
48
|
-
f"API returned {response.status_code}: {response.text[:500]}"
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
data = response.json()
|
|
52
|
-
content = self._extract_content(data)
|
|
53
|
-
self.messages.append({"role": "assistant", "content": content})
|
|
54
|
-
return content
|
|
55
|
-
|
|
56
|
-
def reset(self) -> None:
|
|
57
|
-
"""Clear dialog history but keep initial system prompt."""
|
|
58
|
-
self.messages = []
|
|
59
|
-
if self.system_prompt:
|
|
60
|
-
self.messages.append({"role": "system", "content": self.system_prompt})
|
|
61
|
-
|
|
62
|
-
def _headers(self) -> dict[str, str]:
|
|
63
|
-
return {
|
|
64
|
-
"Authorization": f"Bearer {self.api_key}",
|
|
65
|
-
"Content-Type": "application/json",
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@staticmethod
|
|
69
|
-
def _extract_content(data: dict[str, Any]) -> str:
|
|
70
|
-
try:
|
|
71
|
-
content = data["choices"][0]["message"]["content"]
|
|
72
|
-
if not isinstance(content, str) or not content.strip():
|
|
73
|
-
raise KeyError
|
|
74
|
-
return content
|
|
75
|
-
except (KeyError, TypeError, IndexError):
|
|
76
|
-
raise APIError(f"Unexpected API response: {data}") from None
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: revengelibrary
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Python chat library and CLI for free LLM models via OpenRouter.
|
|
5
|
-
Author: revengebibliotek contributors
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/example/revengelibrary
|
|
8
|
-
Keywords: chat,llm,openrouter,api,cli
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Requires-Python: >=3.9
|
|
13
|
-
Description-Content-Type: text/markdown
|
|
14
|
-
License-File: LICENSE
|
|
15
|
-
Requires-Dist: requests>=2.31.0
|
|
16
|
-
Dynamic: license-file
|
|
17
|
-
|
|
18
|
-
# revengelibrary
|
|
19
|
-
|
|
20
|
-
Небольшая Python-библиотека и CLI для чата с нейросетью через бесплатные модели OpenRouter.
|
|
21
|
-
|
|
22
|
-
## Установка
|
|
23
|
-
|
|
24
|
-
Из PyPI (после публикации):
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
pip install revengelibrary
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Локально из папки проекта:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
pip install .
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Для разработки:
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pip install -e .
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Подготовка API ключа
|
|
43
|
-
|
|
44
|
-
1. Создай бесплатный API key в OpenRouter.
|
|
45
|
-
2. Экспортируй ключ:
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
export OPENROUTER_API_KEY="your_key"
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## Использование в Python
|
|
52
|
-
|
|
53
|
-
```python
|
|
54
|
-
from revengelibrary import FreeNeuroChatClient
|
|
55
|
-
|
|
56
|
-
client = FreeNeuroChatClient(api_key="your_key")
|
|
57
|
-
reply = client.send("Привет! Расскажи коротко анекдот.")
|
|
58
|
-
print(reply)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Запуск из терминала
|
|
62
|
-
|
|
63
|
-
После установки доступна команда:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
revengelibrary --model meta-llama/llama-3.1-8b-instruct:free
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Если ключ лежит в `OPENROUTER_API_KEY`, то `--api-key` можно не передавать.
|
|
70
|
-
|
|
71
|
-
## Минимальный API библиотеки
|
|
72
|
-
|
|
73
|
-
- `FreeNeuroChatClient.send(text: str) -> str`
|
|
74
|
-
- `FreeNeuroChatClient.reset() -> None`
|
|
75
|
-
|
|
76
|
-
## Публикация в PyPI
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
python3 -m pip install --upgrade build twine
|
|
80
|
-
python3 -m build
|
|
81
|
-
python3 -m twine upload dist/*
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
После этого любой человек сможет установить библиотеку:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
pip install revengelibrary
|
|
88
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|