Conceptually split the system Instruction with the rest of the history

This commit is contained in:
Enrico Ros
2024-12-12 17:59:37 -08:00
parent 6ffcb731a3
commit 36eda51789
8 changed files with 113 additions and 68 deletions
+9 -8
View File
@@ -15,7 +15,7 @@ import { useChatLLMDropdown } from '../chat/components/layout-bar/useLLMDropdown
import { SystemPurposeId, SystemPurposes } from '../../data';
import { elevenLabsSpeakText } from '~/modules/elevenlabs/elevenlabs.client';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromHistory } from '~/modules/aix/client/aix.client';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromConversation } from '~/modules/aix/client/aix.client';
import { useElevenLabsVoiceDropdown } from '~/modules/elevenlabs/useElevenLabsVoiceDropdown';
import type { OptimaBarControlMethods } from '~/common/layout/optima/bar/OptimaBarDropdown';
@@ -229,13 +229,13 @@ export function Telephone(props: {
// Call Message Generation Prompt
const callSystemInstruction = createDMessageTextContent('system', 'You are having a phone call. Your response style is brief and to the point, and according to your personality, defined below.');
const callGenerationInputHistory: DMessage[] = [
// Call system prompt
createDMessageTextContent('system', 'You are having a phone call. Your response style is brief and to the point, and according to your personality, defined below.'),
// Chat messages, including the system prompt
// Chat messages, including the system prompt which is casted to a user message
// TODO: when upgrading to dynamic personas, we need to inject the persona message instead - not rely on reMessages, as messages[0] !== 'system'
...((reMessages && reMessages?.length > 0)
? reMessages.map(_m => _m.role === 'system' ? { ..._m, role: 'user' as const } : _m) // cast system chat messages to the user role
: [createDMessageTextContent('user', personaSystemMessage)]
? reMessages.map(_m => _m.role === 'system' ? { ..._m, role: 'user' as const } : _m) // (MUST: [0] is the system message of the original chat) cast system chat messages to the user role
: [createDMessageTextContent('user', personaSystemMessage)] // see TO-DO ^
),
// Call system prompt 2, to indicate the call has started
createDMessageTextContent('user', '**You are now on the phone call related to the chat above**.\nRespect your personality and answer with short, friendly and accurate thoughtful brief lines.'),
@@ -249,13 +249,14 @@ export function Telephone(props: {
let finalText = '';
setPersonaTextInterim('💭...');
aixChatGenerateContent_DMessage_FromHistory(
aixChatGenerateContent_DMessage_FromConversation(
chatLLMId,
callSystemInstruction,
callGenerationInputHistory,
'call',
callMessages[0].id,
{ abortSignal: responseAbortController.current.signal },
(update: AixChatGenerateContent_DMessage, isDone: boolean) => {
(update: AixChatGenerateContent_DMessage, _isDone: boolean) => {
const updatedText = messageFragmentsReduceText(update.fragments).trim();
if (updatedText)
setPersonaTextInterim(finalText = updatedText);
+13 -5
View File
@@ -1,4 +1,4 @@
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromHistory } from '~/modules/aix/client/aix.client';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromConversation } from '~/modules/aix/client/aix.client';
import { autoChatFollowUps } from '~/modules/aifn/auto-chat-follow-ups/autoChatFollowUps';
import { autoConversationTitle } from '~/modules/aifn/autotitle/autoTitle';
@@ -34,14 +34,21 @@ export async function runPersonaOnConversationHead(
const cHandler = ConversationsManager.getHandler(conversationId);
const history = cHandler.historyViewHead('runPersonaOnConversationHead') as Readonly<DMessage[]>;
const _history = cHandler.historyViewHead('runPersonaOnConversationHead') as Readonly<DMessage[]>;
if (_history.length === 0)
return false;
// split pre dynamic-personas
const chatSystemInstruction = _history[0].role === 'system' ? _history[0] : null;
const chatHistory = (chatSystemInstruction ? _history.slice(1) : _history);
// .map(_m => _m.role === 'system' ? { ..._m, role: 'user' as const } : _m) // cast system chat messages to the user role
// assistant response placeholder
const isNotifyEnabled = getIsNotificationEnabledForModel(assistantLlmId);
const { assistantMessageId } = cHandler.messageAppendAssistantPlaceholder(
CHATGENERATE_RESPONSE_PLACEHOLDER,
{
purposeId: history[0].purposeId,
purposeId: chatSystemInstruction?.purposeId,
generator: { mgt: 'named', name: assistantLlmId },
...(isNotifyEnabled ? { userFlags: [MESSAGE_FLAG_NOTIFY_COMPLETE] } : {}),
},
@@ -60,9 +67,10 @@ export async function runPersonaOnConversationHead(
cHandler.setAbortController(abortController, 'chat-persona');
// stream the assistant's messages directly to the state store
const messageStatus = await aixChatGenerateContent_DMessage_FromHistory(
const messageStatus = await aixChatGenerateContent_DMessage_FromConversation(
assistantLlmId,
history,
chatSystemInstruction,
chatHistory,
'conversation',
conversationId,
{ abortSignal: abortController.signal, throttleParallelThreads: parallelViewCount },
@@ -3,7 +3,7 @@ import { z } from 'zod';
import { getLLMIdOrThrow } from '~/common/stores/llms/store-llms';
import type { AixAPIChatGenerate_Request } from '~/modules/aix/server/api/aix.wiretypes';
import { aixCGR_FromDMessagesOrThrow, aixCGR_SystemMessage } from '~/modules/aix/client/aix.client.chatGenerateRequest';
import { aixCGR_ChatSequence_FromDMessagesOrThrow, aixCGR_SystemMessageText } from '~/modules/aix/client/aix.client.chatGenerateRequest';
import { aixChatGenerateContent_DMessage, aixCreateChatGenerateContext } from '~/modules/aix/client/aix.client';
import { aixFunctionCallTool, aixRequireSingleFunctionCallInvocation } from '~/modules/aix/client/aix.client.fromSimpleFunction';
@@ -41,11 +41,11 @@ export async function agiAttachmentPrompts(attachmentFragments: DMessageAttachme
});
const aixChatGenerate: AixAPIChatGenerate_Request = {
systemMessage: aixCGR_SystemMessage(
systemMessage: aixCGR_SystemMessageText(
`You are an AI assistant skilled in content analysis and task inference within a chat application.
Your function is to examine the attachments provided by the user, understand their nature and potential relationships, guess the user intention, and suggest the most likely and valuable actions the user intends to perform.
Respond only by calling the propose_user_actions_for_attachments function.`),
chatSequence: (await aixCGR_FromDMessagesOrThrow([{
chatSequence: await aixCGR_ChatSequence_FromDMessagesOrThrow([{
role: 'user',
fragments: [createTextContentFragment(`The user wants to perform an action for which is attaching ${docs_count} related pieces of content.
Analyze the provided content to determine its nature, identify any relationships between the pieces, and infer the most probable high-value task or action the user wants to perform.`)],
@@ -55,7 +55,7 @@ Analyze the provided content to determine its nature, identify any relationships
}, {
role: 'user',
fragments: [createTextContentFragment(`Call the function once, filling in order the attachments, the relationships between them, the top ${num_suggestions} orthogonal actions you inferred and the single most valuable action.`)],
}])).chatSequence,
}]),
tools: [
aixFunctionCallTool({
name: 'propose_user_actions_for_attachments',
@@ -2,7 +2,7 @@ import { z } from 'zod';
import type { AixAPIChatGenerate_Request } from '~/modules/aix/server/api/aix.wiretypes';
import { AixClientFunctionCallToolDefinition, aixFunctionCallTool, aixRequireSingleFunctionCallInvocation } from '~/modules/aix/client/aix.client.fromSimpleFunction';
import { aixCGR_FromDMessagesOrThrow, aixCGR_SystemMessage } from '~/modules/aix/client/aix.client.chatGenerateRequest';
import { aixCGR_ChatSequence_FromDMessagesOrThrow, aixCGR_SystemMessageText } from '~/modules/aix/client/aix.client.chatGenerateRequest';
import { aixChatGenerateContent_DMessage, aixCreateChatGenerateContext } from '~/modules/aix/client/aix.client';
import { ConversationsManager } from '~/common/chat-overlay/ConversationsManager';
@@ -43,7 +43,7 @@ interface DumbToolTBD {
function _getSystemMessage(tool: DumbToolTBD, variables: Record<string, string>, templateName: string): AixAPIChatGenerate_Request['systemMessage'] {
return aixCGR_SystemMessage(processPromptTemplate(tool.sys, { ...variables, functionName: tool.fun.name }, templateName));
return aixCGR_SystemMessageText(processPromptTemplate(tool.sys, { ...variables, functionName: tool.fun.name }, templateName));
}
@@ -180,11 +180,11 @@ export async function autoChatFollowUps(conversationId: string, assistantMessage
// Instructions
const systemMessage = _getSystemMessage(diagramsTool, { personaSystemPrompt }, 'chat-followup-diagram_system');
const chatSequence = (await aixCGR_FromDMessagesOrThrow([
const chatSequence = await aixCGR_ChatSequence_FromDMessagesOrThrow([
userMessage,
assistantMessage,
createDMessageTextContent('user', processPromptTemplate(diagramsTool.usr, { functionName: diagramsTool.fun.name }, 'chat-followup-diagram_reminder')),
])).chatSequence;
]);
// Strict call to a function
aixChatGenerateContent_DMessage(
@@ -231,11 +231,11 @@ export async function autoChatFollowUps(conversationId: string, assistantMessage
// Instructions
const systemMessage = _getSystemMessage(uiTool, { personaSystemPrompt }, 'chat-followup-htmlui_system');
const chatSequence = (await aixCGR_FromDMessagesOrThrow([
const chatSequence = await aixCGR_ChatSequence_FromDMessagesOrThrow([
userMessage,
assistantMessage,
createDMessageTextContent('user', processPromptTemplate(uiTool.usr, { functionName: uiTool.fun.name }, 'chat-followup-htmlui_reminder')),
])).chatSequence;
]);
// Strict call to a function
aixChatGenerateContent_DMessage(
@@ -28,7 +28,7 @@ export type AixChatGenerate_TextMessages = {
export function aixCGR_FromSimpleText(systemInstruction: string, messages: AixChatGenerate_TextMessages): AixAPIChatGenerate_Request {
return {
systemMessage: aixCGR_SystemMessage(systemInstruction),
systemMessage: aixCGR_SystemMessageText(systemInstruction),
chatSequence: messages.map(m => {
switch (m.role) {
case 'user':
@@ -40,7 +40,7 @@ export function aixCGR_FromSimpleText(systemInstruction: string, messages: AixCh
};
}
export function aixCGR_SystemMessage(text: string) {
export function aixCGR_SystemMessageText(text: string) {
return { parts: [aixCGRTextPart(text)] };
}
@@ -61,45 +61,61 @@ function aixCGRTextPart(text: string) {
// AIX <> Chat Messages API helpers
//
type AixCGR_FromDmessages = Pick<AixAPIChatGenerate_Request, 'systemMessage' | 'chatSequence'>;
export async function aixCGR_FromDMessagesOrThrow(
messageSequence: Readonly<Pick<DMessage, 'role' | 'fragments' | 'metadata' | 'userFlags'>[]>, // Note: adding the "Pick" to show the low requirement from the DMessage type, as we'll move to simpler APIs soon
_assemblyMode: 'complete' = 'complete',
): Promise<AixCGR_FromDmessages> {
export async function aixCGR_SystemMessage_FromDMessageOrThrow(
systemInstruction: null | Pick<DMessage, 'fragments' | 'metadata' | 'userFlags'>,
): Promise<AixAPIChatGenerate_Request['systemMessage']> {
// quick bypass for no message
if (!systemInstruction)
return undefined;
// create the system instruction
const sm: AixAPIChatGenerate_Request['systemMessage'] = {
parts: [],
};
// process fragments of the system instruction
for (const fragment of systemInstruction.fragments) {
if (isTextContentFragment(fragment)) {
sm.parts.push(fragment.part);
}
// TODO: handle other types of fragments if needed, such as the 'doc' type
else {
if (process.env.NODE_ENV === 'development')
throw new Error('[DEV] aixCGR_systemMessageFromInstruction: unexpected system fragment');
console.warn('[DEV] aixCGR_systemMessageFromInstruction: unexpected system fragment:', fragment);
}
}
// (on System message) handle the ant-cache-prompt user/auto flags
const mHasAntCacheFlag = messageHasUserFlag(systemInstruction, MESSAGE_FLAG_VND_ANT_CACHE_AUTO) || messageHasUserFlag(systemInstruction, MESSAGE_FLAG_VND_ANT_CACHE_USER);
if (mHasAntCacheFlag)
sm.parts.push(_clientCreateAixMetaCacheControlPart('anthropic-ephemeral'));
return sm;
}
export async function aixCGR_ChatSequence_FromDMessagesOrThrow(
messageSequenceWithoutSystem: Readonly<Pick<DMessage, 'role' | 'fragments' | 'metadata' | 'userFlags'>[]>, // Note: adding the "Pick" to show the low requirement from the DMessage type, as we'll move to simpler APIs soon
// _assemblyMode: 'complete' = 'complete',
): Promise<AixAPIChatGenerate_Request['chatSequence']> {
// if the user has marked messages for exclusion, we skip them
messageSequence = messageSequence.filter(m => !messageHasUserFlag(m, MESSAGE_FLAG_AIX_SKIP));
messageSequenceWithoutSystem = messageSequenceWithoutSystem.filter(m => !messageHasUserFlag(m, MESSAGE_FLAG_AIX_SKIP));
// reduce history
return await messageSequence.reduce(async (accPromise, m, index): Promise<AixCGR_FromDmessages> => {
// NOTE: we used to have a "systemMessage" here, but we're moving to a more strict API with separate processing of it;
// - as such we now 'throw' if a system message is found (on dev mode, and just warn in production).
// - still, we keep the full reducer as a 'AixCGR_FromDmessages' type, in case we need more complex reductions in the future
const cgr = await messageSequenceWithoutSystem.reduce(async (accPromise, m, _index): Promise<AixAPIChatGenerate_Request> => {
const acc = await accPromise;
// (on Assistant messages) handle the ant-cache-prompt user/auto flags
// (on any User/Assistant messages) check the ant-cache-prompt user/auto flags
const mHasAntCacheFlag = messageHasUserFlag(m, MESSAGE_FLAG_VND_ANT_CACHE_AUTO) || messageHasUserFlag(m, MESSAGE_FLAG_VND_ANT_CACHE_USER);
// extract system
if (index === 0 && m.role === 'system') {
// create parts if not exist
if (!acc.systemMessage) {
acc.systemMessage = {
parts: [],
};
}
for (const systemFragment of m.fragments) {
if (isTextContentFragment(systemFragment)) {
acc.systemMessage.parts.push(systemFragment.part);
} else {
console.warn('aixCGR_FromDMessages: unexpected system fragment', systemFragment);
}
}
// (on System message) handle the ant-cache-prompt user/auto flags
if (mHasAntCacheFlag)
acc.systemMessage.parts.push(_clientCreateAixMetaCacheControlPart('anthropic-ephemeral'));
return acc;
}
// map the other parts
// in the new version we handle all parts and only expect User and Assistant DMessages - as the System has been handled separately
const dMessageRole: DMessageRole = m.role;
if (dMessageRole === 'user') {
@@ -231,6 +247,10 @@ export async function aixCGR_FromDMessagesOrThrow(
} else {
// DEV MODE: THROW ERROR, to aid the porting efforts
if (process.env.NODE_ENV === 'development')
throw new Error(`[DEV] aixCGR_FromDMessages: unexpected message role ${m.role}. Please PORT the caller to the systemIntruction API change.`);
// TODO: implement mid-chat system messages if needed
// NOTE: the API should just disallow 'system' messages in the middle of the chat
console.warn('[DEV] aixCGR_FromDMessages: unexpected message role', m.role);
@@ -238,7 +258,12 @@ export async function aixCGR_FromDMessagesOrThrow(
}
return acc;
}, Promise.resolve({ chatSequence: [] } as AixCGR_FromDmessages));
}, Promise.resolve({
chatSequence: [],
} as Pick<AixAPIChatGenerate_Request, 'chatSequence'>) /* this is the key to the new version of this function which doesn't extract system messages anymore */);
// as promised we only return this as we only built this, and not the full CGR.
return cgr.chatSequence;
}
+9 -5
View File
@@ -3,7 +3,7 @@ import { findServiceAccessOrThrow } from '~/modules/llms/vendors/vendor.helpers'
import type { DMessage, DMessageGenerator } from '~/common/stores/chat/chat.message';
import { DLLM, DLLMId, LLM_IF_SPECIAL_OAI_O1Preview } from '~/common/stores/llms/llms.types';
import { apiStream } from '~/common/util/trpc.client';
import { metricsChatGenerateLgToMd, metricsComputeChatGenerateCostsMd, DMetricsChatGenerate_Lg } from '~/common/stores/metrics/metrics.chatgenerate';
import { DMetricsChatGenerate_Lg, metricsChatGenerateLgToMd, metricsComputeChatGenerateCostsMd } from '~/common/stores/metrics/metrics.chatgenerate';
import { createErrorContentFragment, DMessageContentFragment, DMessageErrorPart, isErrorPart } from '~/common/stores/chat/chat.fragments';
import { findLLMOrThrow } from '~/common/stores/llms/store-llms';
import { getLabsDevMode, getLabsDevNoStreaming } from '~/common/state/store-ux-labs';
@@ -13,7 +13,7 @@ import { presentErrorToHumans } from '~/common/util/errorUtils';
// NOTE: pay particular attention to the "import type", as this is importing from the server-side Zod definitions
import type { AixAPI_Access, AixAPI_Context_ChatGenerate, AixAPI_Model, AixAPIChatGenerate_Request } from '../server/api/aix.wiretypes';
import { aixCGR_FromDMessagesOrThrow, aixCGR_FromSimpleText, AixChatGenerate_TextMessages, clientHotFixGenerateRequestForO1Preview } from './aix.client.chatGenerateRequest';
import { aixCGR_ChatSequence_FromDMessagesOrThrow, aixCGR_FromSimpleText, aixCGR_SystemMessage_FromDMessageOrThrow, AixChatGenerate_TextMessages, clientHotFixGenerateRequestForO1Preview } from './aix.client.chatGenerateRequest';
import { ContentReassembler } from './ContentReassembler';
import { ThrottleFunctionCall } from './ThrottleFunctionCall';
@@ -79,10 +79,11 @@ interface AixClientOptions {
/**
* Level 3 Generation from an LLM Id + Chat History.
*/
export async function aixChatGenerateContent_DMessage_FromHistory(
export async function aixChatGenerateContent_DMessage_FromConversation(
// chat-inputs -> Partial<DMessage> outputs
llmId: DLLMId,
chatHistory: Readonly<DMessage[]>,
chatSystemInstruction: null | Pick<DMessage, 'fragments' | 'metadata' | 'userFlags'>,
chatHistoryWithoutSystemMessages: Readonly<DMessage[]>,
// aix inputs
aixContextName: AixAPI_Context_ChatGenerate['name'],
aixContextRef: AixAPI_Context_ChatGenerate['ref'],
@@ -105,7 +106,10 @@ export async function aixChatGenerateContent_DMessage_FromHistory(
try {
// Aix ChatGenerate Request
const aixChatContentGenerateRequest = await aixCGR_FromDMessagesOrThrow(chatHistory, 'complete');
const aixChatContentGenerateRequest: AixAPIChatGenerate_Request = {
systemMessage: await aixCGR_SystemMessage_FromDMessageOrThrow(chatSystemInstruction),
chatSequence: await aixCGR_ChatSequence_FromDMessagesOrThrow(chatHistoryWithoutSystemMessages),
};
await aixChatGenerateContent_DMessage(
llmId,
@@ -4,7 +4,7 @@ import { Typography } from '@mui/joy';
import { ChatMessage } from '../../../../apps/chat/components/message/ChatMessage';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromHistory } from '~/modules/aix/client/aix.client';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromConversation } from '~/modules/aix/client/aix.client';
import { bareBonesPromptMixer } from '~/modules/persona/pmix/pmix';
import { createDMessageTextContent, DMessage, messageFragmentsReduceText, messageWasInterruptedAtStart } from '~/common/stores/chat/chat.message';
@@ -50,9 +50,8 @@ export async function executeGatherInstruction(_i: GatherInstruction, inputs: Ex
if (rayMessage.role !== 'assistant')
throw new Error('Invalid response role');
const gatherSystemInstruction = createDMessageTextContent('system', _mixChatGeneratePrompt(_i.systemPrompt, inputs.rayMessages.length, prevStepOutput));
const gatherHistory: DMessage[] = [
// s
createDMessageTextContent('system', _mixChatGeneratePrompt(_i.systemPrompt, inputs.rayMessages.length, prevStepOutput)),
// s0-h0-u0: remove the system messages
...inputs.chatMessages
.filter(_m => (_m.role === 'user' || _m.role === 'assistant')),
@@ -105,8 +104,9 @@ export async function executeGatherInstruction(_i: GatherInstruction, inputs: Ex
};
// stream the gathered message
return aixChatGenerateContent_DMessage_FromHistory(
return aixChatGenerateContent_DMessage_FromConversation(
inputs.llmId,
gatherSystemInstruction,
gatherHistory,
'beam-gather', inputs.contextRef,
{ abortSignal: inputs.chainAbortController.signal, throttleParallelThreads: getUXLabsHighPerformance() ? 0 : 1 },
+10 -3
View File
@@ -1,6 +1,6 @@
import type { StateCreator } from 'zustand/vanilla';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromHistory } from '~/modules/aix/client/aix.client';
import { AixChatGenerateContent_DMessage, aixChatGenerateContent_DMessage_FromConversation } from '~/modules/aix/client/aix.client';
import type { DLLMId } from '~/common/stores/llms/llms.types';
import { agiUuid } from '~/common/util/idUtils';
@@ -53,6 +53,12 @@ function rayScatterStart(ray: BRay, llmId: DLLMId | null, inputHistory: DMessage
if (!inputHistory || inputHistory.length < 1 || inputHistory[inputHistory.length - 1].role !== 'user')
return { ...ray, scatterIssue: `Invalid conversation history (${inputHistory?.length})` };
// split pre dynamic-personas
const scatterSystemInstruction = inputHistory[0].role === 'system' ? inputHistory[0] : null;
const scatterInputHistory = (scatterSystemInstruction ? inputHistory.slice(1) : inputHistory);
// .map(_m => _m.role === 'system' ? { ..._m, role: 'user' as const } : _m) // cast system chat messages to the user role
const abortController = new AbortController();
const onMessageUpdated = (incrementalMessage: AixChatGenerateContent_DMessage, completed: boolean) => {
@@ -69,9 +75,10 @@ function rayScatterStart(ray: BRay, llmId: DLLMId | null, inputHistory: DMessage
};
// stream the ray's messages directly to the state store
aixChatGenerateContent_DMessage_FromHistory(
aixChatGenerateContent_DMessage_FromConversation(
llmId,
inputHistory,
scatterSystemInstruction,
scatterInputHistory,
'beam-scatter', ray.rayId,
{ abortSignal: abortController.signal, throttleParallelThreads: getUXLabsHighPerformance() ? 0 : rays.length },
onMessageUpdated,