File size: 4,657 Bytes
3fd1a35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
#!/usr/bin/env python3
"""Run against an initialized portable service; Python is client-side only."""
import argparse
import concurrent.futures
import http.client
import json
import time
import urllib.error
import urllib.request


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--url", default="http://127.0.0.1:19093")
    args = p.parse_args()
    base = args.url.rstrip("/")

    def call(path, body=None):
        request = urllib.request.Request(base + path, None if body is None else json.dumps(body).encode(),
                                         {"Content-Type": "application/json"})
        with urllib.request.urlopen(request, timeout=120) as r:
            return json.load(r)

    def request(text, **kwargs):
        return {"model": "mindnano-ling3-tiny", "messages": [{"role": "user", "content": text}],
                "max_tokens": 24, "temperature": 0, "cache_prompt": False, **kwargs}

    health = call("/health")
    assert health["status"] == "ok"
    assert call("/v1/models")["data"][0]["context_length"] == health["context_length"]
    for patch, status in [({"model": "bad"}, 404), ({"max_tokens": -1}, 400),
                          ({"tools": []}, 400), ({"messages": []}, 400),
                          ({"messages": [{"role": "user", "content": "1" * (health["context_length"] + 1)}]}, 400)]:
        try:
            call("/v1/chat/completions", request("你好", **patch))
            raise AssertionError("invalid request accepted")
        except urllib.error.HTTPError as e:
            assert e.code == status, (e.code, e.read())
            assert "error" in json.load(e)

    a = call("/v1/chat/completions", request("请用一句话介绍你自己。"))
    b = call("/v1/chat/completions", request("只回答:一加一等于几?"))
    again = call("/v1/chat/completions", request("请用一句话介绍你自己。"))
    assert a["choices"][0]["message"]["content"] == again["choices"][0]["message"]["content"], "request isolation/reset failed"
    for r in (a, b, again):
        assert r["object"] == "chat.completion"
        assert r["usage"]["total_tokens"] == r["usage"]["prompt_tokens"] + r["usage"]["completion_tokens"]
        assert r["mindnano_metrics"]["cached_tokens"] == 0

    r = urllib.request.Request(base + "/v1/chat/completions", json.dumps(
        request("请用一句话介绍你自己。", stream=True, stream_options={"include_usage": True})).encode(),
        {"Content-Type": "application/json"})
    content = ""; done = False; metrics = None; usage = None
    with urllib.request.urlopen(r, timeout=120) as response:
        for raw in response:
            line = raw.decode("utf-8").strip()
            if not line.startswith("data: "):
                continue
            if line == "data: [DONE]":
                done = True; break
            event = json.loads(line[6:])
            assert "error" not in event, event
            for c in event["choices"]:
                content += c["delta"].get("content", "")
            metrics = event.get("mindnano_metrics", metrics)
            usage = event.get("usage") or usage
    assert done and usage and metrics
    assert content == a["choices"][0]["message"]["content"], "SSE text mismatch"

    # Cancel non-stream generation; health/cancel must remain available while NPU is busy.
    with concurrent.futures.ThreadPoolExecutor() as pool:
        future = pool.submit(call, "/v1/chat/completions", request("详细写一篇长篇故事。", max_tokens=1024))
        for _ in range(100):
            if call("/health")["active"]:
                break
            time.sleep(.02)
        else:
            raise AssertionError("generation did not start")
        try:
            call("/v1/chat/completions", request("并发请求"))
            raise AssertionError("concurrent request was not rejected")
        except urllib.error.HTTPError as e:
            assert e.code == 429
        assert call("/v1/cancel", {})["cancel_requested"]
        try:
            future.result(timeout=10)
            raise AssertionError("cancel returned successful completion")
        except urllib.error.HTTPError as e:
            assert e.code == 409
    final = call("/v1/chat/completions", request("请用一句话介绍你自己。"))
    assert final["choices"][0]["message"]["content"] == content, "canceled state leaked"
    print(json.dumps({"passed": True, "context": health["context_length"],
                      "sample": content, "metrics": [a["mindnano_metrics"], b["mindnano_metrics"], metrics]},
                     ensure_ascii=False, indent=2))


if __name__ == "__main__":
    main()