llama_cpp_openai_probe.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env python3
  2. import argparse
  3. import io
  4. import json
  5. import math
  6. import struct
  7. import urllib.error
  8. import urllib.request
  9. from datetime import datetime
  10. from pathlib import Path
  11. CHAT_TESTS = [
  12. ("baseline", {}),
  13. ("enable_thinking_false", {"enable_thinking": False}),
  14. ("reasoning_off", {"reasoning": "off"}),
  15. ]
  16. def post_json(url: str, payload: dict, timeout: int) -> tuple[int, str]:
  17. body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
  18. req = urllib.request.Request(
  19. url,
  20. data=body,
  21. headers={"Content-Type": "application/json"},
  22. method="POST",
  23. )
  24. try:
  25. with urllib.request.urlopen(req, timeout=timeout) as resp:
  26. return resp.status, resp.read().decode("utf-8", errors="replace")
  27. except urllib.error.HTTPError as exc:
  28. return exc.code, exc.read().decode("utf-8", errors="replace")
  29. def post_multipart(url: str, fields: dict[str, str], file_name: str, file_bytes: bytes, timeout: int) -> tuple[int, str]:
  30. boundary = "----CodexBoundary20260609"
  31. body = io.BytesIO()
  32. for key, value in fields.items():
  33. body.write(f"--{boundary}\r\n".encode())
  34. body.write(f'Content-Disposition: form-data; name="{key}"\r\n\r\n'.encode())
  35. body.write(f"{value}\r\n".encode())
  36. body.write(f"--{boundary}\r\n".encode())
  37. body.write(
  38. f'Content-Disposition: form-data; name="file"; filename="{file_name}"\r\n'.encode()
  39. )
  40. body.write(b"Content-Type: audio/wav\r\n\r\n")
  41. body.write(file_bytes)
  42. body.write(b"\r\n")
  43. body.write(f"--{boundary}--\r\n".encode())
  44. req = urllib.request.Request(
  45. url,
  46. data=body.getvalue(),
  47. headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
  48. method="POST",
  49. )
  50. try:
  51. with urllib.request.urlopen(req, timeout=timeout) as resp:
  52. return resp.status, resp.read().decode("utf-8", errors="replace")
  53. except urllib.error.HTTPError as exc:
  54. return exc.code, exc.read().decode("utf-8", errors="replace")
  55. def generate_silence_wav(sample_rate: int = 16000, duration_seconds: float = 1.0) -> bytes:
  56. total_samples = int(sample_rate * duration_seconds)
  57. pcm = b"".join(struct.pack("<h", 0) for _ in range(total_samples))
  58. byte_rate = sample_rate * 2
  59. block_align = 2
  60. bits_per_sample = 16
  61. data_size = len(pcm)
  62. riff_size = 36 + data_size
  63. header = struct.pack(
  64. "<4sI4s4sIHHIIHH4sI",
  65. b"RIFF",
  66. riff_size,
  67. b"WAVE",
  68. b"fmt ",
  69. 16,
  70. 1,
  71. 1,
  72. sample_rate,
  73. byte_rate,
  74. block_align,
  75. bits_per_sample,
  76. b"data",
  77. data_size,
  78. )
  79. return header + pcm
  80. def main() -> int:
  81. parser = argparse.ArgumentParser()
  82. parser.add_argument("--port", type=int, default=18003)
  83. parser.add_argument("--model", default="Qwen3-ASR-1.7B-Q8_0.gguf")
  84. parser.add_argument("--timeout", type=int, default=300)
  85. parser.add_argument("--output-root", default="artifacts/llama-cpp-openai-probe")
  86. args = parser.parse_args()
  87. run_dir = Path(args.output_root) / datetime.now().strftime("%Y%m%d-%H%M%S")
  88. run_dir.mkdir(parents=True, exist_ok=True)
  89. chat_url = f"http://127.0.0.1:{args.port}/v1/chat/completions"
  90. audio_url = f"http://127.0.0.1:{args.port}/v1/audio/transcriptions"
  91. chat_results = []
  92. for index, (name, extra) in enumerate(CHAT_TESTS, start=1):
  93. payload = {
  94. "model": args.model,
  95. "messages": [{"role": "user", "content": "Hello"}],
  96. "temperature": 0.2,
  97. "max_tokens": 64,
  98. "stream": False,
  99. }
  100. payload.update(extra)
  101. status, raw = post_json(chat_url, payload, timeout=args.timeout)
  102. (run_dir / f"chat_{index:02d}_{name}.request.json").write_text(
  103. json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
  104. encoding="utf-8",
  105. )
  106. (run_dir / f"chat_{index:02d}_{name}.response.raw.json").write_text(raw, encoding="utf-8")
  107. chat_results.append({"name": name, "status": status, "raw": raw})
  108. wav_bytes = generate_silence_wav()
  109. status, raw = post_multipart(
  110. audio_url,
  111. fields={"model": args.model},
  112. file_name="silence.wav",
  113. file_bytes=wav_bytes,
  114. timeout=args.timeout,
  115. )
  116. (run_dir / "audio_silence.response.raw.json").write_text(raw, encoding="utf-8")
  117. (run_dir / "audio_silence.summary.json").write_text(
  118. json.dumps({"status": status, "raw": raw}, ensure_ascii=False, indent=2) + "\n",
  119. encoding="utf-8",
  120. )
  121. (run_dir / "all_summaries.json").write_text(
  122. json.dumps(
  123. {
  124. "chat_results": chat_results,
  125. "audio_result": {"status": status, "raw": raw},
  126. },
  127. ensure_ascii=False,
  128. indent=2,
  129. )
  130. + "\n",
  131. encoding="utf-8",
  132. )
  133. print(run_dir)
  134. return 0
  135. if __name__ == "__main__":
  136. raise SystemExit(main())