forked from wangziqi/gongxue-base
feat: 集成 AI 对话与只读查询工具
This commit is contained in:
68
apps/server/src/ai-chat/ai-model-stream.service.spec.ts
Normal file
68
apps/server/src/ai-chat/ai-model-stream.service.spec.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { AiModelStreamService } from './ai-model-stream.service';
|
||||
import type { AiRuntimeConfig } from '../ai-config/dto/ai-config.dto';
|
||||
|
||||
const config: AiRuntimeConfig = {
|
||||
provider: 'DEEPSEEK' as AiRuntimeConfig['provider'],
|
||||
baseUrl: 'https://example.test/v1',
|
||||
apiKey: 'secret',
|
||||
defaultModel: 'deepseek-reasoner',
|
||||
timeoutMs: 1000,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
describe('AiModelStreamService', () => {
|
||||
afterEach(() => jest.restoreAllMocks());
|
||||
|
||||
it('分离思考、正文并拼接分片工具调用,且处理无尾随空行的最后事件', async () => {
|
||||
const chunks = [
|
||||
'data: {"choices":[{"delta":{"reasoning_content":"思考"}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"content":"答案","tool_calls":[{"index":0,"id":"call_","function":{"name":"search_","arguments":"{\\"q\\":"}}]}}]}\n\n',
|
||||
'data: {"choices":[{"delta":{"tool_calls":[{"index":0,"id":"1","function":{"name":"students","arguments":"\\"张三\\"}"}}]}}]}',
|
||||
];
|
||||
async function* body() {
|
||||
for (const chunk of chunks) yield Buffer.from(chunk);
|
||||
}
|
||||
const service = new AiModelStreamService();
|
||||
jest.spyOn(service as never, 'pinnedPost' as never).mockResolvedValue({
|
||||
status: 200,
|
||||
contentType: 'text/event-stream',
|
||||
body: body(),
|
||||
} as never);
|
||||
|
||||
const events = [];
|
||||
for await (const event of service.stream(
|
||||
config,
|
||||
[{ role: 'user', content: '查询' }],
|
||||
[],
|
||||
new AbortController().signal,
|
||||
)) events.push(event);
|
||||
|
||||
expect(events).toEqual([
|
||||
{ type: 'reasoning', delta: '思考' },
|
||||
{ type: 'content', delta: '答案' },
|
||||
{
|
||||
type: 'complete',
|
||||
toolCalls: [{ id: 'call_1', name: 'search_students', arguments: '{"q":"张三"}' }],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('不向调用方暴露上游非 JSON 错误正文', async () => {
|
||||
async function* body() { yield Buffer.from('proxy internal detail'); }
|
||||
const service = new AiModelStreamService();
|
||||
jest.spyOn(service as never, 'pinnedPost' as never).mockResolvedValue({
|
||||
status: 502,
|
||||
contentType: 'text/plain',
|
||||
body: body(),
|
||||
} as never);
|
||||
const consume = async () => {
|
||||
for await (const _ of service.stream(
|
||||
config,
|
||||
[{ role: 'user', content: '查询' }],
|
||||
[],
|
||||
new AbortController().signal,
|
||||
)) void _;
|
||||
};
|
||||
await expect(consume()).rejects.toThrow('AI 服务暂时不可用');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user