AI 工具

用 Chatsonic API 构建带长度校验的客户端

Chatsonic 接口形如 POST /v2/business/content/chatsonic?engine=premium,正文 inputtext 长度 1–600。这篇客户端在发请求前先做长度守卫,并用 mock 演示。

BINBHO · 图文教程

Chatsonic 接口形如 POST /v2/business/content/chatsonic?engine=premium,正文 input_text 长度 1–600。这篇客户端在发请求前先做长度守卫,并用 mock 演示。

先看清成品

  • 长度守卫拒绝空串与 601 字符。
  • mock 返回 Echo。
  • 官方无效密钥 HTTP 401。
  • 2 项 pytest 通过。

合规提醒:Writesonic API 密钥在控制台激活后仅显示一次副本,勿提交公开仓库。SEO/AI 可见度类功能变化较快,以官网与文档为准;本文不编造价格。生成内容需避免抄袭与虚假宣传,并遵守目标市场广告法。

运行说明:无正式密钥。A/C 用 mock Chatsonic;对官方主机无效密钥实测 HTTP 401。B 为本地 SEO brief 校验。2026-10-23(Asia/Shanghai)pytest 通过。

做完之后你会得到什么?

成果 说明
chatsonic_client.py 客户端
mock_writesonic.py 假服务
test_99a.py 测试

需要准备哪些环境?

项目 说明
Python 3.10+
环境变量 WRITESONIC_API_KEY

关键事实

事实 来源
请求头 X-API-KEY;query engine=premium 官方 Chatsonic 参考
input_text 长度 1–600;可开关 google results 与 memory 官方
密钥在 API Dashboard 激活与查看 官方 Finding your API key

文件结构

99-writesonic-A-src/
├── chatsonic_client.py
├── mock_writesonic.py
└── test_99a.py

第 1 步:客户端

#!/usr/bin/env python3
"""Writesonic Chatsonic client with input_text length guard (1..600)."""
from __future__ import annotations

import argparse
import json
import os
import sys
import urllib.error
import urllib.request


def chat(
    *,
    base: str,
    api_key: str,
    input_text: str,
    enable_google_results: bool = True,
    enable_memory: bool = False,
    history_data: list | None = None,
    engine: str = "premium",
) -> dict:
    if not (1 <= len(input_text) <= 600):
        raise ValueError("input_text length must be between 1 and 600")
    payload = {
        "input_text": input_text,
        "enable_google_results": enable_google_results,
        "enable_memory": enable_memory,
        "history_data": history_data or [],
    }
    url = f"{base.rstrip('/')}/v2/business/content/chatsonic?engine={engine}"
    req = urllib.request.Request(
        url,
        data=json.dumps(payload).encode(),
        headers={
            "X-API-KEY": api_key,
            "Content-Type": "application/json",
            "Accept": "application/json",
        },
        method="POST",
    )
    try:
        with urllib.request.urlopen(req, timeout=45) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        raise SystemExit(f"HTTP {e.code}: {e.read().decode(errors='replace')}") from e


def main(argv=None) -> int:
    ap = argparse.ArgumentParser(description=__doc__)
    ap.add_argument("text", nargs="?", default="What is Writesonic in one sentence?")
    ap.add_argument("--base", default=os.environ.get("WRITESONIC_BASE_URL", "https://api.writesonic.com"))
    ap.add_argument("--key", default=os.environ.get("WRITESONIC_API_KEY", ""))
    ap.add_argument("--no-google", action="store_true")
    args = ap.parse_args(argv)
    if not args.key:
        print("设置 WRITESONIC_API_KEY", file=sys.stderr)
        return 2
    try:
        r = chat(
            base=args.base,
            api_key=args.key,
            input_text=args.text,
            enable_google_results=not args.no_google,
        )
    except ValueError as e:
        print(f"INVALID: {e}", file=sys.stderr)
        return 2
    print(r.get("message") or r)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

第 2 步:Mock

#!/usr/bin/env python3
"""Mock Writesonic Chatsonic endpoint."""
from __future__ import annotations

import json
from http.server import BaseHTTPRequestHandler, HTTPServer
from urllib.parse import urlparse, parse_qs

API_KEY = "ws-test-key"


class H(BaseHTTPRequestHandler):
    def log_message(self, *a):
        pass

    def do_POST(self):
        parsed = urlparse(self.path)
        if parsed.path != "/v2/business/content/chatsonic":
            self.send_error(404)
            return
        if self.headers.get("X-API-KEY") != API_KEY:
            body = b'{"detail":"Unauthorized"}'
            self.send_response(401)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        n = int(self.headers.get("Content-Length", 0))
        data = json.loads(self.rfile.read(n) or b"{}")
        text = data.get("input_text") or ""
        if not isinstance(text, str) or not (1 <= len(text) <= 600):
            body = json.dumps({"detail": "input_text length must be 1..600"}).encode()
            self.send_response(422)
            self.send_header("Content-Type", "application/json")
            self.send_header("Content-Length", str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        qs = parse_qs(parsed.query)
        engine = (qs.get("engine") or ["premium"])[0]
        google = data.get("enable_google_results", True)
        mem = data.get("enable_memory", False)
        hist = data.get("history_data") or []
        reply = f"[engine={engine} google={google} mem={mem} hist={len(hist)}] Echo: {text[:120]}"
        out = json.dumps({"message": reply}).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(out)))
        self.end_headers()
        self.wfile.write(out)


def main():
    import argparse

    ap = argparse.ArgumentParser()
    ap.add_argument("--port", type=int, default=8990)
    args = ap.parse_args()
    HTTPServer(("127.0.0.1", args.port), H).serve_forever()


if __name__ == "__main__":
    main()

第 3 步:测试

import subprocess
import sys
import time
from pathlib import Path
from chatsonic_client import chat

HERE = Path(__file__).resolve().parent


def test_length_guard():
    try:
        chat(base="http://x", api_key="k", input_text="")
        assert False
    except ValueError:
        pass
    try:
        chat(base="http://x", api_key="k", input_text="x" * 601)
        assert False
    except ValueError:
        pass


def test_mock():
    port = 18990
    proc = subprocess.Popen(
        [sys.executable, str(HERE / "mock_writesonic.py"), "--port", str(port)],
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,
    )
    try:
        time.sleep(0.3)
        r = subprocess.run(
            [
                sys.executable,
                str(HERE / "chatsonic_client.py"),
                "Hello Writesonic",
                "--base",
                f"http://127.0.0.1:{port}",
                "--key",
                "ws-test-key",
                "--no-google",
            ],
            capture_output=True,
            text=True,
            check=True,
        )
        assert "Echo: Hello Writesonic" in r.stdout
    finally:
        proc.terminate()
        proc.wait(timeout=5)

第 4 步:运行

..                                                                       [100%]
2 passed in 0.38s
[engine=premium google=False mem=False hist=0] Echo: What is SEO?
HTTP 401
{"detail":"Invalid or inactive API key. Please check your API key and try again."}

怎么检查结果?

调试示例

600 字符不够写长文。 Chatsonic 接口适合短问答;长文请用产品里的文章工作流或其他端点(以当前文档为准),或自行分段。

常见问题

现象 原因 处理
401 密钥未激活 按 Finding API key 打开开关
422 长度/字段 检查 input_text
要实时搜索 enable_google_results 注意引用来源审核

模板规则

  • 密钥保密。
  • 短接口不硬塞长文。
  • 输出人工审。

项目资源

用途 地址
Chatsonic API 说明(官方) https://docs.writesonic.com/docs/chatsonic-api
Chatsonic 参考(官方) https://docs.writesonic.com/reference/chatsonic_v2businesscontentchatsonic_post-1
Finding API key(官方) https://docs.writesonic.com/reference/finding-your-api-key

先守住 1..600,再谈记忆与搜索开关。