KO ▾
API 키 받기

Wu Xianzhi API코드 예시

무검열 AI API 호출 예시: Python, Node.js 및 cURL 전체 코드

이것은 개발자를 위한 무검열 AI API 호출 매뉴얼입니다. 인터페이스는 OpenAI Chat Completions과 호환되므로, 익숙한 openai SDK 설정을 두 줄만 변경하면 사용할 수 있습니다. 이 문서에서는 기본 요청, 스트리밍 출력, 함수 호출, 오류 재시도, max_tokens로 비용 제어, 다중 턴 컨텍스트 유지 등 가장 자주 사용하는 기능을 한 번에 설명합니다. 모든 코드는 복사하여 바로 실행할 수 있으며, 키는 환경 변수에서 일괄적으로 읽습니다.

업데이트:

핵심 요약

  1. base_url을 https://api.wuxianzhiapi.com/v1,模型名写 uncensored로 변경하고 공식 openai SDK는 다른 수정 없이 사용 가능합니다.
  2. 스트리밍 응답의 마지막 블록은 사용량 통계이며 choices가 비어 있으므로 읽을 때 먼저 확인해야 함
  3. 429과 503에만 지수 백오프 재시도 적용, 400/401/402/403은 재시도할 의미가 없음
  4. 인터페이스는 상태가 없으므로 다중 턴 대화에서 이력을 직접 재전송해야 하며, max_tokens와 잘라내기로 비용을 제어해야 함

인터페이스 기본 정보 및 환경 변수

고정 파라미터를 먼저 확인하세요. Base URL은 https://api.wuxianzhiapi.com/v1, 모델명은 uncensored입니다. 인증은 Authorization: Bearer <키> 헤더를 사용합니다. 대화용 POST /v1/chat/completions, 모델 확인용 GET /v1/models 두 인터페이스만 제공합니다. OpenAI 형식이므로 openai SDK의 base_url과 키만 변경하면 됩니다.

키는 /get-api-key/에서 등록 즉시 표시됩니다. 이메일과 비밀번호로 가입하면 새 계정에 $0.50의 무료 체험 크레딧이 제공되며(7일 유효), 카드 등록이 필요하지 않습니다. 이 문서의 모든 예제는 환경 변수 WUXIANZHI_API_KEY에서 키를 읽습니다. 키를 코드 저장소에 넣지 말고 프론트엔드 페이지에도 넣지 마세요. 몇 가지 제한 사항을 미리 알려드립니다: 컨텍스트 창은 100,000 토큰(프롬프트와 출력 합계), 단일 요청 본문 크기는 8 MB를 초과할 수 없으며, 키당 분당 300회 요청 제한이 있습니다.

export WUXIANZHI_API_KEY="把你的密钥放这里"

# 确认连通性,应返回包含 uncensored 的模型列表
curl https://api.wuxianzhiapi.com/v1/models \
  -H "Authorization: Bearer $WUXIANZHI_API_KEY"

이 단계에서 401이 반환되면 키가 잘못 입력되었거나 환경 변수가 적용되지 않았음을 의미합니다. 먼저 이 부분을 확인한 후 코드를 살펴보세요. 전체 매개변수 설명은 인터페이스 문서를 참조하세요.

cURL: 최소 유효 요청

최종적으로 어떤 언어를 사용하든 cURL로 먼저 테스트해 보시길 권장합니다. 이렇게 하면 '네트워크, 키, 요청 형식' 관련 문제와 자체 비즈니스 코드를 분리할 수 있습니다. 아래는 max_tokens이 포함된 일반 요청입니다. 반환된 JSON에서 choices[0].message.content는 응답 본문이며, usage에는 이번 요청의 입력 및 출력 토큰 수가 포함되어 있으며, 요금은 이 두 숫자를 기준으로 계산됩니다.

curl https://api.wuxianzhiapi.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WUXIANZHI_API_KEY" \
  -d '{
    "model": "uncensored",
    "messages": [
      {"role": "system", "content": "你是一个直来直去的写作助手。"},
      {"role": "user", "content": "用三句话描述一场暴雨前的小镇。"}
    ],
    "max_tokens": 300
  }'

JSON 내의 중국어는 수동으로 이스케이프할 필요가 없으며, 요청 헤더에 UTF-8 호환 JSON을 선언하면 됩니다. 대부분의 터미널에서는 붙여넣기만 하면 바로 사용할 수 있습니다. Windows PowerShell에서 디버깅할 때 따옴표 처리가 번거로우므로 요청 본문을 body.json 파일로 저장한 후 -d @body.json로 전송하는 것을 권장합니다.

Python 및 Node.js 전체 호출

Python은 공식 openai 패키지(v1 이상)를 사용합니다. 먼저 pip install openai를 실행하세요. 클라이언트 생성 시 base_url과 api_key을 전달하면, 이후 호출 방식은 OpenAI 호출과 완전히 동일합니다. 아래 스크립트는 chat.py로 저장하여 바로 실행할 수 있습니다.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.wuxianzhiapi.com/v1",
    api_key=os.environ["WUXIANZHI_API_KEY"],
)

resp = client.chat.completions.create(
    model="uncensored",
    messages=[
        {"role": "system", "content": "你是一个直来直去的写作助手。"},
        {"role": "user", "content": "用三句话描述一场暴雨前的小镇。"},
    ],
    max_tokens=300,
)

print(resp.choices[0].message.content)
print("输入 tokens:", resp.usage.prompt_tokens, "输出 tokens:", resp.usage.completion_tokens)

Node.js는 openai npm 패키지를(v4 이상) 사용합니다. npm install openai를 실행하세요. 아래 코드는 최상위 await를 사용하므로 파일을 chat.mjs로 저장하거나, package.json에서 "type": "module"을 설정해야 합니다.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.wuxianzhiapi.com/v1",
  apiKey: process.env.WUXIANZHI_API_KEY,
});

const resp = await client.chat.completions.create({
  model: "uncensored",
  messages: [
    { role: "system", content: "你是一个直来直去的写作助手。" },
    { role: "user", content: "用三句话描述一场暴雨前的小镇。" },
  ],
  max_tokens: 300,
});

console.log(resp.choices[0].message.content);
console.log("用量:", resp.usage);

두 코드 블록의 구조는 동일하며 차이점은 구문에만 있습니다. 프로젝트에 이미 OpenAI 호출 코드가 있다면 일반적으로 클라이언트 초기화 부분만 교체하고 모델명을 uncensored로 변경하면 됩니다. 다른 인터페이스로의 전체 마이그레이션 단계는 마이그레이션 가이드를 참조하세요.

스트리밍 출력(SSE) 읽는 방법

긴 텍스트 작성이나 채팅 인터페이스 구축 시 스트리밍 출력을 반드시 사용해야 합니다. 그렇지 않으면 사용자가 전체 생성이 완료될 때까지 첫 글자를 볼 수 없이 기다려야 합니다. stream: true를 설정하면 서버는 SSE(Server-Sent Events)를 통해 블록 단위로 데이터를 푸시하며, 각 블록은 data: {...} 형식의 한 줄이며, data: [DONE]로 종료됩니다. 공식 SDK는 이미 파싱을 처리하므로 코드에서는 반복문만 작성하면 됩니다.

주의해야 할 세부 사항이 있습니다. 스트림의 마지막에는 자동으로 usage가 포함된 데이터 블록이 추가되며, 이 블록의 choices는 빈 배열입니다. 이를 활성화하기 위해 추가 파라미터를 전달할 필요는 없지만, 코드에서 chunk.choices[0]를 무작정 접근하면 스트림 종료 시 인덱스 오류가 발생할 수 있으므로 먼저 빈 배열인지 확인해야 합니다.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.wuxianzhiapi.com/v1",
    api_key=os.environ["WUXIANZHI_API_KEY"],
)

stream = client.chat.completions.create(
    model="uncensored",
    messages=[{"role": "user", "content": "写一段 200 字左右的悬疑小说开头。"}],
    max_tokens=600,
    stream=True,
)

usage = None
for chunk in stream:
    if chunk.usage:            # 最后一块:用量统计
        usage = chunk.usage
    if not chunk.choices:      # 用量块没有 choices
        continue
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

print()
if usage:
    print("输入", usage.prompt_tokens, "输出", usage.completion_tokens)
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.wuxianzhiapi.com/v1",
  apiKey: process.env.WUXIANZHI_API_KEY,
});

const stream = await client.chat.completions.create({
  model: "uncensored",
  messages: [{ role: "user", content: "写一段 200 字左右的悬疑小说开头。" }],
  max_tokens: 600,
  stream: true,
});

let usage = null;
for await (const chunk of stream) {
  if (chunk.usage) usage = chunk.usage;
  const delta = chunk.choices?.[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
}
console.log("\n用量:", usage);

원시 SSE 데이터를 확인하려면 cURL에 -N 파라미터를 추가하여 출력 버퍼링을 비활성화하세요. 이렇게 하면 각 블록이 도착할 때 즉시 출력되며, 프록시나 게이트웨이에서 스트리밍 응답이 손실되었는지 디버깅할 때 유용합니다.

curl -N https://api.wuxianzhiapi.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $WUXIANZHI_API_KEY" \
  -d '{"model":"uncensored","stream":true,"max_tokens":100,
       "messages":[{"role":"user","content":"数到五。"}]}'

Nginx와 같은 역방향 프록시 뒤에서 스트리밍 응답을 전달할 경우, 해당 경로에 대한 응답 버퍼링을 비활성화해야 합니다. 그렇지 않으면 프론트엔드에서는 한 번에 모든 데이터가 출력되는 것처럼 보일 뿐, 글자가 하나씩 나타나는 스트리밍 효과가 발생하지 않습니다.

함수 호출: tools 및 도구 결과 반환

함수 호출은 OpenAI 형식을 따릅니다. 요청에서 tools를 사용하여 함수의 이름, 설명, JSON Schema 파라미터를 선언합니다. 모델이 호출을 결정하면 응답의 message.tool_calls에 함수 이름과 JSON 문자열 형태의 파라미터가 포함됩니다. 코드에서는 실제 함수를 실행한 후 결과를 role: "tool" 메시지로 다시 전달해야 하며, 한 번 더 요청하여 모델이 결과를 바탕으로 최종 답변을 작성하도록 해야 합니다.

tool_choice의 기본값은 "auto"로 모델이 자체적으로 판단합니다. 특정 함수의 호출을 강제하려면 {"type": "function", "function": {"name": "get_weather"}}를 전달하고, "none"을 전달하면 호출이 비활성화됩니다. 아래 예제는 전체 흐름을 보여줍니다: 첫 번째 요청에서 tool_calls를 받아 로컬 함수를 실행한 후, 두 번째 요청에서 도구 결과를 전달합니다.

import json, os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.wuxianzhiapi.com/v1",
    api_key=os.environ["WUXIANZHI_API_KEY"],
)

def get_weather(city: str) -> dict:
    # 这里用假数据代替真实的天气接口
    return {"city": city, "temp_c": 18, "condition": "多云"}

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "查询指定城市的当前天气",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string", "description": "城市名"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "杭州现在天气怎么样?"}]

first = client.chat.completions.create(
    model="uncensored", messages=messages, tools=tools, tool_choice="auto", max_tokens=500
)
msg = first.choices[0].message

if msg.tool_calls:
    messages.append(msg)  # 必须把带 tool_calls 的 assistant 消息原样放回历史
    for call in msg.tool_calls:
        args = json.loads(call.function.arguments)
        result = get_weather(**args)
        messages.append({
            "role": "tool",
            "tool_call_id": call.id,
            "content": json.dumps(result, ensure_ascii=False),
        })
    final = client.chat.completions.create(
        model="uncensored", messages=messages, tools=tools, max_tokens=500
    )
    print(final.choices[0].message.content)
else:
    print(msg.content)

세 가지 일반적인 오류: 첫째, 어시스턴트의 tool_calls 메시지를 이력에 다시 추가하지 않고 tool 메시지를 직접 이어붙이면 요청 형식이 유효하지 않게 됩니다. 둘째, tool_call_id가 일치하지 않습니다. 셋째, 모델이 반환한 파라미터가 문자열이므로 반드시 json.loads로 파싱해야 하며, 파싱 실패에 대한 폴백 처리를 철저히 해야 합니다. 모델 출력을 명령어나 SQL에 직접 연결하지 마세요. Node.js의 흐름은 완전히 동일하며, 아래는 동등한 코드입니다.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.wuxianzhiapi.com/v1",
  apiKey: process.env.WUXIANZHI_API_KEY,
});

const getWeather = (city) => ({ city, temp_c: 18, condition: "多云" });

const tools = [{
  type: "function",
  function: {
    name: "get_weather",
    description: "查询指定城市的当前天气",
    parameters: {
      type: "object",
      properties: { city: { type: "string", description: "城市名" } },
      required: ["city"],
    },
  },
}];

const messages = [{ role: "user", content: "杭州现在天气怎么样?" }];

const first = await client.chat.completions.create({
  model: "uncensored", messages, tools, tool_choice: "auto", max_tokens: 500,
});
const msg = first.choices[0].message;

if (msg.tool_calls?.length) {
  messages.push(msg);
  for (const call of msg.tool_calls) {
    const args = JSON.parse(call.function.arguments);
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: JSON.stringify(getWeather(args.city)),
    });
  }
  const final = await client.chat.completions.create({
    model: "uncensored", messages, tools, max_tokens: 500,
  });
  console.log(final.choices[0].message.content);
} else {
  console.log(msg.content);
}

오류 처리 및 재시도: 429, 503 백오프 방법

오류 응답은 모두 통일된 JSON입니다: {"error":{"code":...,"message":...}}. 프로그램에서 실제로 재시도해야 하는 경우는 두 가지뿐입니다: 429(분당 300회 제한 초과)과 503(upstream_busy, 모델이 일시적으로 바쁠 때 몇 초 후 재시도). 네트워크 계층의 연결 시간 초과도 재시도할 가치가 있습니다. 나머지 오류는 재시도해도 의미가 없습니다: 400은 요청 자체에 문제가 있는 경우(예: 프롬프트에 max_tokens가 100k를 초과함), 401은 키가 유효하지 않음, 402 no_credit는 잔액이 없거나 체험이 만료됨, 403 content_blocked은 콘텐츠가 차단됨을 의미하며 재시도해도 결과가 같습니다.

백오프 전략은 지수 증가와 랜덤 점프를 사용합니다. 1차 재시도는 약 1초, 2차는 약 2초, 3차는 약 4초 대기하며, 상한과 최대 재시도 횟수를 설정하여 여러 병렬 작업이 동시에 재시도하여 속도 제한을 더 심하게 유발하는 것을 방지합니다. 공식 SDK에는 기본값으로 429 및 5xx에 대해 소량의 재시도를 수행하는 max_retries이 내장되어 있으므로, 간단한 시나리오에서는 이를 크게 설정하면 됩니다. 로깅, 서킷 브레이커 또는 사용자 정의 대기 시간이 필요하면 직접 루프를 작성하세요.

import os, random, time
import openai
from openai import OpenAI

# max_retries=0:关闭 SDK 自带重试,完全由下面的函数控制
client = OpenAI(
    base_url="https://api.wuxianzhiapi.com/v1",
    api_key=os.environ["WUXIANZHI_API_KEY"],
    max_retries=0,
    timeout=60,
)

def chat_with_retry(messages, max_attempts=5, **kwargs):
    for attempt in range(max_attempts):
        try:
            return client.chat.completions.create(
                model="uncensored", messages=messages, **kwargs
            )
        except (openai.RateLimitError, openai.InternalServerError,
                openai.APIConnectionError, openai.APITimeoutError) as e:
            if attempt == max_attempts - 1:
                raise
            wait = min(30, 2 ** attempt) + random.uniform(0, 1)
            print(f"{type(e).__name__},{wait:.1f} 秒后重试(第 {attempt + 1} 次)")
            time.sleep(wait)
        except openai.APIStatusError as e:
            # 400 / 401 / 402 / 403 / 404:重试无效,直接交给上层处理
            print("不可重试:", e.status_code, e.response.text)
            raise

resp = chat_with_retry([{"role": "user", "content": "你好"}], max_tokens=100)
print(resp.choices[0].message.content)

Node.js에서도 maxRetries을 직접 크게 설정할 수 있으며, SDK가 백오프 전략에 따라 429 및 5xx를 처리합니다. 오류를 구분해야 할 때는 error.status로 확인하면 됩니다.

import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.wuxianzhiapi.com/v1",
  apiKey: process.env.WUXIANZHI_API_KEY,
  maxRetries: 5,
  timeout: 60_000,
});

try {
  const resp = await client.chat.completions.create({
    model: "uncensored",
    messages: [{ role: "user", content: "你好" }],
    max_tokens: 100,
  });
  console.log(resp.choices[0].message.content);
} catch (err) {
  if (err instanceof OpenAI.APIError) {
    if (err.status === 402) console.error("余额不足,请充值后再试");
    else if (err.status === 403) console.error("内容被拦截:", err.message);
    else console.error("请求失败:", err.status, err.message);
  } else {
    throw err;
  }
}

또 한 가지 주의할 점은 스트리밍 요청 중 연결이 끊어지면 이미 수신한 내용을 자체적으로 보관해야 한다는 것입니다. 재시도하면 생성이 처음부터 다시 시작되며 요금이 다시 부과되므로, 긴 텍스트 생성의 경우 한 번에 긴 텍스트를 요청하기보다 분할 요청을 권장합니다.

max_tokens로 비용 및 길이 제어

요금 정책은 매우 명확합니다: 입력 $0.25 / 백만 토큰, 출력 $1.00 / 백만 토큰, 선불 크레딧 방식이며 월 구독료가 없고 잔액은 만료되지 않습니다. 출력 단가는 입력의 4배이므로 비용을 절감할 수 있는 핵심은 출력에 있습니다. max_tokens의 기본값은 2048이며, 단일 요청 최대치는 32,000입니다. 시나리오에서 짧은 답변만 필요하다면 200 또는 300으로 명확히 설정하여 모델의 장황함을 방지하고 단일 요청 비용 상한을 차단하세요.

비용을 계산해 보겠습니다. max_tokens를 1,000으로 설정했을 때 최악의 경우 출력 비용은 $0.001이며, 최대치 32,000으로 설정했을 때 최악의 경우 $0.016입니다. $0.50의 무료 체험 크레딧은 약 50만 출력 토큰 또는 200만 입력 토큰에 해당하며, 이 문서의 모든 예제를 여러 번 실행하기에 충분합니다. 프롬프트와 max_tokens의 합계가 100,000을 초과하면 400 오류가 반환되므로, 입력이 길 경우 출력 상한을 함께 낮춰야 합니다. 더 자세한 가격 설명은 가격 페이지를 참조하세요.

또 다른 세부 사항: 응답이 잘리면 응답의 finish_reason이 "length"가 됩니다. 이는 모델이 스스로 작성을 마친 것이 아니라 max_tokens가 부족했음을 의미합니다. 긴 텍스트 작성 시 이 필드를 확인하여 이어서 작성할지 여부를 결정하세요.

다중 턴 대화: 컨텍스트 직접 유지

인터페이스 자체는 상태가 없으므로 서버는 이전 요청을 기억하지 않습니다. 모델이 대화를 계속 이어나가게 하려면 매번 전체 이력 메시지를 순서대로 다시 전송해야 합니다. 먼저 system, 그 다음 user와 assistant가 교대로 메시지를 보냅니다. 이는 즉, 대화 턴이 늘어날수록 입력 토큰 수가 증가하며 비용이 누적된다는 것을 의미합니다. 대화 후기로 갈수록 각 턴의 입력 비용이 이전보다 높아집니다.

컨텍스트 총량은 100,000 토큰으로 제한됩니다(이번 출력 포함). 따라서 긴 대화는 반드시 잘라야 합니다. 가장 간단한 방법은 system 메시지와 최근 몇 턴만 유지하는 것입니다. 조금 더 복잡한 경우, 이전 내용을 한 번의 요청으로 요약하여 system 메시지에 넣을 수 있습니다. 아래 클래스는 기록 저장 및 개수 기반 잘라내기 로직을 캡슐화하여 채팅 서비스에 바로 포함할 수 있습니다.

import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.wuxianzhiapi.com/v1",
    api_key=os.environ["WUXIANZHI_API_KEY"],
)

class Chat:
    def __init__(self, system: str, keep_last: int = 20):
        self.system = {"role": "system", "content": system}
        self.history = []          # 只存 user / assistant 消息
        self.keep_last = keep_last

    def say(self, text: str, max_tokens: int = 500) -> str:
        self.history.append({"role": "user", "content": text})
        recent = self.history[-self.keep_last:]
        resp = client.chat.completions.create(
            model="uncensored",
            messages=[self.system] + recent,
            max_tokens=max_tokens,
        )
        reply = resp.choices[0].message.content
        self.history.append({"role": "assistant", "content": reply})
        return reply

bot = Chat("你是一位说话简短的旅行顾问。")
print(bot.say("我想去云南玩五天,有什么建议?"))
print(bot.say("刚才说的第二个地方,适合带老人吗?"))  # 能接上上一轮

메시지 개수 기준으로 잘라내는 것은 충분하지만 정확하지 않습니다. 메시지 길이가 크게 다르기 때문입니다. 엄격하게 관리해야 한다면 응답의 usage.prompt_tokens를 실제 사용량의 기준으로 삼으세요: 50,000에 가까워지면 역사(history)를 능동적으로 압축하세요. 대화를 장기적인 동반자형 제품으로 만들려면 사용 사례의 컨텍스트 설계 예시를 참고하세요.

자주 묻는 질문

마지막 스트리밍 데이터 블록에 내용이 없는 이유는 무엇입니까?

자동으로 추가된 사용량 통계 블록입니다. choices가 빈 배열이고 usage에 토큰 수가 포함되어 있습니다. 읽을 때 choices가 빈지 먼저 확인한 후 delta를 가져오면 됩니다. 추가 파라미터를 전달하여 활성화할 필요가 없습니다.

429와 503 모두 재시도해야 합니까? 얼마나 기다려야 합니까?

모두 재시도해야 합니다. 429는 분당 300회 제한을 초과했음을, 503의 upstream_busy는 모델이 일시적으로 바쁨을 의미합니다. 1초부터 시작해 최대 재시도 횟수를 설정하고 무한 재시도를 피하며 지수 백오프와 랜덤 조동을 권장합니다.

함수 호출 시 모델이 tool_calls를 반환하지 않으면 어떻게 합니까?

모델이 호출이 필요 없다고 판단했기 때문입니다. 이때 message.content가 최종 답변입니다. 반드시 호출해야 한다면 tool_choice를 특정 함수로 지정하고, 함수 설명과 파라미터 스키마가 명확하게 작성되었는지 확인하세요.

다중 턴 대화는 대화할수록 비싸집니까?

그렇습니다. 인터페이스는 상태가 없으므로 매번 역사(history)를 다시 전송해야 하며, 입력 토큰은 턴 수에 따라 누적됩니다. 최근 몇 턴만 유지하거나, 이전 내용을 요약하여 압축하고 max_tokens로 출력을 제한할 수 있습니다.

양식을 작성하기만 하면 키를 얻을 수 있습니다

계정을 생성하고 키를 복사한 후 Base URL을 수정하세요. 설정은 매우 간단합니다.

API 키 받기