File size: 8,142 Bytes
ce52794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69f9cd1
 
ce52794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69f9cd1
ce52794
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
"""Checklist de aceite do endpoint Programa.

Cobre, nesta ordem: /v1/models, chat simples, stream, system, multi-turn,
tools + tool_choice=auto, tool result + continuacao, max_tokens, temperature,
stop, /v1/responses e /v1/messages (somente se o servidor expuser de verdade).

Uso:
  BASE=https://erinaldordso-programa.hf.space/v1 python3 tests/test_agent.py
  BASE=... MODEL=... APIKEY=... python3 tests/test_agent.py

Nao declara sucesso parcial: qualquer FAIL aborta com exit 1.
"""
import json
import os
import sys
import urllib.request

import tiktoken

BASE = os.environ.get("BASE", "https://erinaldordso-minecraft-friends-ai-zerogpu.hf.space/v1").rstrip("/")
MODEL = os.environ.get("MODEL", "granite-4.0-h-tiny")
APIKEY = os.environ.get("APIKEY", "")
PASS, FAIL = "PASS", "FAIL"
results = []


def req(method, path, body=None, stream=False):
    url = BASE + path
    data = json.dumps(body).encode() if body is not None else None
    headers = {"Content-Type": "application/json"}
    if APIKEY:
        headers["Authorization"] = f"Bearer {APIKEY}"
    r = urllib.request.Request(url, data=data, headers=headers, method=method)
    if not stream:
        with urllib.request.urlopen(r, timeout=300) as resp:
            return resp.status, json.loads(resp.read().decode())
    out = []
    with urllib.request.urlopen(r, timeout=300) as resp:
        for line in resp:
            s = line.decode(errors="replace").strip()
            if s.startswith("data:"):
                s = s[5:].strip()
                if s == "[DONE]":
                    break
                try:
                    out.append(json.loads(s))
                except json.JSONDecodeError:
                    pass
    return 200, out


def check(name, cond, extra=""):
    results.append((name, bool(cond)))
    print(f"[{PASS if cond else FAIL}] {name} {extra}")
    if not cond:
        raise SystemExit(f"FAIL em {name}: {extra}")


TOOLS = [{"type": "function", "function": {
    "name": "read_file",
    "description": "Read a file from the project",
    "parameters": {"type": "object",
                   "properties": {"path": {"type": "string"}},
                   "required": ["path"]}}},
    {"type": "function", "function": {
        "name": "list_directory",
        "description": "List a directory",
        "parameters": {"type": "object",
                       "properties": {"path": {"type": "string"}},
                       "required": ["path"]}}},
    {"type": "function", "function": {
        "name": "execute_command",
        "description": "Run a shell command",
        "parameters": {"type": "object",
                       "properties": {"command": {"type": "string"}},
                       "required": ["command"]}}}]


def main():
    print(f"BASE={BASE} MODEL={MODEL}")

    st, models = req("GET", "/models")
    ids = [m.get("id", "") for m in models.get("data", [])]
    check("models", st == 200 and any(MODEL in i or "granite" in i.lower() for i in ids), f"ids={ids[:4]}")

    st, emb = req("POST", "/embeddings", {"model": "text-embedding-ada-002", "input": ["mine wood", "craft pickaxe"]})
    vecs = emb.get("data", [])
    check("embeddings Voyager", st == 200 and len(vecs) == 2 and all(isinstance(x.get("embedding"), list) and len(x.get("embedding")) > 0 for x in vecs), f"vectors={len(vecs)}")

    enc = tiktoken.get_encoding("cl100k_base")
    st, emb = req("POST", "/embeddings", {"model": "text-embedding-ada-002", "input": [enc.encode("mine wood"), enc.encode("craft pickaxe")]})
    vecs = emb.get("data", [])
    check("embeddings token input", st == 200 and len(vecs) == 2 and all(isinstance(x.get("embedding"), list) and len(x.get("embedding")) > 0 for x in vecs), f"vectors={len(vecs)}")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL, "messages": [{"role": "user", "content": "diga ok"}],
        "max_tokens": 16, "temperature": 0})
    txt = r["choices"][0]["message"].get("content", "")
    check("chat stream=false", st == 200 and isinstance(txt, str), f"len={len(txt)}")

    st, chunks = req("POST", "/chat/completions", {
        "model": MODEL, "messages": [{"role": "user", "content": "conte ate 3"}],
        "max_tokens": 32, "temperature": 0, "stream": True}, stream=True)
    joined = "".join((c.get("choices", [{}])[0].get("delta", {}) or {}).get("content") or "" for c in chunks)
    check("chat stream=true", st == 200 and len(chunks) > 0, f"chunks={len(chunks)} chars={len(joined)}")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL,
        "messages": [{"role": "system", "content": "You are an autonomous software engineering agent."},
                     {"role": "user", "content": "diga ok"}],
        "max_tokens": 16, "temperature": 0})
    check("system message", st == 200, "")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL,
        "messages": [{"role": "user", "content": "meu projeto usa npm"},
                     {"role": "assistant", "content": "anotado"},
                     {"role": "user", "content": "qual gerenciador citei?"}],
        "max_tokens": 32, "temperature": 0})
    check("multi-turn", st == 200 and "npm" in json.dumps(r).lower(), "")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL,
        "messages": [
            {"role": "system", "content": "You are an autonomous software engineering agent. Use tools instead of describing hypothetical actions. Inspect files before modifying them."},
            {"role": "user", "content": "Leia package.json e diga qual comando executa os testes."}],
        "tools": TOOLS[:1], "tool_choice": "auto",
        "max_tokens": 256, "temperature": 0})
    msg = r["choices"][0]["message"]
    tcs = msg.get("tool_calls") or []
    ok = (r["choices"][0].get("finish_reason") == "tool_calls" and tcs
          and tcs[0]["function"]["name"] == "read_file"
          and "package.json" in tcs[0]["function"].get("arguments", ""))
    check("tools read_file", ok, f"finish={r['choices'][0].get('finish_reason')} calls={json.dumps(tcs)[:200]}")

    tc = tcs[0]
    st, r = req("POST", "/chat/completions", {
        "model": MODEL,
        "messages": [
            {"role": "system", "content": "You are an autonomous software engineering agent."},
            {"role": "user", "content": "Leia package.json e diga qual comando executa os testes."},
            {"role": "assistant", "tool_calls": [{
                "id": tc.get("id", "call_1"), "type": "function",
                "function": {"name": "read_file", "arguments": tc["function"]["arguments"]}}]},
            {"role": "tool", "tool_call_id": tc.get("id", "call_1"),
             "content": "{\"name\": \"demo\", \"scripts\": {\"test\": \"npm test\"}}"}],
        "max_tokens": 128, "temperature": 0})
    txt2 = r["choices"][0]["message"].get("content", "")
    check("tool result + continuacao", st == 200 and "npm test" in txt2, f"resp={txt2[:160]}")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL, "messages": [{"role": "user", "content": "fale muito"}],
        "max_tokens": 8, "temperature": 0})
    check("max_tokens respeitado", st == 200, "")

    st, r = req("POST", "/chat/completions", {
        "model": MODEL, "messages": [{"role": "user", "content": "diga ok"}],
        "max_tokens": 16, "temperature": 0.7, "stop": ["<|im_end|>"]})
    check("temperature+stop", st == 200, "")

    for path, label in [("/responses", "responses"), ("/messages", "messages")]:
        try:
            if label == "responses":
                st, r = req("POST", path, {"model": MODEL, "input": "diga ok", "max_output_tokens": 16})
            else:
                st, r = req("POST", path, {"model": MODEL, "max_tokens": 16,
                                           "messages": [{"role": "user", "content": "diga ok"}]})
            check(label, st in (200, 201), f"status={st}")
        except Exception as e:  # noqa: BLE001
            print(f"[SKIP] {label} ausente neste build ({str(e)[:120]})")

    print(f"\nTodos os checks obrigatorios passaram ({len(results)}).")


if __name__ == "__main__":
    main()