💻 源码
import type { ChatMessage, PlainRunResult, ReActStep, ToolDefinition } from '../types'

const BASE_URL = '/api/dashscope/compatible-mode/v1'
const DEFAULT_MODEL = 'qwen-plus'
const MAX_LOOPS = 6

interface CompletionResponse {
  choices: Array<{
    message: {
      role: string
      content: string | null
      reasoning_content?: string | null
      tool_calls?: Array<{
        id: string
        type: 'function'
        function: { name: string; arguments: string }
      }>
    }
    finish_reason: string
  }>
  error?: { message: string }
}

export class DashScopeError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'DashScopeError'
  }
}

async function chatCompletion(
  apiKey: string,
  messages: ChatMessage[],
  tools?: ToolDefinition[],
): Promise<CompletionResponse['choices'][0]['message']> {
  const body: Record<string, unknown> = {
    model: DEFAULT_MODEL,
    messages,
  }
  if (tools?.length) {
    body.tools = tools
    body.tool_choice = 'auto'
  }

  const res = await fetch(`${BASE_URL}/chat/completions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  })

  const data = (await res.json()) as CompletionResponse
  if (!res.ok) {
    throw new DashScopeError(data.error?.message ?? `请求失败 (${res.status})`)
  }
  const choice = data.choices?.[0]
  if (!choice?.message) {
    throw new DashScopeError('模型返回为空')
  }
  return choice.message
}

function uid(): string {
  return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
}

export async function plainChat(
  apiKey: string,
  userQuestion: string,
  onStep?: (step: ReActStep) => void,
): Promise<PlainRunResult> {
  const steps: ReActStep[] = []
  const push = (step: ReActStep) => {
    steps.push(step)
    onStep?.(step)
  }

  push({
    id: uid(),
    type: 'input',
    round: 0,
    title: '用户提问',
    content: userQuestion,
  })

  push({
    id: uid(),
    type: 'plain',
    round: 1,
    title: '无工具 · 直接作答',
    content: [
      '【普通模式流程】',
      '❌ 无 Thought(思考)',
      '❌ 无 Action(工具调用)',
      '❌ 无 Observation(真实数据)',
      '→ 模型仅凭训练知识一次性生成回答',
      '→ 涉及实时数据时,容易出现幻觉或计算错误',
    ].join('\n'),
  })

  const messages: ChatMessage[] = [
    {
      role: 'system',
      content:
        '你是一个有帮助的助手。请用中文简洁回答用户问题。注意:你没有接入任何外部工具,只能依靠自身知识回答,不要说明你无法调用工具。',
    },
    { role: 'user', content: userQuestion },
  ]

  const reply = await chatCompletion(apiKey, messages)
  const finalAnswer = reply.content?.trim() || '(模型未返回内容)'

  push({
    id: uid(),
    type: 'answer',
    round: 1,
    title: '直接输出(未经验证)',
    content: finalAnswer,
  })

  return { steps, finalAnswer }
}

export { chatCompletion, MAX_LOOPS }