feat(suggest): 改造 createCompleteHandler 并行化 + generateLLMSuggestions 增强
- createCompleteHandler: 记忆提取+上下文拉取 Promise.all 并行 - generateLLMSuggestions: 新增 SuggestionContext 参数,构建增强 user message - llmSuggestViaSaaS: 删除 2s 人为延迟(并行化后不再需要) - 变量重命名 context→conversationContext 避免与 SuggestionContext 冲突
This commit is contained in:
@@ -36,6 +36,7 @@ import { useMessageStore } from './messageStore';
|
|||||||
import { useArtifactStore } from './artifactStore';
|
import { useArtifactStore } from './artifactStore';
|
||||||
import { llmSuggest } from '../../lib/llm-service';
|
import { llmSuggest } from '../../lib/llm-service';
|
||||||
import { detectNameSuggestion, detectAgentNameSuggestion } from '../../lib/cold-start-mapper';
|
import { detectNameSuggestion, detectAgentNameSuggestion } from '../../lib/cold-start-mapper';
|
||||||
|
import { fetchSuggestionContext, type SuggestionContext } from '../../lib/suggestion-context';
|
||||||
|
|
||||||
const log = createLogger('StreamStore');
|
const log = createLogger('StreamStore');
|
||||||
|
|
||||||
@@ -399,17 +400,24 @@ function createCompleteHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Async memory extraction (independent — failures don't block name detection)
|
// Parallel: memory extraction + intelligence context fetch
|
||||||
const filtered = msgs
|
const filtered = msgs
|
||||||
.filter(m => m.role === 'user' || m.role === 'assistant')
|
.filter(m => m.role === 'user' || m.role === 'assistant')
|
||||||
.map(m => ({ role: m.role, content: m.content }));
|
.map(m => ({ role: m.role, content: m.content }));
|
||||||
const convId = useConversationStore.getState().currentConversationId;
|
const convId = useConversationStore.getState().currentConversationId;
|
||||||
getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined)
|
const lastUserContent = typeof lastContent === 'string' ? lastContent : '';
|
||||||
.catch(err => log.warn('Memory extraction failed:', err));
|
|
||||||
|
|
||||||
intelligenceClient.reflection.recordConversation().catch(err => {
|
const suggestionContextPromise = fetchSuggestionContext(agentId, lastUserContent);
|
||||||
log.warn('Recording conversation failed:', err);
|
|
||||||
});
|
// Fire-and-forget background tasks
|
||||||
|
Promise.all([
|
||||||
|
getMemoryExtractor().extractFromConversation(filtered, agentId, convId ?? undefined)
|
||||||
|
.catch(err => log.warn('Memory extraction failed:', err)),
|
||||||
|
intelligenceClient.reflection.recordConversation()
|
||||||
|
.catch(err => log.warn('Recording conversation failed:', err)),
|
||||||
|
suggestionContextPromise,
|
||||||
|
]).then(([, , context]) => {
|
||||||
|
// Conditional reflection (after context is ready)
|
||||||
intelligenceClient.reflection.shouldReflect().then(shouldReflect => {
|
intelligenceClient.reflection.shouldReflect().then(shouldReflect => {
|
||||||
if (shouldReflect) {
|
if (shouldReflect) {
|
||||||
intelligenceClient.reflection.reflect(agentId, []).catch(err => {
|
intelligenceClient.reflection.reflect(agentId, []).catch(err => {
|
||||||
@@ -418,17 +426,18 @@ function createCompleteHandler(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Follow-up suggestions (async LLM call with keyword fallback)
|
// Follow-up suggestions with enriched context
|
||||||
const latestMsgs = chat.getMessages() || [];
|
const latestMsgs = chat.getMessages() || [];
|
||||||
const conversationMessages = latestMsgs
|
const conversationMessages = latestMsgs
|
||||||
.filter(m => m.role === 'user' || m.role === 'assistant')
|
.filter(m => m.role === 'user' || m.role === 'assistant')
|
||||||
.filter(m => !m.streaming)
|
.filter(m => !m.streaming)
|
||||||
.map(m => ({ role: m.role, content: m.content }));
|
.map(m => ({ role: m.role, content: m.content }));
|
||||||
|
|
||||||
generateLLMSuggestions(conversationMessages, set).catch(err => {
|
generateLLMSuggestions(conversationMessages, set, context).catch(err => {
|
||||||
log.warn('Suggestion generation error:', err);
|
log.warn('Suggestion generation error:', err);
|
||||||
set({ suggestionsLoading: false });
|
set({ suggestionsLoading: false });
|
||||||
});
|
});
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,15 +568,32 @@ function parseSuggestionResponse(raw: string): string[] {
|
|||||||
async function generateLLMSuggestions(
|
async function generateLLMSuggestions(
|
||||||
messages: Array<{ role: string; content: string }>,
|
messages: Array<{ role: string; content: string }>,
|
||||||
set: (partial: Partial<StreamState>) => void,
|
set: (partial: Partial<StreamState>) => void,
|
||||||
|
context?: SuggestionContext,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
set({ suggestionsLoading: true });
|
set({ suggestionsLoading: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const recentMessages = messages.slice(-6);
|
const recentMessages = messages.slice(-6);
|
||||||
const context = recentMessages
|
const conversationContext = recentMessages
|
||||||
.map(m => `${m.role === 'user' ? '用户' : '助手'}: ${m.content}`)
|
.map(m => `${m.role === 'user' ? '用户' : '助手'}: ${m.content}`)
|
||||||
.join('\n\n');
|
.join('\n\n');
|
||||||
|
|
||||||
|
// Build dynamic user message with intelligence context
|
||||||
|
const ctx = context ?? { userProfile: '', painPoints: '', experiences: '', skillMatch: '' };
|
||||||
|
const hasContext = ctx.userProfile || ctx.painPoints || ctx.experiences || ctx.skillMatch;
|
||||||
|
let userMessage: string;
|
||||||
|
if (hasContext) {
|
||||||
|
const sections: string[] = ['以下是用户的背景信息,请在生成建议时参考:\n'];
|
||||||
|
if (ctx.userProfile) sections.push(`## 用户画像\n${ctx.userProfile}`);
|
||||||
|
if (ctx.painPoints) sections.push(`## 活跃痛点\n${ctx.painPoints}`);
|
||||||
|
if (ctx.experiences) sections.push(`## 相关经验\n${ctx.experiences}`);
|
||||||
|
if (ctx.skillMatch) sections.push(`## 可用技能\n${ctx.skillMatch}`);
|
||||||
|
sections.push(`\n最近对话:\n${conversationContext}`);
|
||||||
|
userMessage = sections.join('\n\n');
|
||||||
|
} else {
|
||||||
|
userMessage = `以下是对话中最近的消息:\n\n${conversationContext}\n\n请生成 3 个后续问题。`;
|
||||||
|
}
|
||||||
|
|
||||||
const connectionMode = typeof localStorage !== 'undefined'
|
const connectionMode = typeof localStorage !== 'undefined'
|
||||||
? localStorage.getItem('zclaw-connection-mode')
|
? localStorage.getItem('zclaw-connection-mode')
|
||||||
: null;
|
: null;
|
||||||
@@ -575,9 +601,9 @@ async function generateLLMSuggestions(
|
|||||||
let raw: string;
|
let raw: string;
|
||||||
|
|
||||||
if (connectionMode === 'saas') {
|
if (connectionMode === 'saas') {
|
||||||
raw = await llmSuggestViaSaaS(context);
|
raw = await llmSuggestViaSaaS(userMessage);
|
||||||
} else {
|
} else {
|
||||||
raw = await llmSuggest(context);
|
raw = await llmSuggest(userMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestions = parseSuggestionResponse(raw);
|
const suggestions = parseSuggestionResponse(raw);
|
||||||
@@ -601,7 +627,7 @@ async function generateLLMSuggestions(
|
|||||||
* with non-streaming requests. Collects the full response from SSE deltas,
|
* with non-streaming requests. Collects the full response from SSE deltas,
|
||||||
* then parses the suggestion JSON from the accumulated text.
|
* then parses the suggestion JSON from the accumulated text.
|
||||||
*/
|
*/
|
||||||
async function llmSuggestViaSaaS(context: string): Promise<string> {
|
async function llmSuggestViaSaaS(userMessage: string): Promise<string> {
|
||||||
const { saasClient } = await import('../../lib/saas-client');
|
const { saasClient } = await import('../../lib/saas-client');
|
||||||
const { useConversationStore } = await import('./conversationStore');
|
const { useConversationStore } = await import('./conversationStore');
|
||||||
const { useSaaSStore } = await import('../saasStore');
|
const { useSaaSStore } = await import('../saasStore');
|
||||||
@@ -611,9 +637,6 @@ async function llmSuggestViaSaaS(context: string): Promise<string> {
|
|||||||
const model = currentModel || (availableModels.length > 0 ? availableModels[0]?.id : undefined);
|
const model = currentModel || (availableModels.length > 0 ? availableModels[0]?.id : undefined);
|
||||||
if (!model) throw new Error('No model available for suggestions');
|
if (!model) throw new Error('No model available for suggestions');
|
||||||
|
|
||||||
// Delay to avoid concurrent relay requests with memory extraction
|
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
|
||||||
|
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 60000);
|
const timeoutId = setTimeout(() => controller.abort(), 60000);
|
||||||
|
|
||||||
@@ -623,7 +646,7 @@ async function llmSuggestViaSaaS(context: string): Promise<string> {
|
|||||||
model,
|
model,
|
||||||
messages: [
|
messages: [
|
||||||
{ role: 'system', content: LLM_PROMPTS_SYSTEM },
|
{ role: 'system', content: LLM_PROMPTS_SYSTEM },
|
||||||
{ role: 'user', content: `以下是对话中最近的消息:\n\n${context}\n\n请生成 3 个后续问题。` },
|
{ role: 'user', content: userMessage },
|
||||||
],
|
],
|
||||||
max_tokens: 500,
|
max_tokens: 500,
|
||||||
temperature: 0.7,
|
temperature: 0.7,
|
||||||
|
|||||||
Reference in New Issue
Block a user