Skip to content

Commit c77776d

Browse files
Update to v0.1.5
1 parent e742e04 commit c77776d

File tree

6 files changed

+279
-21
lines changed

6 files changed

+279
-21
lines changed

examples/test_HR_json_database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def get_user_info(name, field=None):
4949
return {f: user.get(f, "Field not found") for f in fields}
5050
return user
5151

52-
client = T.clients.openrouter(API_KEY)
52+
client = T.clients.openrouter("API_KEY")
5353

5454
# System prompt with user list
5555
system_prompt = f"You are a HR assistant who MUST use the get_user_info tool before answering questions about users. Available users: {user_list}."

pyproject.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "open-taranis"
7-
version = "0.1.4"
7+
version = "0.1.5"
88
description = "Minimalist Python framework for AI agents logic-only coding with streaming, tool calls, and multi-LLM provider support"
99
authors = [{name = "SyntaxError4Life", email = "lilian@zanomega.com"}]
10-
dependencies = ["openai"]
10+
dependencies = ["openai", "bs4"]
1111
readme = "README.md"
1212
requires-python = ">=3.8"
1313
license = {text = "GPL-3.0-or-later"}
@@ -17,5 +17,8 @@ classifiers = [
1717
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)"
1818
]
1919

20+
[project.scripts]
21+
taranis = "open_taranis.CLI:main"
22+
2023
[tool.hatch.build.targets.wheel]
21-
packages = ["src/open_taranis"]
24+
packages = ["src/open_taranis"]

src/open_taranis/CLI.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import open_taranis as T
2+
3+
import subprocess
4+
import sys
5+
import os
6+
import curses
7+
8+
argv = sys.argv
9+
10+
# ==============================
11+
# The TUI
12+
# ==============================
13+
14+
LOGO_ASCII = """
15+
░ ░░░ ░░░ ░░░░ ░░░ ░░░ ░░ ░░░ ░░
16+
▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒ ▒▒ ▒▒▒▒ ▒▒ ▒▒▒▒ ▒▒ ▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒
17+
▓▓▓▓ ▓▓▓▓▓ ▓▓ ▓▓▓ ▓▓ ▓ ▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓
18+
████ █████ ████ ██ ███ ███ ████ ██ ██ █████ ███████████ █
19+
████ █████ ████ ██ ████ ██ ████ ██ ███ ██ ███ ██"""
20+
21+
# Contraintes minimales
22+
MIN_HEIGHT = 24
23+
MIN_WIDTH = 80
24+
25+
def run(stdscr):
26+
# Configuration basique de curses
27+
curses.curs_set(1) # Curseur visible
28+
curses.start_color()
29+
curses.use_default_colors()
30+
31+
# Initialisation des paires de couleurs
32+
curses.init_pair(1, curses.COLOR_RED, -1) # Pour le logo
33+
curses.init_pair(2, curses.COLOR_WHITE, -1) # Pour le texte standard
34+
curses.init_pair(3, curses.COLOR_YELLOW, -1)# Pour les erreurs
35+
36+
input_buffer = ""
37+
display_mode = "NONE" # États possibles : "NONE", "HELP", "API", etc.
38+
39+
while True:
40+
# Récupération dynamique des dimensions du terminal
41+
height, width = stdscr.getmaxyx()
42+
43+
# Vérification des contraintes minimales
44+
if height < MIN_HEIGHT or width < MIN_WIDTH:
45+
stdscr.clear()
46+
msg = f"Terminal too small: {width}x{height} (min {MIN_WIDTH}x{MIN_HEIGHT})"
47+
y, x = height // 2, max(0, (width - len(msg)) // 2)
48+
try:
49+
stdscr.addstr(y, x, msg, curses.color_pair(3) | curses.A_BOLD)
50+
stdscr.addstr(y + 1, x, "Resize or press 'q' to quit", curses.color_pair(2))
51+
except:
52+
pass
53+
stdscr.refresh()
54+
if stdscr.getch() == ord('q'):
55+
break
56+
continue
57+
58+
# Nettoyage de l'écran pour le rafraîchissement
59+
stdscr.clear()
60+
61+
# --- Calcul dynamique du Layout ---
62+
63+
# 1. Traitement du Logo (Haut Gauche)
64+
logo_lines = LOGO_ASCII.split('\n')
65+
logo_height = len(logo_lines)
66+
67+
for i, line in enumerate(logo_lines):
68+
if i < height - 2:
69+
try:
70+
truncated = line[:width-1]
71+
stdscr.addstr(i, 0, truncated, curses.color_pair(1))
72+
except curses.error:
73+
pass
74+
75+
# 2. Zone de Contenu (Centre)
76+
content_start = logo_height + 1
77+
content_end = height - 3
78+
79+
# --- Affichage du contenu central selon le mode ---
80+
if display_mode == "HELP":
81+
text = [
82+
"Commands :",
83+
"- /exit = quit the TUI",
84+
"- /help = show the command list",
85+
"- /show api = show registered API"
86+
]
87+
88+
elif display_mode == "API":
89+
text = [
90+
"APIs registered :",
91+
("- [x]" if os.environ.get('OPENROUTER_API') else "- [ ]") + " openrouter",
92+
("- [x]" if os.environ.get('HF_API') else "- [ ]") + " huggingface",
93+
("- [x]" if os.environ.get('VENICEAI_API') else "- [ ]") + " venice.ai",
94+
("- [x]" if os.environ.get('DEEPSEEK_API') else "- [ ]") + " deepseek.ai",
95+
("- [x]" if os.environ.get('XAI_API') else "- [ ]") + " x.ai",
96+
("- [x]" if os.environ.get('GROQ_API') else "- [ ]") + " groq",
97+
"",
98+
"To show the env var : /show more"
99+
]
100+
101+
elif display_mode == 'MORE_API':
102+
text = [
103+
"APIs registered :",
104+
"- openrouter = 'OPENROUTER_API'",
105+
"- huggingface = 'HF_API'",
106+
"- venice.ai = 'VENICEAI_API'",
107+
"- deepseek.ai = 'DEEPSEEK_API'",
108+
"- x.ai = 'XAI_API'",
109+
"- groq = 'GROQ_API'",
110+
]
111+
112+
if display_mode != "NONE" :
113+
current_line = content_start
114+
for line in text:
115+
if current_line < content_end:
116+
try:
117+
stdscr.addstr(current_line, 0, line, curses.color_pair(2))
118+
except:
119+
pass
120+
current_line += 1
121+
122+
123+
# 3. Footer / Invite de commande (Bas)
124+
sep_y = height - 2
125+
input_y = height - 1
126+
127+
separator = "-" * min(width-1, 60)
128+
try:
129+
stdscr.addstr(sep_y, 0, separator, curses.color_pair(2) | curses.A_DIM)
130+
except:
131+
pass
132+
133+
prompt = f"> {input_buffer}"
134+
display_prompt = prompt[:width-1]
135+
try:
136+
stdscr.addstr(input_y, 0, display_prompt, curses.color_pair(2))
137+
cursor_x = min(len(prompt), width - 1)
138+
stdscr.move(input_y, cursor_x)
139+
except:
140+
pass
141+
142+
stdscr.refresh()
143+
144+
# --- Gestion des entrées clavier ---
145+
key = stdscr.getch()
146+
147+
if key == curses.KEY_RESIZE:
148+
continue
149+
150+
elif key in (10, 13): # Entrée
151+
command = input_buffer.strip()
152+
153+
if command == "/exit":
154+
break
155+
156+
elif command == "/help":
157+
display_mode = "HELP"
158+
159+
elif command == "/show api":
160+
display_mode = "API"
161+
162+
elif command == "/show more" and display_mode == "API" :
163+
display_mode = 'MORE_API'
164+
165+
else:
166+
# Commande invalide ou vide : on efface l'affichage central
167+
display_mode = None
168+
169+
input_buffer = ""
170+
171+
elif key in (127, curses.KEY_BACKSPACE, ord('\b')): # Backspace
172+
input_buffer = input_buffer[:-1]
173+
174+
elif key == 27: # Échap
175+
input_buffer = ""
176+
177+
elif 32 <= key <= 126: # Caractères imprimables ASCII
178+
input_buffer += chr(key)
179+
180+
# ==============================
181+
# The args
182+
# ==============================
183+
184+
def main():
185+
if len(argv) == 1 or argv[1] == "help" :
186+
print(f"""=== open-taranis {T.__version__} ===
187+
188+
help : Show this...
189+
190+
open : Open the TUI
191+
192+
update : upgrade the framework
193+
""")
194+
195+
elif argv[1] == "open":
196+
# Lancement de la boucle curses
197+
curses.wrapper(run)
198+
199+
elif argv[1] == "update":
200+
print("Updating open-taranis via pip...")
201+
try:
202+
# On lance pip install -U sur le paquet actuel
203+
subprocess.check_call([sys.executable, "-m", "pip", "install", "-U", "open-taranis"])
204+
print("Update successful.")
205+
except subprocess.CalledProcessError as e:
206+
print(f"Error during update: {e}")

src/open_taranis/__init__.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import openai
22
import json
33
import re
4+
import os
45

56
# For the python function to JSON/dict
67
import inspect
78
from typing import Any, Callable, Literal, Union, get_args, get_origin
89

9-
10-
__version__ = "0.1.4"
10+
__version__ = "0.1.5"
1111

1212
import requests
1313
from packaging import version
@@ -151,45 +151,57 @@ def generic(api_key:str, base_url:str) -> openai.OpenAI:
151151
return openai.OpenAI(api_key=api_key, base_url=base_url)
152152

153153
@staticmethod
154-
def veniceai(api_key: str) -> openai.OpenAI:
154+
def veniceai(api_key: str=None) -> openai.OpenAI:
155155
"""
156156
Use `clients.veniceai_request` for call
157157
"""
158+
if os.environ.get('VENICEAI_API') :
159+
api_key = os.environ.get('VENICEAI_API')
158160
return openai.OpenAI(api_key=api_key, base_url="https://api.venice.ai/api/v1")
159161

160162
@staticmethod
161-
def deepseek(api_key: str) -> openai.OpenAI:
163+
def deepseek(api_key: str=None) -> openai.OpenAI:
162164
"""
163165
Use `clients.generic_request` for call
164166
"""
167+
if os.environ.get('DEEPSEEK_API') :
168+
api_key = os.environ.get('DEEPSEEK_API')
165169
return openai.OpenAI(api_key=api_key, base_url="https://api.deepseek.com")
166170

167171
@staticmethod
168-
def xai(api_key: str) -> openai.OpenAI:
172+
def xai(api_key: str=None) -> openai.OpenAI:
169173
"""
170174
Use `clients.generic_request` for call
171175
"""
176+
if os.environ.get('XAI_API') :
177+
api_key = os.environ.get('XAI_API')
172178
return openai.OpenAI(api_key=api_key, base_url="https://api.x.ai/v1", timeout=3600)
173179

174180
@staticmethod
175-
def groq(api_key: str) -> openai.OpenAI:
181+
def groq(api_key: str=None) -> openai.OpenAI:
176182
"""
177183
Use `clients.generic_request` for call
178184
"""
185+
if os.environ.get('GROQ_API') :
186+
api_key = os.environ.get('GROQ_API')
179187
return openai.OpenAI(api_key=api_key, base_url="https://api.groq.com/openai/v1")
180188

181189
@staticmethod
182-
def huggingface(api_key: str) -> openai.OpenAI:
190+
def huggingface(api_key: str=None) -> openai.OpenAI:
183191
"""
184192
Use `clients.generic_request` for call
185193
"""
194+
if os.environ.get('HF_API') :
195+
os.environ.get('HF_API')
186196
return openai.OpenAI(api_key=api_key, base_url="https://router.huggingface.co/v1")
187197

188198
@staticmethod
189-
def openrouter(api_key: str) -> openai.OpenAI:
199+
def openrouter(api_key: str=None) -> openai.OpenAI:
190200
"""
191201
Use `clients.openrouter_request` for call
192202
"""
203+
if os.environ.get('OPENROUTER_API') :
204+
os.environ.get('OPENROUTER_API')
193205
return openai.OpenAI(api_key=api_key, base_url="https://openrouter.ai/api/v1")
194206

195207
@staticmethod
@@ -378,7 +390,6 @@ def handle_streaming(stream: openai.Stream):
378390
]
379391
yield "", tool_calls, len(tool_calls) > 0
380392

381-
382393
def handle_tool_call(tool_call:dict) -> tuple[str, str, dict, str] :
383394
"""
384395
Return :
@@ -432,11 +443,6 @@ def create_user_prompt(content:str) -> dict[str, str] :
432443
# Agents coding (v0.2.0)
433444
# ==============================
434445

435-
# class Agent():
436-
# def __init__(self):
437-
# pass
438-
#
439-
# def __call__(self):
440-
# pass
441-
#
442-
# ...
446+
class agent:
447+
def __init__(self):
448+
pass

src/open_taranis/tools.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from open_taranis import functions_to_tools
2+
3+
import requests
4+
from bs4 import BeautifulSoup
5+
import os
6+
7+
def brave_research(web_request:str, count:int, country:str):
8+
9+
try:
10+
api = os.environ['BRAVE_API']
11+
except KeyError:
12+
raise ValueError("Critical error: The BRAVE_API environment variable is missing..")
13+
14+
return requests.get(
15+
"https://api.search.brave.com/res/v1/web/search",
16+
headers={"X-Subscription-Token": api},
17+
params={
18+
"q": web_request,
19+
"count": count,
20+
"country": country,
21+
"source": "web"
22+
}
23+
).json()
24+
25+
def fast_scraping(url):
26+
"""Quick scraping function, retrieves only the text from the given URL"""
27+
try:
28+
response = requests.get(url)
29+
response.raise_for_status()
30+
soup = BeautifulSoup(response.content, 'html.parser')
31+
text = soup.get_text(strip=True)
32+
return text
33+
except requests.exceptions.RequestException as e:
34+
# Network errors, timeouts, etc.
35+
msg = str(e)[:100] + "..." if len(str(e)) > 50 else str(e)
36+
return f"Request failed: {msg}"
37+
except Exception as e:
38+
# Parser errors or unexpected issues
39+
msg = str(e)[:100] + "..." if len(str(e)) > 50 else str(e)
40+
return f"Scraping failed: {msg}"

src/open_taranis/web_front.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def fn(message, history, *args):
5959

6060
if "</think>" in token :
6161
is_thinking = False
62+
63+
yield "Thinking...."
64+
continue
6265

6366
else : partial += token
6467

0 commit comments

Comments
 (0)