Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/agentlab/src/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ export const AGENTLAB_PROVIDER_CATALOG: AgentLabProviderCatalogEntry[] = [
models: [
{ id: 'deepseek-chat', label: 'deepseek-chat', capabilities: ['chat'] },
{ id: 'deepseek-reasoner', label: 'deepseek-reasoner', capabilities: ['chat'] },
{ id: 'deepseek-v4-flash', label: 'deepseek-v4-flash', capabilities: ['chat'] },
{ id: 'deepseek-v4-pro', label: 'deepseek-v4-pro', capabilities: ['chat'] },
],
},
{
Expand Down
10 changes: 5 additions & 5 deletions packages/agentlab/src/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
AgentLabExpression,
AgentLabFewShotPair,
} from './types';
import { reportUsage } from './http';
import { reportUsage, pickMessageText } from './http';

function coerceString(value: unknown): string {
if (typeof value === 'string') return value.trim();
Expand Down Expand Up @@ -63,9 +63,9 @@ async function chatCompletion(
if (!res.ok) {
throw new Error(`AgentLab 提炼接口调用失败: HTTP ${res.status}`);
}
const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
const data = (await res.json()) as { choices?: Array<{ message?: { content?: string; reasoning_content?: string } }> };
reportUsage(endpoint, data);
return data.choices?.[0]?.message?.content ?? '';
return pickMessageText(data.choices?.[0]?.message);
}

/**
Expand Down Expand Up @@ -103,9 +103,9 @@ async function visionCompletion(
if (!res.ok) {
throw new Error(`AgentLab 视觉接口调用失败: HTTP ${res.status}`);
}
const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
const data = (await res.json()) as { choices?: Array<{ message?: { content?: string; reasoning_content?: string } }> };
reportUsage(endpoint, data);
return data.choices?.[0]?.message?.content ?? '';
return pickMessageText(data.choices?.[0]?.message);
}

/** generateText + 宽松解析 + 失败重试一次。 */
Expand Down
21 changes: 16 additions & 5 deletions packages/agentlab/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,20 +345,31 @@ async function postJson<T>(
return (await res.json()) as T;
}

/** OpenAI 兼容消息里同时可能存在 content 和 reasoning_content(推理模型专用),取首个非空。 */
export function pickMessageText(msg: { content?: unknown; reasoning_content?: unknown } | undefined): string {
if (!msg) return '';
const c = typeof msg.content === 'string' ? msg.content : '';
const r = typeof msg.reasoning_content === 'string' ? msg.reasoning_content : '';
return (c || r).trim();
}

/**
* 设置页「测试连通性」:用最小的 chat 请求探活,返回 ok + 详细错误(含 HTTP 状态码与响应体)。
* 不抛错——把失败包装成 { ok:false, error },让前端直接展示「模型不对 / key 不对」。
* 推理模型(如 deepseek-v4 系列)会把思考过程放在 reasoning_content 中,
* 当 max_tokens 较小时 content 可能为空——因此同时检查两个字段。
*/
export async function testChatEndpoint(
endpoint: AgentLabEndpoint,
): Promise<{ ok: boolean; error?: string; reply?: string }> {
try {
const data = await postJson<{ choices?: Array<{ message?: { content?: string } }> }>(
const data = await postJson<{ choices?: Array<{ message?: { content?: string; reasoning_content?: string } }> }>(
endpoint,
'/chat/completions',
{ model: endpoint.model, messages: [{ role: 'user', content: '你好' }], max_tokens: 16 },
// 推理模型需要更多 token 预算才能产出 content;64 足够短路"你好"又不浪费。
{ model: endpoint.model, messages: [{ role: 'user', content: '你好' }], max_tokens: 64 },
);
const reply = data.choices?.[0]?.message?.content?.trim();
const reply = pickMessageText(data.choices?.[0]?.message);
if (!reply) return { ok: false, error: '接口可达,但返回内容为空(模型可能不支持 chat/completions)。' };
return { ok: true, reply };
} catch (e) {
Expand Down Expand Up @@ -564,7 +575,7 @@ export async function runPersonaChat(
];

const data = await postJson<{
choices?: Array<{ message?: { content?: string } }>;
choices?: Array<{ message?: { content?: string; reasoning_content?: string } }>;
usage?: OpenAiUsage;
}>(chat, '/chat/completions', {
model: chat.model,
Expand All @@ -573,7 +584,7 @@ export async function runPersonaChat(
messages,
});
reportUsage(chat, data);
const raw = data.choices?.[0]?.message?.content?.trim();
const raw = pickMessageText(data.choices?.[0]?.message);
if (!raw) {
throw new Error('AgentLab chat 返回为空');
}
Expand Down
2 changes: 1 addition & 1 deletion packages/agentlab/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export {
modelsWithCapability,
resolveEndpoint,
} from './provider';
export { embedTexts, runPersonaChat, reportUsage, keywordsOf, testChatEndpoint } from './http';
export { embedTexts, runPersonaChat, reportUsage, keywordsOf, testChatEndpoint, pickMessageText } from './http';
export { selectStickerByEmotion } from './sticker';
export { humanizeText, DEFAULT_TYPO_INTENSITY } from './typo';
export { scoreReplyWillingness } from './willing';
Expand Down
8 changes: 4 additions & 4 deletions packages/service/src/account/assistant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { basename, dirname, extname, join, resolve, sep } from 'node:path';
import { reportUsage, type AgentLabEndpoint, type AgentLabModelRef, type AgentLabUsage } from '@weq/agentlab';
import { reportUsage, pickMessageText, type AgentLabEndpoint, type AgentLabModelRef, type AgentLabUsage } from '@weq/agentlab';
import type { TokenUsageStore } from './agentlab_usage';
import type { ConversationStore, ConversationTurn } from './agentlab_conversation';

Expand Down Expand Up @@ -384,8 +384,8 @@ export class AssistantService {
],
[],
);
const raw = data.choices?.[0]?.message?.content;
return cleanTitle(typeof raw === 'string' ? raw : '');
const raw = pickMessageText(data.choices?.[0]?.message);
return cleanTitle(raw);
}

/** 核心多轮循环:返回最终文本。中途只 emit 过程 step,不 emit final。 */
Expand Down Expand Up @@ -424,7 +424,7 @@ export class AssistantService {
const msg = data.choices?.[0]?.message;
if (!msg) throw new Error('助手返回为空');

const content = typeof msg.content === 'string' ? msg.content.trim() : '';
const content = pickMessageText(msg);

if (allowTools && msg.tool_calls?.length) {
// 模型在调用工具前给出的思路 → 作为"思考"展示。
Expand Down
Loading