feat: scaffold meshnet monorepo
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
.pytest_cache/
|
||||||
|
*.egg-info/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Local environments
|
||||||
|
.venv/
|
||||||
|
|
||||||
|
# Ralph local runtime state
|
||||||
|
.ralph-tui/
|
||||||
@@ -20,9 +20,10 @@
|
|||||||
"Commit only this story's code changes with a focused conventional commit message"
|
"Commit only this story's code changes with a focused conventional commit message"
|
||||||
],
|
],
|
||||||
"priority": 1,
|
"priority": 1,
|
||||||
"passes": false,
|
"passes": true,
|
||||||
"notes": "Source issue: .scratch/distributed-inference-network/issues/01-monorepo-scaffold.md",
|
"notes": "Source issue: .scratch/distributed-inference-network/issues/01-monorepo-scaffold.md",
|
||||||
"dependsOn": []
|
"dependsOn": [],
|
||||||
|
"completionNotes": "Completed by Ralph iteration 7b260695; verified by pytest and editable package installs."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "US-002",
|
"id": "US-002",
|
||||||
|
|||||||
19
conftest.py
Normal file
19
conftest.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
"""Root conftest: add all package source directories to sys.path for pytest discovery."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
_root = pathlib.Path(__file__).parent
|
||||||
|
_packages = [
|
||||||
|
"packages/node",
|
||||||
|
"packages/gateway",
|
||||||
|
"packages/tracker",
|
||||||
|
"packages/sdk",
|
||||||
|
"packages/contracts",
|
||||||
|
"packages/p2p",
|
||||||
|
]
|
||||||
|
|
||||||
|
for _pkg in _packages:
|
||||||
|
_path = str(_root / _pkg)
|
||||||
|
if _path not in sys.path:
|
||||||
|
sys.path.insert(0, _path)
|
||||||
3
packages/contracts/meshnet_contracts/__init__.py
Normal file
3
packages/contracts/meshnet_contracts/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet_contracts — Solana smart contract wrappers for the Distributed Inference Network."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
13
packages/contracts/pyproject.toml
Normal file
13
packages/contracts/pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet-contracts"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network Solana smart contract wrappers"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet_contracts*"]
|
||||||
3
packages/gateway/meshnet_gateway/__init__.py
Normal file
3
packages/gateway/meshnet_gateway/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet_gateway — Distributed Inference Network HTTP gateway."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
45
packages/gateway/meshnet_gateway/cli.py
Normal file
45
packages/gateway/meshnet_gateway/cli.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
"""meshnet-gateway CLI entry point."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="meshnet-gateway",
|
||||||
|
description="Distributed Inference Network HTTP gateway",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command")
|
||||||
|
|
||||||
|
start_cmd = subparsers.add_parser("start", help="Start the gateway server")
|
||||||
|
start_cmd.add_argument("--tracker", default="http://localhost:8080", help="Tracker URL")
|
||||||
|
start_cmd.add_argument(
|
||||||
|
"--node-url",
|
||||||
|
default="http://localhost:7000",
|
||||||
|
help="Node URL to route to until tracker routing is implemented",
|
||||||
|
)
|
||||||
|
start_cmd.add_argument("--port", type=int, default=8080, help="Port to listen on")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "start":
|
||||||
|
from meshnet_gateway.server import GatewayServer
|
||||||
|
|
||||||
|
gateway = GatewayServer(node_url=args.node_url, port=args.port)
|
||||||
|
port = gateway.start()
|
||||||
|
print(f"meshnet-gateway listening on port {port}")
|
||||||
|
print(f"Tracker URL: {args.tracker}")
|
||||||
|
print(f"Routing to node URL: {args.node_url}")
|
||||||
|
try:
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
gateway.stop()
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
104
packages/gateway/meshnet_gateway/server.py
Normal file
104
packages/gateway/meshnet_gateway/server.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
"""Gateway HTTP server — accepts OpenAI-format requests and routes to inference nodes."""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
class _GatewayHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt, *args): # noqa: suppress request logs in tests
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
if self.path == "/v1/chat/completions":
|
||||||
|
self._handle_chat_completions()
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def _handle_chat_completions(self):
|
||||||
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
|
body = json.loads(self.rfile.read(length))
|
||||||
|
|
||||||
|
node_url = self.server.node_url # type: ignore[attr-defined]
|
||||||
|
payload = json.dumps({"messages": body.get("messages", [])}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{node_url}/v1/infer",
|
||||||
|
data=payload,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
node_resp = json.loads(r.read())
|
||||||
|
|
||||||
|
response_body = json.dumps({
|
||||||
|
"id": "chatcmpl-stub",
|
||||||
|
"object": "chat.completion",
|
||||||
|
"created": int(time.time()),
|
||||||
|
"model": body.get("model", "stub-model"),
|
||||||
|
"choices": [{
|
||||||
|
"index": 0,
|
||||||
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": node_resp["text"],
|
||||||
|
},
|
||||||
|
"finish_reason": "stop",
|
||||||
|
}],
|
||||||
|
"usage": {
|
||||||
|
"prompt_tokens": 0,
|
||||||
|
"completion_tokens": 0,
|
||||||
|
"total_tokens": 0,
|
||||||
|
},
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Content-Length", str(len(response_body)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(response_body)
|
||||||
|
|
||||||
|
|
||||||
|
class _GatewayHTTPServer(http.server.HTTPServer):
|
||||||
|
def __init__(self, addr, handler, node_url: str):
|
||||||
|
super().__init__(addr, handler)
|
||||||
|
self.node_url = node_url
|
||||||
|
|
||||||
|
|
||||||
|
class GatewayServer:
|
||||||
|
"""HTTP gateway that forwards /v1/chat/completions to a registered node."""
|
||||||
|
|
||||||
|
def __init__(self, node_url: str, host: str = "127.0.0.1", port: int = 0):
|
||||||
|
self._node_url = node_url
|
||||||
|
self._host = host
|
||||||
|
self._requested_port = port
|
||||||
|
self._server: _GatewayHTTPServer | None = None
|
||||||
|
self._thread: threading.Thread | None = None
|
||||||
|
self.port: int | None = None
|
||||||
|
|
||||||
|
def start(self) -> int:
|
||||||
|
if self._server is not None:
|
||||||
|
raise RuntimeError("GatewayServer is already running")
|
||||||
|
|
||||||
|
self._server = _GatewayHTTPServer(
|
||||||
|
(self._host, self._requested_port),
|
||||||
|
_GatewayHandler,
|
||||||
|
self._node_url,
|
||||||
|
)
|
||||||
|
self.port = self._server.server_address[1]
|
||||||
|
self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
|
||||||
|
self._thread.start()
|
||||||
|
return self.port
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
if self._server is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._server.shutdown()
|
||||||
|
self._server.server_close()
|
||||||
|
if self._thread is not None:
|
||||||
|
self._thread.join(timeout=1)
|
||||||
|
self._server = None
|
||||||
|
self._thread = None
|
||||||
|
self.port = None
|
||||||
16
packages/gateway/pyproject.toml
Normal file
16
packages/gateway/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet-gateway"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network HTTP gateway"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
meshnet-gateway = "meshnet_gateway.cli:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet_gateway*"]
|
||||||
3
packages/node/meshnet_node/__init__.py
Normal file
3
packages/node/meshnet_node/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet_node — Distributed Inference Network node client."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
39
packages/node/meshnet_node/cli.py
Normal file
39
packages/node/meshnet_node/cli.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
"""meshnet-node CLI entry point."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="meshnet-node",
|
||||||
|
description="Distributed Inference Network node client",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command")
|
||||||
|
|
||||||
|
start_cmd = subparsers.add_parser("start", help="Start the node server")
|
||||||
|
start_cmd.add_argument("--tracker", default="http://localhost:8080", help="Tracker URL")
|
||||||
|
start_cmd.add_argument("--port", type=int, default=7000, help="Port to listen on")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "start":
|
||||||
|
from meshnet_node.server import StubNodeServer
|
||||||
|
|
||||||
|
node = StubNodeServer(port=args.port)
|
||||||
|
port = node.start()
|
||||||
|
print(f"meshnet-node listening on port {port}")
|
||||||
|
print(f"Tracker URL: {args.tracker}")
|
||||||
|
try:
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
node.stop()
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
59
packages/node/meshnet_node/server.py
Normal file
59
packages/node/meshnet_node/server.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
"""Stub node HTTP server for the Distributed Inference Network."""
|
||||||
|
|
||||||
|
import http.server
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class _StubHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def log_message(self, fmt, *args): # noqa: suppress request logs in tests
|
||||||
|
pass
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
if self.path == "/v1/infer":
|
||||||
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
|
body = json.loads(self.rfile.read(length))
|
||||||
|
messages = body.get("messages", [])
|
||||||
|
last_content = messages[-1]["content"] if messages else ""
|
||||||
|
payload = json.dumps({"text": f"stub response to: {last_content}"}).encode()
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "application/json")
|
||||||
|
self.send_header("Content-Length", str(len(payload)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(payload)
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
|
||||||
|
class StubNodeServer:
|
||||||
|
"""Single-process stub node that returns fixed inference responses."""
|
||||||
|
|
||||||
|
def __init__(self, host: str = "127.0.0.1", port: int = 0):
|
||||||
|
self._host = host
|
||||||
|
self._requested_port = port
|
||||||
|
self._server: http.server.HTTPServer | None = None
|
||||||
|
self._thread: threading.Thread | None = None
|
||||||
|
self.port: int | None = None
|
||||||
|
|
||||||
|
def start(self) -> int:
|
||||||
|
if self._server is not None:
|
||||||
|
raise RuntimeError("StubNodeServer is already running")
|
||||||
|
|
||||||
|
self._server = http.server.HTTPServer((self._host, self._requested_port), _StubHandler)
|
||||||
|
self.port = self._server.server_address[1]
|
||||||
|
self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
|
||||||
|
self._thread.start()
|
||||||
|
return self.port
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
if self._server is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self._server.shutdown()
|
||||||
|
self._server.server_close()
|
||||||
|
if self._thread is not None:
|
||||||
|
self._thread.join(timeout=1)
|
||||||
|
self._server = None
|
||||||
|
self._thread = None
|
||||||
|
self.port = None
|
||||||
16
packages/node/pyproject.toml
Normal file
16
packages/node/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet-node"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network node client"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
meshnet-node = "meshnet_node.cli:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet_node*"]
|
||||||
3
packages/p2p/meshnet_p2p/__init__.py
Normal file
3
packages/p2p/meshnet_p2p/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet_p2p — Gossip protocol and shard swarm for the Distributed Inference Network."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
13
packages/p2p/pyproject.toml
Normal file
13
packages/p2p/pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet-p2p"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network gossip and shard swarm"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet_p2p*"]
|
||||||
3
packages/sdk/meshnet/__init__.py
Normal file
3
packages/sdk/meshnet/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet — Distributed Inference Network Python SDK."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
13
packages/sdk/pyproject.toml
Normal file
13
packages/sdk/pyproject.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network Python SDK"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet*"]
|
||||||
3
packages/tracker/meshnet_tracker/__init__.py
Normal file
3
packages/tracker/meshnet_tracker/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""meshnet_tracker — Distributed Inference Network node registry and route selection."""
|
||||||
|
|
||||||
|
__version__ = "0.1.0"
|
||||||
32
packages/tracker/meshnet_tracker/cli.py
Normal file
32
packages/tracker/meshnet_tracker/cli.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""meshnet-tracker CLI entry point."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="meshnet-tracker",
|
||||||
|
description="Distributed Inference Network node registry and route selection",
|
||||||
|
)
|
||||||
|
subparsers = parser.add_subparsers(dest="command")
|
||||||
|
|
||||||
|
start_cmd = subparsers.add_parser("start", help="Start the tracker server")
|
||||||
|
start_cmd.add_argument("--port", type=int, default=8080, help="Port to listen on")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "start":
|
||||||
|
print(f"meshnet-tracker starting on port {args.port}")
|
||||||
|
try:
|
||||||
|
import time
|
||||||
|
while True:
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
16
packages/tracker/pyproject.toml
Normal file
16
packages/tracker/pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "meshnet-tracker"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network node registry and route selection"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
meshnet-tracker = "meshnet_tracker.cli:main"
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
include = ["meshnet_tracker*"]
|
||||||
15
pyproject.toml
Normal file
15
pyproject.toml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=64"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "distributed-inference-network"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Distributed Inference Network monorepo root"
|
||||||
|
requires-python = ">=3.10"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pytest>=8"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
testpaths = ["tests"]
|
||||||
53
tests/test_smoke.py
Normal file
53
tests/test_smoke.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
"""US-001 smoke test: gateway → single stub node → valid OpenAI-format response."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
from meshnet_node.server import StubNodeServer
|
||||||
|
from meshnet_gateway.server import GatewayServer
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_node_smoke():
|
||||||
|
node = StubNodeServer()
|
||||||
|
node_port = node.start()
|
||||||
|
|
||||||
|
gateway = GatewayServer(node_url=f"http://127.0.0.1:{node_port}")
|
||||||
|
gateway_port = gateway.start()
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload = json.dumps({
|
||||||
|
"model": "stub-model",
|
||||||
|
"messages": [{"role": "user", "content": "hello"}],
|
||||||
|
}).encode()
|
||||||
|
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"http://127.0.0.1:{gateway_port}/v1/chat/completions",
|
||||||
|
data=payload,
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
method="POST",
|
||||||
|
)
|
||||||
|
|
||||||
|
with urllib.request.urlopen(req) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
assert resp.headers["Content-Type"] == "application/json"
|
||||||
|
body = json.loads(resp.read())
|
||||||
|
|
||||||
|
assert body["id"] == "chatcmpl-stub"
|
||||||
|
assert body["object"] == "chat.completion"
|
||||||
|
assert isinstance(body["created"], int)
|
||||||
|
assert body["model"] == "stub-model"
|
||||||
|
assert "choices" in body
|
||||||
|
assert len(body["choices"]) == 1
|
||||||
|
choice = body["choices"][0]
|
||||||
|
assert choice["index"] == 0
|
||||||
|
assert choice["message"]["role"] == "assistant"
|
||||||
|
assert choice["message"]["content"] == "stub response to: hello"
|
||||||
|
assert choice["finish_reason"] == "stop"
|
||||||
|
assert body["usage"] == {
|
||||||
|
"prompt_tokens": 0,
|
||||||
|
"completion_tokens": 0,
|
||||||
|
"total_tokens": 0,
|
||||||
|
}
|
||||||
|
finally:
|
||||||
|
gateway.stop()
|
||||||
|
node.stop()
|
||||||
Reference in New Issue
Block a user