motif64

m64_958790edae53 / server.py

94 lines, 4.0 KBraw
1"""hello-world: an OpenAI-compatible chat server that greets you back (stdlib only).23m64 does the rest: it serves AGENT.md at /agent, human/ at /human/, and its own4/status, and lets only the chat routes through to this server.5"""67import json8import os9import time10from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer1112PORT = int(os.environ["M64_PORT"])13MODEL = "hello-world"141516def reply_to(messages):17    """The whole "model": greet, and repeat the last user message."""18    last = next((m.get("content") for m in reversed(messages) if m.get("role") == "user"), "")19    if isinstance(last, list):  # content parts: keep the text ones20        last = " ".join(p.get("text", "") for p in last if isinstance(p, dict))21    return f"Hello, world! You said: {last}" if last else "Hello, world!"222324class Handler(BaseHTTPRequestHandler):25    protocol_version = "HTTP/1.1"2627    def log_message(self, fmt, *args):28        if self.path != "/health":  # health is polled often; keep the log readable29            super().log_message(fmt, *args)3031    def _json(self, status, obj):32        data = json.dumps(obj).encode()33        self.send_response(status)34        self.send_header("Content-Type", "application/json")35        self.send_header("Content-Length", str(len(data)))36        self.end_headers()37        self.wfile.write(data)3839    def do_GET(self):40        if self.path == "/health":41            return self._json(200, {"status": "ok"})42        if self.path == "/v1/models":43            return self._json(200, {"object": "list", "data": [{"id": MODEL, "object": "model", "owned_by": "hello-world"}]})44        return self._json(404, {"error": "not found"})4546    def do_POST(self):47        if self.path != "/v1/chat/completions":48            return self._json(404, {"error": "not found"})49        try:50            body = json.loads(self.rfile.read(int(self.headers.get("Content-Length") or 0)) or b"{}")51            messages = body["messages"]52            assert isinstance(messages, list)53        except (ValueError, KeyError, AssertionError):54            return self._json(400, {"error": "send a JSON body with a messages list"})55        text = reply_to(messages)56        now = int(time.time())57        if body.get("stream") is True:58            return self._stream(text, now)59        return self._json(200, {60            "id": f"hello-{now}", "object": "chat.completion", "created": now, "model": MODEL,61            "choices": [{"index": 0, "message": {"role": "assistant", "content": text}, "finish_reason": "stop"}],62            "usage": {"prompt_tokens": 0, "completion_tokens": len(text.split()), "total_tokens": len(text.split())},63        })6465    def _stream(self, text, now):66        """Server-sent events, one word per chunk, the way OpenAI-compatible clients expect."""67        self.send_response(200)68        self.send_header("Content-Type", "text/event-stream")69        self.send_header("Cache-Control", "no-cache")70        self.send_header("Connection", "close")71        self.end_headers()72        self.close_connection = True73        words = text.split(" ")74        for i, word in enumerate(words):75            delta = {"content": word + (" " if i < len(words) - 1 else "")}76            chunk = {"id": f"hello-{now}", "object": "chat.completion.chunk", "created": now, "model": MODEL,77                     "choices": [{"index": 0, "delta": delta, "finish_reason": None}]}78            self.wfile.write(f"data: {json.dumps(chunk)}\n\n".encode())79        done = {"id": f"hello-{now}", "object": "chat.completion.chunk", "created": now, "model": MODEL,80                "choices": [{"index": 0, "delta": {}, "finish_reason": "stop"}]}81        self.wfile.write(f"data: {json.dumps(done)}\n\ndata: [DONE]\n\n".encode())828384def main():85    server = ThreadingHTTPServer(("0.0.0.0", PORT), Handler)86    server.daemon_threads = True87    # Tell m64 we are ready; it still waits for /health to answer 200.88    with open(os.environ["M64_STATUS"], "a") as f:89        f.write(json.dumps({"phase": "ready", "detail": "saying hello"}) + "\n")90    server.serve_forever()919293if __name__ == "__main__":94    main()