oskaragent 0.1.38a0__py3-none-any.whl
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.
- oskaragent/__init__.py +49 -0
- oskaragent/agent.py +1911 -0
- oskaragent/agent_config.py +328 -0
- oskaragent/agent_mcp_tools.py +102 -0
- oskaragent/agent_tools.py +962 -0
- oskaragent/helpers.py +175 -0
- oskaragent-0.1.38a0.dist-info/METADATA +29 -0
- oskaragent-0.1.38a0.dist-info/RECORD +30 -0
- oskaragent-0.1.38a0.dist-info/WHEEL +5 -0
- oskaragent-0.1.38a0.dist-info/licenses/LICENSE +21 -0
- oskaragent-0.1.38a0.dist-info/top_level.txt +2 -0
- tests/1a_test_basico.py +43 -0
- tests/1b_test_history.py +72 -0
- tests/1c_test_basico.py +61 -0
- tests/2a_test_tool_python.py +50 -0
- tests/2b_test_tool_calculator.py +54 -0
- tests/2c_test_tool_savefile.py +50 -0
- tests/3a_test_upload_md.py +46 -0
- tests/3b_test_upload_img.py +43 -0
- tests/3c_test_upload_pdf_compare.py +44 -0
- tests/4_test_RAG.py +56 -0
- tests/5_test_MAS.py +58 -0
- tests/6a_test_MCP_tool_CRM.py +77 -0
- tests/6b_test_MCP_tool_ITSM.py +72 -0
- tests/6c_test_MCP_tool_SQL.py +69 -0
- tests/6d_test_MCP_tool_DOC_SQL.py +45 -0
- tests/7a_test_BI_CSV.py +37 -0
- tests/7b_test_BI_SQL.py +47 -0
- tests/8a_test_external_tool.py +194 -0
- tests/helpers.py +60 -0
oskaragent/helpers.py
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, reveal_type
|
|
6
|
+
import secrets
|
|
7
|
+
import string
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Optional, Callable
|
|
10
|
+
from colorama import Fore, Style, init as colorama_init
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Initialize colorama once
|
|
15
|
+
colorama_init(autoreset=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
_COLOR_MAP = {
|
|
19
|
+
"BLUE": Fore.BLUE,
|
|
20
|
+
"MAGENTA": Fore.MAGENTA,
|
|
21
|
+
"GREEN": Fore.GREEN,
|
|
22
|
+
"YELLOW": Fore.YELLOW,
|
|
23
|
+
"RED": Fore.RED,
|
|
24
|
+
"CYAN": Fore.CYAN,
|
|
25
|
+
"WHITE": Fore.WHITE,
|
|
26
|
+
"RESET": Style.RESET_ALL,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
# Types
|
|
30
|
+
ToolFn = Callable[..., Any]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def log_msg(message: str, *, func: str = "", action: str = "", color: str = "MAGENTA") -> None:
|
|
34
|
+
"""Log a colorized message with contextual metadata.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
message (str): Texto principal que será impresso no log.
|
|
38
|
+
func (str, optional): Nome da função que originou o log. Defaults to "".
|
|
39
|
+
action (str, optional): Identificador adicional para a ação relacionada. Defaults to "".
|
|
40
|
+
color (str, optional): Código de cor aceito por `colorama` para o texto. Defaults to "MAGENTA".
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
None: Esta função não retorna valor; apenas escreve na saída padrão.
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
47
|
+
col = _COLOR_MAP.get(color.upper(), Fore.BLUE)
|
|
48
|
+
reset = Style.RESET_ALL
|
|
49
|
+
func_part = f" [func={func}]" if func else ""
|
|
50
|
+
action_part = f" [action={action}]" if action else ""
|
|
51
|
+
print(f"{col}[{ts}]{func_part}{action_part} {message}{reset}")
|
|
52
|
+
|
|
53
|
+
except Exception:
|
|
54
|
+
# Fallback to plain print if anything goes wrong
|
|
55
|
+
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
56
|
+
print(f"[{ts}] {message}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def gen_randon_filename(name_length: int = 10, prefix: str = "") -> str:
|
|
60
|
+
"""Generate a random filename composed of ASCII letters and digits.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
name_length (int, optional): Quantidade de caracteres aleatórios desejada. Defaults to 10.
|
|
64
|
+
prefix (str, optional): Texto opcional a ser prefixado ao nome gerado. Defaults to "".
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
str: Nome de arquivo construído a partir do prefixo (quando fornecido) seguido dos caracteres randômicos.
|
|
68
|
+
"""
|
|
69
|
+
chars = string.ascii_letters + string.digits
|
|
70
|
+
base_name = ''.join(secrets.choice(chars) for _ in range(name_length))
|
|
71
|
+
return prefix + base_name
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@dataclass
|
|
75
|
+
class ToolContext:
|
|
76
|
+
session_id: str
|
|
77
|
+
message_id: str
|
|
78
|
+
agent_id: str
|
|
79
|
+
is_verbose: bool = False
|
|
80
|
+
input_data: dict[str, Any] = field(default_factory=dict)
|
|
81
|
+
subordinate_agents: list[Any] = field(default_factory=list)
|
|
82
|
+
mcp_tools_schema: list[dict] = field(default_factory=dict)
|
|
83
|
+
external_tools_schema: list[dict] = field(default_factory=dict)
|
|
84
|
+
retrievers: list[Any] = field(default_factory=list)
|
|
85
|
+
working_folder: Optional[str] = None
|
|
86
|
+
knowledge_base: Optional[list[dict[str, Any]]] = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def vlog(ctx: "ToolContext | None", message: str, *, func: str = "") -> None:
|
|
90
|
+
"""Print verbose diagnostic logs when the tool context requires it.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
ctx (ToolContext | None): Contexto da execução da ferramenta, contendo flag `is_verbose`.
|
|
94
|
+
message (str): Texto que será emitido no log.
|
|
95
|
+
func (str, optional): Nome da função geradora da mensagem. Defaults to "".
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
None: Apenas encaminha mensagens para `log_msg` quando habilitado.
|
|
99
|
+
"""
|
|
100
|
+
if ctx and getattr(ctx, "is_verbose", False):
|
|
101
|
+
log_msg(message, func=func or "tool", action="tool", color="MAGENTA")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def render_file_as_content_blocks(file_path: str) -> list[dict[str, Any]]:
|
|
105
|
+
"""Converte um arquivo em blocos compatíveis com a Responses API.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
file_path (str): Caminho absoluto ou relativo para o arquivo de entrada.
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
list[dict[str, Any]]: Coleção de blocos estruturados (`input_text` ou `input_image`).
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: Quando o arquivo não pode ser lido, excede limites ou possui tipo não suportado.
|
|
115
|
+
"""
|
|
116
|
+
# Extract filename from path
|
|
117
|
+
filename = os.path.basename(file_path)
|
|
118
|
+
|
|
119
|
+
# 1. Carregar o JSON como texto puro
|
|
120
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
121
|
+
text = f.read()
|
|
122
|
+
|
|
123
|
+
# 2. Definir tamanho dos pedaços (chunks)
|
|
124
|
+
CHUNK_SIZE = 50_000
|
|
125
|
+
|
|
126
|
+
chunks = [
|
|
127
|
+
text[i:i + CHUNK_SIZE]
|
|
128
|
+
for i in range(0, len(text), CHUNK_SIZE)
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
# 3. Construir as mensagens a serem enviadas
|
|
132
|
+
messages = [
|
|
133
|
+
{
|
|
134
|
+
"type": "input_text",
|
|
135
|
+
"text": (
|
|
136
|
+
f"Vou enviar o conteúdo de do arquivo '{filename}' em múltiplas partes. "
|
|
137
|
+
"Não responda ainda. Apenas armazene cada parte. "
|
|
138
|
+
"Quando eu enviar a mensagem FINAL, você terá o arquivo inteiro."
|
|
139
|
+
)
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
# Inserir cada pedaço como mensagem separada
|
|
144
|
+
for i, chunk in enumerate(chunks):
|
|
145
|
+
messages.append(
|
|
146
|
+
{
|
|
147
|
+
"type": "input_text",
|
|
148
|
+
"text": f"[PARTE {i + 1}/{len(chunks)}]\n{chunk}"
|
|
149
|
+
}
|
|
150
|
+
)
|
|
151
|
+
# Mensagem final instruindo o modelo a analisar os dados
|
|
152
|
+
messages.append({
|
|
153
|
+
"type": "input_text",
|
|
154
|
+
"text": (
|
|
155
|
+
f"FINAL. Agora que você recebeu todas as partes do arquivo {filename}, "
|
|
156
|
+
"analise o conteúdo completo e produza a resposta solicitada."
|
|
157
|
+
)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
return messages
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def get_working_folder_path(base_folder: Path, session_id: str) -> Path:
|
|
164
|
+
# cria o nome da pasta de trabalho se uma sessão
|
|
165
|
+
if base_folder.resolve().as_posix().endswith(session_id):
|
|
166
|
+
# o base_folder já é a pasta de trabalho
|
|
167
|
+
return base_folder
|
|
168
|
+
return Path(base_folder if base_folder else Path("_oskar_working_folder")) / "oskar_sessions"/ session_id
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def create_working_folder(base_folder: Path | None, session_id: str) -> str:
|
|
172
|
+
# cria uma pasta de trabalho para manter os arquivos temporários, se a pasta não existir
|
|
173
|
+
session_dir = get_working_folder_path(base_folder, session_id)
|
|
174
|
+
session_dir.mkdir(parents=True, exist_ok=True)
|
|
175
|
+
return session_dir.resolve().as_posix()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: oskaragent
|
|
3
|
+
Version: 0.1.38a0
|
|
4
|
+
Summary: Plataforma de agente conversacional Oskar.
|
|
5
|
+
Author: José Carlos Cordeiro
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: openai==2.9.0
|
|
10
|
+
Requires-Dist: mcp==1.25.0
|
|
11
|
+
Requires-Dist: faiss-cpu==1.13.1
|
|
12
|
+
Requires-Dist: numpy==2.3.5
|
|
13
|
+
Requires-Dist: pandas==2.3.3
|
|
14
|
+
Requires-Dist: pyodbc==5.3.0
|
|
15
|
+
Requires-Dist: pyyaml==6.0.3
|
|
16
|
+
Requires-Dist: pypdf==6.4.1
|
|
17
|
+
Requires-Dist: pdfkit==1.0.0
|
|
18
|
+
Requires-Dist: pdfminer.six==20251107
|
|
19
|
+
Requires-Dist: python-docx==1.2.0
|
|
20
|
+
Requires-Dist: python-pptx==1.0.2
|
|
21
|
+
Requires-Dist: pillow==12.0.0
|
|
22
|
+
Requires-Dist: lxml==6.0.2
|
|
23
|
+
Requires-Dist: markdown2==2.5.4
|
|
24
|
+
Requires-Dist: beautifulsoup4==4.14.3
|
|
25
|
+
Requires-Dist: matplotlib==3.10.8
|
|
26
|
+
Requires-Dist: seaborn==0.13.2
|
|
27
|
+
Requires-Dist: tabulate==0.9.0
|
|
28
|
+
Requires-Dist: colorama==0.4.6
|
|
29
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
oskaragent/__init__.py,sha256=R1R-z9EYLQBy_wRaa79kO_jRTTCidqpLqMKsUIwq6l0,1121
|
|
2
|
+
oskaragent/agent.py,sha256=B7KwPxGiNw40A0TpUPFHftU2hwqLC0w8sY6_dclAYX4,85449
|
|
3
|
+
oskaragent/agent_config.py,sha256=CYDSsJFfdrFTU-9UeonZhjzBocOXbUu5IjhM6jYxhl8,10320
|
|
4
|
+
oskaragent/agent_mcp_tools.py,sha256=LGRiecH-gBMsS2I1L7aGrZiWwc2R8gp8lN3aJpWlveY,4338
|
|
5
|
+
oskaragent/agent_tools.py,sha256=TowXGkclirnZq9u6_-QOq6jzbJYS3Uv3xU5YqgyExOU,39020
|
|
6
|
+
oskaragent/helpers.py,sha256=J53QnC9-y4PiEBoFsfpWWeL1zouWLlqfEMmZE8KyTsM,6113
|
|
7
|
+
oskaragent-0.1.38a0.dist-info/licenses/LICENSE,sha256=cdONOmAtqpa6SxVG7sCcw-2czBq0iuD9Exn7YQLP6Ek,1075
|
|
8
|
+
tests/1a_test_basico.py,sha256=kuaHOJk5nGkSuuXwvGPzIPtvJREyEIkU2WX-8OyHKRI,1255
|
|
9
|
+
tests/1b_test_history.py,sha256=4NDvs1hCCgi2U5GrAhiPE0EO2MVzY5qbZPdS11DZ0zM,2318
|
|
10
|
+
tests/1c_test_basico.py,sha256=ZcpzUI1b8cz7m0dYT6SqpGWjY_-KpT7mtCHfYIAw8kQ,2073
|
|
11
|
+
tests/2a_test_tool_python.py,sha256=9cHp8oxHEbBfPdp_F4JxBu2zBOfEeSn07YHuKLkoCrA,1671
|
|
12
|
+
tests/2b_test_tool_calculator.py,sha256=Nau_tW4XIpXp-QE74tVhK2n8Bbh26c_wHinigu8wS3A,1771
|
|
13
|
+
tests/2c_test_tool_savefile.py,sha256=CahDZwKafXCGetY73eW5WKhpGP12D9EmiCkHKa6zWuU,1560
|
|
14
|
+
tests/3a_test_upload_md.py,sha256=ReHXFQMdSzEftqvCFUZZLFauqQBMcREVPwrgGhrA7I4,1259
|
|
15
|
+
tests/3b_test_upload_img.py,sha256=3uvZIDAwhvtu_FFeVL0QRQg_6xbzH9fEA_IWwD1cZfI,1184
|
|
16
|
+
tests/3c_test_upload_pdf_compare.py,sha256=-AUdOo1otw0gHZJjm18qywk9AiTjrfDR5hVFFLKTbm4,1350
|
|
17
|
+
tests/4_test_RAG.py,sha256=hSj_oMK5hKCnqcVXZK4BBZH6eif7_bkPPwxklJQdF_M,1884
|
|
18
|
+
tests/5_test_MAS.py,sha256=1VuZKroJR032wAbPIxGLvTh6nz2YmCv0wKX-3hrKXrU,2127
|
|
19
|
+
tests/6a_test_MCP_tool_CRM.py,sha256=mZOSe6FWmOFnIk9LdJCxFDWVMnCfmuudsBV0bEtzi-c,3908
|
|
20
|
+
tests/6b_test_MCP_tool_ITSM.py,sha256=BOeA3ALeehHm_EdJtalZoauDmmOIeQl2aFauD_5ZuM4,2826
|
|
21
|
+
tests/6c_test_MCP_tool_SQL.py,sha256=VPi2E91DKF6jp2uGGdVH_6eDY_Et5cV9eU40p_Btlso,2531
|
|
22
|
+
tests/6d_test_MCP_tool_DOC_SQL.py,sha256=Ft9e1jwWlcwcpl3mXd6u_o-adj7fFP6Hc8JqU1R-pwA,1336
|
|
23
|
+
tests/7a_test_BI_CSV.py,sha256=RN4ksfgItBHrn8Ve_Eyo7ecbm4NgOMulCrLcVMv2fXA,1087
|
|
24
|
+
tests/7b_test_BI_SQL.py,sha256=AyCWfTQ_1EYB7lPg8CKULTxaOdUG273yKuyqgR9UiM4,2268
|
|
25
|
+
tests/8a_test_external_tool.py,sha256=Mkmm5yx3ByVpwyq5o1LFOHtUfQf6DeED3Bn-4sxgFfA,8507
|
|
26
|
+
tests/helpers.py,sha256=6MsTer0ANW3Dwir5_-meb1ctpJ_MeM4t6mNLW2K0Xbg,1374
|
|
27
|
+
oskaragent-0.1.38a0.dist-info/METADATA,sha256=1G6-_3gYh4Yp6m6i7PCo-WuOdtfLUWXWLuyz88IojPQ,881
|
|
28
|
+
oskaragent-0.1.38a0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
oskaragent-0.1.38a0.dist-info/top_level.txt,sha256=P6Ksple4IyvbBAIrSJ71dP2j_jRntW4z8XLmou9TEfE,17
|
|
30
|
+
oskaragent-0.1.38a0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Oskar contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tests/1a_test_basico.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from oskaragent.agent import Oskar
|
|
7
|
+
from oskaragent.agent_config import AgentConfig
|
|
8
|
+
from tests.helpers import set_key
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
ag_cfg = AgentConfig(
|
|
24
|
+
model="gpt-5",
|
|
25
|
+
model_settings={
|
|
26
|
+
"history_window_size": 5, # parametrizável
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
31
|
+
|
|
32
|
+
# No explicit question provided: run a quick test prompt and exit
|
|
33
|
+
test_q = "Quem é o presidente do Brasil?"
|
|
34
|
+
res = agent.answer(test_q)
|
|
35
|
+
|
|
36
|
+
print(test_q)
|
|
37
|
+
# Ensure UTF-8 characters are printed properly (no ASCII escapes)
|
|
38
|
+
print(json.dumps(res, indent=2, ensure_ascii=False))
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
raise SystemExit(main())
|
tests/1b_test_history.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tests.helpers import set_key
|
|
7
|
+
from oskaragent.agent import Oskar
|
|
8
|
+
from oskaragent.agent_config import AgentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
# Build initial agent configuration
|
|
24
|
+
ag_cfg = AgentConfig(
|
|
25
|
+
model_settings={"history_window_size": 5},
|
|
26
|
+
system_prompt=(
|
|
27
|
+
"Você é um assistente prestativo chamado oskaragent. Mantenha e use o histórico para responder coerentemente."
|
|
28
|
+
),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Create the agent and send the first prompt
|
|
32
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
33
|
+
q1 = "Minha cor preferida é azul. Qual é sua cor preferida?"
|
|
34
|
+
print(f"\n[teste] Enviando primeira pergunta: {q1}")
|
|
35
|
+
r1 = agent.answer(q1)
|
|
36
|
+
print("[teste] Resposta 1:")
|
|
37
|
+
print(r1['content'])
|
|
38
|
+
|
|
39
|
+
# Export agent to JSON
|
|
40
|
+
j = agent.to_json()
|
|
41
|
+
|
|
42
|
+
# Destroy the agent (drop the reference)
|
|
43
|
+
del agent
|
|
44
|
+
|
|
45
|
+
# Recreate a new agent importing the exported JSON
|
|
46
|
+
print("[teste] Recriando agente a partir do JSON exportado…")
|
|
47
|
+
agent2 = Oskar.from_json(j, working_folder="./output")
|
|
48
|
+
|
|
49
|
+
# Send the second prompt which relies on remembered history
|
|
50
|
+
q2 = "Qual é minha cor preferida?"
|
|
51
|
+
print(f"\n[teste] Enviando segunda pergunta: {q2}")
|
|
52
|
+
r2 = agent2.answer(q2)
|
|
53
|
+
print("[teste] Resposta 2:")
|
|
54
|
+
print(r2['content'])
|
|
55
|
+
|
|
56
|
+
# Simple check: expect the model/agent to answer "azul"
|
|
57
|
+
content = (r2 or {}).get("content", "")
|
|
58
|
+
ok = isinstance(content, str) and ("azul" in content.lower())
|
|
59
|
+
status = "OK" if ok else "ATENÇÃO"
|
|
60
|
+
print(f"\n[verificação] Esperado conter 'azul' → {status}")
|
|
61
|
+
|
|
62
|
+
print('-' * 40)
|
|
63
|
+
print(json.dumps(j, indent=2, ensure_ascii=False))
|
|
64
|
+
|
|
65
|
+
print('-' * 40)
|
|
66
|
+
print(json.dumps(agent2.to_json(), indent=2, ensure_ascii=False))
|
|
67
|
+
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
raise SystemExit(main())
|
tests/1c_test_basico.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from textwrap import dedent
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from oskaragent.agent import Oskar
|
|
7
|
+
from oskaragent.agent_config import AgentConfig
|
|
8
|
+
from tests.helpers import set_key
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
ag_cfg = AgentConfig(
|
|
24
|
+
model="gpt-5",
|
|
25
|
+
model_settings={
|
|
26
|
+
"history_window_size": 5, # parametrizável
|
|
27
|
+
}
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
31
|
+
|
|
32
|
+
# No explicit question provided: run a quick test prompt and exit
|
|
33
|
+
test_q = dedent("""
|
|
34
|
+
Estou desenvolvendo um programa CLI em Python para conversar com agentes de IA.
|
|
35
|
+
|
|
36
|
+
Quero que você:
|
|
37
|
+
|
|
38
|
+
1. Defina uma arquitetura de alto nível para o programa.
|
|
39
|
+
2. Proponha a estrutura de diretórios e arquivos.
|
|
40
|
+
3. Explique o fluxo de execução principal.
|
|
41
|
+
4. Indique como persistir histórico de conversas por agente.
|
|
42
|
+
5. Liste riscos técnicos e como mitigá-los.
|
|
43
|
+
|
|
44
|
+
Restrições:
|
|
45
|
+
- O programa deve funcionar no Windows.
|
|
46
|
+
- Deve ser compatível com Python 3.12+.
|
|
47
|
+
- O design deve permitir adicionar novos agentes sem alterar o núcleo.
|
|
48
|
+
- A resposta deve ser estruturada em seções claras.
|
|
49
|
+
|
|
50
|
+
Não escreva código agora. Foque apenas em arquitetura e decisões técnicas.
|
|
51
|
+
""")
|
|
52
|
+
res = agent.answer(test_q)
|
|
53
|
+
|
|
54
|
+
print(test_q)
|
|
55
|
+
# Ensure UTF-8 characters are printed properly (no ASCII escapes)
|
|
56
|
+
print(res['content'])
|
|
57
|
+
return 0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tests.helpers import set_key
|
|
7
|
+
from oskaragent.agent import Oskar
|
|
8
|
+
from oskaragent.agent_config import AgentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
# Allow tools we might want the model to call; not strictly required for this test
|
|
24
|
+
ag_cfg = AgentConfig(
|
|
25
|
+
tools_names=[
|
|
26
|
+
"execute_python_code_tool",
|
|
27
|
+
],
|
|
28
|
+
system_prompt=(
|
|
29
|
+
"Você é um assistente com capacidade para executar código python usando a ferramenta 'execute_python_code_tool'."
|
|
30
|
+
),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
34
|
+
|
|
35
|
+
question = (
|
|
36
|
+
"Crie um bloco de código Python para gerar um gráfico de barras simples usando matplotlib com três categorias (A, B, C) e valores, "
|
|
37
|
+
"adicionar rótulos de eixos e título, e chamar plt.show() no final. "
|
|
38
|
+
"Execute o código gerado usando a ferramenta execute_python_code_tool e retorne apenas o resultado final."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
res = agent.answer(question)
|
|
42
|
+
|
|
43
|
+
# Ensure UTF-8 characters are printed properly (no ASCII escapes)
|
|
44
|
+
print(json.dumps(res, indent=2, ensure_ascii=False))
|
|
45
|
+
|
|
46
|
+
return 0
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tests.helpers import set_key
|
|
7
|
+
from oskaragent.agent import Oskar
|
|
8
|
+
from oskaragent.agent_config import AgentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> int:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
# Allow the calculator tool explicitly (defaults are included, but we state intent)
|
|
24
|
+
ag_cfg = AgentConfig(
|
|
25
|
+
tools_names=[
|
|
26
|
+
"calculator_tool",
|
|
27
|
+
],
|
|
28
|
+
system_prompt=(
|
|
29
|
+
"Você é um assistente focado em cálculos. Sempre que houver uma expressão matemática, "
|
|
30
|
+
"use a ferramenta 'calculator_tool' para avaliar e retorne apenas o número final."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
35
|
+
|
|
36
|
+
# Expression chosen to exercise math functions available to the tool
|
|
37
|
+
# expression = "2**10 + sqrt(144) + sin(pi/2)" # 1024 + 12 + 1 = 1037.0
|
|
38
|
+
expression = "1024 + 12 + 1" # 1024 + 12 + 1 = 1037.0
|
|
39
|
+
question = (
|
|
40
|
+
"Calcule a expressão a seguir usando a ferramenta calculator_tool e retorne apenas o número final: "
|
|
41
|
+
f"{expression}"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
res = agent.answer(question, action='tool:calculator_tool')
|
|
45
|
+
|
|
46
|
+
# Ensure UTF-8 characters are printed properly (no ASCII escapes)
|
|
47
|
+
print(json.dumps(res, indent=2, ensure_ascii=False))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
return 0
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
if __name__ == "__main__":
|
|
54
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from tests.helpers import set_key
|
|
7
|
+
from oskaragent.agent import Oskar
|
|
8
|
+
from oskaragent.agent_config import AgentConfig
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _response_log(assistant_message_struct: dict[str, Any]):
|
|
12
|
+
usage = assistant_message_struct.get("usage", {}) or {}
|
|
13
|
+
print(
|
|
14
|
+
f"[callback] message_id={assistant_message_struct.get('message_id')} "
|
|
15
|
+
f"tokens={{'input_tokens': {usage.get('input_tokens')}, 'output_tokens': {usage.get('output_tokens')}, 'total_tokens': {usage.get('total_tokens')}}}"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> None:
|
|
20
|
+
# Set OpenAI API key
|
|
21
|
+
set_key()
|
|
22
|
+
|
|
23
|
+
# Habilita explicitamente a ferramenta de escrita em arquivo
|
|
24
|
+
ag_cfg = AgentConfig(
|
|
25
|
+
tools_names=[
|
|
26
|
+
"write_file_tool",
|
|
27
|
+
],
|
|
28
|
+
system_prompt=(
|
|
29
|
+
"Você é um agente chamado oskaragent. Quando solicitado a salvar conteúdo, "
|
|
30
|
+
"use a ferramenta 'write_file_tool'."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
agent = Oskar(agent_config=ag_cfg, response_callback=_response_log, is_verbose=True)
|
|
35
|
+
|
|
36
|
+
# 1) Expressão regular para telefone internacional
|
|
37
|
+
q1 = "Gere um diagrama plantuml de uma expressão regular para telefone internacional."
|
|
38
|
+
agent.answer(q1)
|
|
39
|
+
print("[q1] enviado")
|
|
40
|
+
|
|
41
|
+
# 3) Solicitar que o agente salve o diagrama em arquivo (sem ação explícita)
|
|
42
|
+
q3 = "Salve o diagrama plantuml num arquivo."
|
|
43
|
+
r3 = agent.answer(q3)
|
|
44
|
+
|
|
45
|
+
# Imprime o JSON completo da última resposta
|
|
46
|
+
print(json.dumps(r3, indent=2, ensure_ascii=False))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == "__main__":
|
|
50
|
+
main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from oskaragent.agent import Oskar
|
|
7
|
+
from oskaragent.agent_config import AgentConfig
|
|
8
|
+
from tests.helpers import set_key
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> None:
|
|
12
|
+
# Set OpenAI API key
|
|
13
|
+
set_key()
|
|
14
|
+
|
|
15
|
+
# Caminho do arquivo Markdown a ser resumido
|
|
16
|
+
md_path = Path("sources/cristianismo.md").resolve()
|
|
17
|
+
if not md_path.exists():
|
|
18
|
+
raise FileNotFoundError(f"Arquivo não encontrado: {md_path}")
|
|
19
|
+
|
|
20
|
+
# Instancia o agente oskaragent (com configurações padrão)
|
|
21
|
+
agent_cfg = AgentConfig(
|
|
22
|
+
model_settings={
|
|
23
|
+
"history_window_size": 5, # parametrizável
|
|
24
|
+
},
|
|
25
|
+
)
|
|
26
|
+
agent = Oskar(agent_config=agent_cfg, is_verbose=True)
|
|
27
|
+
|
|
28
|
+
# Pergunta ao agente para fazer um resumo do arquivo
|
|
29
|
+
question = (
|
|
30
|
+
"Por favor, leia o arquivo anexo e produza um resumo objetivo em português, "
|
|
31
|
+
"destacando as ideias principais em 5–7 linhas."
|
|
32
|
+
)
|
|
33
|
+
result: dict[str, Any] = agent.answer(
|
|
34
|
+
question=question,
|
|
35
|
+
attached_files=str(md_path)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Extrai e imprime o conteúdo da resposta (resumo)
|
|
39
|
+
content = (result or {}).get("content") or ""
|
|
40
|
+
print("Resumo gerado:\n")
|
|
41
|
+
print(content)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
main()
|
|
46
|
+
|