fix: send system instructions in Devin's dedicated prompt field
This commit is contained in:
+7
-9
@@ -2,6 +2,7 @@ import {
|
||||
collapseSystemMessages,
|
||||
getCurrentSystemPrompt,
|
||||
getCurrentTools,
|
||||
withoutInitialSystemMessage,
|
||||
type Message,
|
||||
type Tool,
|
||||
type TranscriptContext,
|
||||
@@ -15,7 +16,7 @@ export interface ContentPart {
|
||||
}
|
||||
|
||||
export interface ChatHistoryItem {
|
||||
role: "user" | "assistant" | "system" | "tool";
|
||||
role: "user" | "assistant" | "tool";
|
||||
content: string | ContentPart[];
|
||||
tool_call_id?: string;
|
||||
tool_calls?: Array<{ id: string; name: string; arguments: string }>;
|
||||
@@ -28,6 +29,8 @@ export interface ToolDef {
|
||||
}
|
||||
|
||||
export interface MappedChat {
|
||||
/** Sent in GetChatMessageRequest.prompt, separately from conversation history. */
|
||||
systemPrompt?: string;
|
||||
messages: ChatHistoryItem[];
|
||||
tools: ToolDef[];
|
||||
}
|
||||
@@ -47,16 +50,11 @@ function userContent(content: Message["content"]): string | ContentPart[] {
|
||||
}
|
||||
|
||||
export function mapContextToChat(context: TranscriptContext): MappedChat {
|
||||
// Devin takes one leading system message and knows no mid-conversation system
|
||||
// message, so replay the transcript deltas into the leading one first.
|
||||
// Fold later system updates into the single prompt sent separately to Devin.
|
||||
const transcript = collapseSystemMessages(context);
|
||||
const systemPrompt = getCurrentSystemPrompt(transcript.messages);
|
||||
const messages: ChatHistoryItem[] = [];
|
||||
if (systemPrompt) {
|
||||
messages.push({ role: "system", content: systemPrompt });
|
||||
}
|
||||
|
||||
for (const message of transcript.messages) {
|
||||
for (const message of withoutInitialSystemMessage(transcript.messages)) {
|
||||
if (message.role === "user") {
|
||||
messages.push({ role: "user", content: userContent(message.content) });
|
||||
continue;
|
||||
@@ -103,5 +101,5 @@ export function mapContextToChat(context: TranscriptContext): MappedChat {
|
||||
parameters: tool.parameters,
|
||||
}));
|
||||
|
||||
return { messages, tools };
|
||||
return { systemPrompt: systemPrompt || undefined, messages, tools };
|
||||
}
|
||||
|
||||
+8
-38
@@ -23,10 +23,9 @@ import {
|
||||
iterFields,
|
||||
} from "./wire.js";
|
||||
|
||||
const SOURCE_BY_ROLE: Record<string, number> = {
|
||||
const SOURCE_BY_ROLE: Record<ChatHistoryItem["role"], number> = {
|
||||
user: 1,
|
||||
assistant: 2,
|
||||
system: 1,
|
||||
tool: 4,
|
||||
};
|
||||
|
||||
@@ -50,40 +49,6 @@ function normalizeContent(content: string | ContentPart[]): ContentPart[] {
|
||||
return content;
|
||||
}
|
||||
|
||||
function collapseSystemIntoUser(messages: ChatHistoryItem[]): ChatHistoryItem[] {
|
||||
const out: ChatHistoryItem[] = [];
|
||||
let pending: string[] = [];
|
||||
const textOf = (content: string | ContentPart[]) =>
|
||||
normalizeContent(content)
|
||||
.filter((part) => part.type === "text" && part.text)
|
||||
.map((part) => part.text as string)
|
||||
.join("\n");
|
||||
|
||||
for (const message of messages) {
|
||||
if (message.role === "system") {
|
||||
const text = textOf(message.content);
|
||||
if (text) pending.push(text);
|
||||
continue;
|
||||
}
|
||||
if (message.role === "user" && pending.length > 0) {
|
||||
const userParts = normalizeContent(message.content);
|
||||
const userText = textOf(userParts);
|
||||
const images = userParts.filter((part) => part.type === "image");
|
||||
out.push({
|
||||
role: "user",
|
||||
content: [{ type: "text", text: `<system>\n${pending.join("\n\n")}\n</system>\n${userText}` }, ...images],
|
||||
});
|
||||
pending = [];
|
||||
continue;
|
||||
}
|
||||
out.push(message);
|
||||
}
|
||||
if (pending.length > 0) {
|
||||
out.push({ role: "user", content: `<system>\n${pending.join("\n\n")}\n</system>` });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function encodeImageData(img: { mimeType?: string; base64Data?: string }): Buffer {
|
||||
return Buffer.concat([
|
||||
encodeString(1, img.base64Data ?? ""),
|
||||
@@ -144,6 +109,7 @@ function buildGetChatMessageRequest(args: {
|
||||
apiKey: string;
|
||||
userJwt: string;
|
||||
modelUid: string;
|
||||
systemPrompt?: string;
|
||||
messages: ChatHistoryItem[];
|
||||
tools?: ToolDef[];
|
||||
cascadeId: string;
|
||||
@@ -160,10 +126,10 @@ function buildGetChatMessageRequest(args: {
|
||||
requestId: args.requestId,
|
||||
triggerId: args.triggerId,
|
||||
});
|
||||
const prompts = collapseSystemIntoUser(args.messages).map((message) =>
|
||||
const prompts = args.messages.map((message) =>
|
||||
encodeMessage(
|
||||
3,
|
||||
encodeChatMessagePrompt(normalizeContent(message.content), SOURCE_BY_ROLE[message.role] ?? 1, {
|
||||
encodeChatMessagePrompt(normalizeContent(message.content), SOURCE_BY_ROLE[message.role], {
|
||||
toolCallId: message.role === "tool" ? message.tool_call_id : undefined,
|
||||
toolCalls: message.role === "assistant" ? message.tool_calls : undefined,
|
||||
}),
|
||||
@@ -171,6 +137,7 @@ function buildGetChatMessageRequest(args: {
|
||||
);
|
||||
return Buffer.concat([
|
||||
encodeMessage(1, metadata),
|
||||
...(args.systemPrompt ? [encodeString(2, args.systemPrompt)] : []),
|
||||
...prompts,
|
||||
encodeVarintField(7, 5),
|
||||
encodeMessage(8, encodeCompletionConfiguration(args.maxOutputTokens)),
|
||||
@@ -271,6 +238,7 @@ async function* streamChatEvents(args: {
|
||||
apiKey: string;
|
||||
host: string;
|
||||
modelUid: string;
|
||||
systemPrompt?: string;
|
||||
messages: ChatHistoryItem[];
|
||||
tools?: ToolDef[];
|
||||
maxOutputTokens?: number;
|
||||
@@ -283,6 +251,7 @@ async function* streamChatEvents(args: {
|
||||
apiKey: args.apiKey,
|
||||
userJwt,
|
||||
modelUid: args.modelUid,
|
||||
systemPrompt: args.systemPrompt,
|
||||
messages: args.messages,
|
||||
tools: args.tools,
|
||||
cascadeId: ids.cascadeId,
|
||||
@@ -482,6 +451,7 @@ export function streamDevin(
|
||||
apiKey,
|
||||
host,
|
||||
modelUid,
|
||||
systemPrompt: mapped.systemPrompt,
|
||||
messages: mapped.messages,
|
||||
tools: mapped.tools.length > 0 ? mapped.tools : undefined,
|
||||
maxOutputTokens: options?.maxTokens,
|
||||
|
||||
Reference in New Issue
Block a user