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."}
怎么检查结果?
- 超长输入在本地被拒,不浪费配额。
- 正式 base 默认 https://api.writesonic.com 。
调试示例
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,再谈记忆与搜索开关。