Просмотр исходного кода

Merge pull request #9833 from weseek/feat/164290-llm-response-format

feat: LLM response format
Yuki Takei 1 год назад
Родитель
Сommit
224ed0a167

+ 16 - 13
apps/app/src/features/openai/client/services/editor-assistant.ts

@@ -11,9 +11,10 @@ import {
   SseMessageSchema,
   SseDetectedDiffSchema,
   SseFinalizedSchema,
-  isInsertDiff,
-  isDeleteDiff,
-  isRetainDiff,
+  isReplaceDiff,
+  // isInsertDiff,
+  // isDeleteDiff,
+  // isRetainDiff,
   type SseMessage,
   type SseDetectedDiff,
   type SseFinalized,
@@ -93,10 +94,9 @@ export const useEditorAssistant = (): {postMessage: PostMessage, processMessage:
   }, [codeMirrorEditor?.view, mutateIsEnableUnifiedMergeView]);
 
   useEffect(() => {
-    const markdown = codeMirrorEditor?.getDoc();
 
     const pendingDetectedDiff: DetectedDiff | undefined = detectedDiff?.filter(diff => diff.applied === false);
-    if (markdown != null && ydocs?.secondaryDoc != null && pendingDetectedDiff != null && pendingDetectedDiff.length > 0) {
+    if (ydocs?.secondaryDoc != null && pendingDetectedDiff != null && pendingDetectedDiff.length > 0) {
 
       // For debug
       // const testDetectedDiff = [
@@ -120,15 +120,18 @@ export const useEditorAssistant = (): {postMessage: PostMessage, processMessage:
       const ytext = ydocs.secondaryDoc.getText('codemirror');
       ydocs.secondaryDoc.transact(() => {
         pendingDetectedDiff.forEach((detectedDiff) => {
-          if (isInsertDiff(detectedDiff.data)) {
-            ytext.insert(positionRef.current, detectedDiff.data.diff.insert);
-          }
-          if (isDeleteDiff(detectedDiff.data)) {
-            ytext.delete(positionRef.current, detectedDiff.data.diff.delete);
-          }
-          if (isRetainDiff(detectedDiff.data)) {
-            positionRef.current += detectedDiff.data.diff.retain;
+          if (isReplaceDiff(detectedDiff.data)) {
+            // TODO: https://redmine.weseek.co.jp/issues/164330
           }
+          // if (isInsertDiff(detectedDiff.data)) {
+          //   ytext.insert(positionRef.current, detectedDiff.data.diff.insert);
+          // }
+          // if (isDeleteDiff(detectedDiff.data)) {
+          //   ytext.delete(positionRef.current, detectedDiff.data.diff.delete);
+          // }
+          // if (isRetainDiff(detectedDiff.data)) {
+          //   positionRef.current += detectedDiff.data.diff.retain;
+          // }
         });
       });
 

+ 15 - 12
apps/app/src/features/openai/interfaces/editor-assistant/llm-response-schemas.ts

@@ -11,18 +11,21 @@ export const LlmEditorAssistantMessageSchema = z.object({
 
 export const LlmEditorAssistantDiffSchema = z
   .object({
-    insert: z.string().describe('The text that should insert the content in the current position'),
-  })
-  .or(
-    z.object({
-      delete: z.number().int().describe('The number of characters that should be deleted from the current position'),
-    }),
-  )
-  .or(
-    z.object({
-      retain: z.number().int().describe('The number of characters that should be retained in the current position'),
-    }),
-  );
+    replace: z.string().describe('The text that should replace the current content'),
+  });
+  // .object({
+  //   insert: z.string().describe('The text that should insert the content in the current position'),
+  // })
+  // .or(
+  //   z.object({
+  //     delete: z.number().int().describe('The number of characters that should be deleted from the current position'),
+  //   }),
+  // )
+  // .or(
+  //   z.object({
+  //     retain: z.number().int().describe('The number of characters that should be retained in the current position'),
+  //   }),
+  // );
 
 // Type definitions
 export type LlmEditorAssistantMessage = z.infer<typeof LlmEditorAssistantMessageSchema>;

+ 12 - 8
apps/app/src/features/openai/interfaces/editor-assistant/sse-schemas.ts

@@ -30,14 +30,18 @@ export type SseDetectedDiff = z.infer<typeof SseDetectedDiffSchema>;
 export type SseFinalized = z.infer<typeof SseFinalizedSchema>;
 
 // Type guard for SseDetectedDiff
-export const isInsertDiff = (diff: SseDetectedDiff): diff is { diff: { insert: string } } => {
-  return 'insert' in diff.diff;
-};
+// export const isInsertDiff = (diff: SseDetectedDiff): diff is { diff: { insert: string } } => {
+//   return 'insert' in diff.diff;
+// };
 
-export const isDeleteDiff = (diff: SseDetectedDiff): diff is { diff: { delete: number } } => {
-  return 'delete' in diff.diff;
-};
+// export const isDeleteDiff = (diff: SseDetectedDiff): diff is { diff: { delete: number } } => {
+//   return 'delete' in diff.diff;
+// };
+
+// export const isRetainDiff = (diff: SseDetectedDiff): diff is { diff : { retain: number} } => {
+//   return 'retain' in diff.diff;
+// };
 
-export const isRetainDiff = (diff: SseDetectedDiff): diff is { diff : { retain: number} } => {
-  return 'retain' in diff.diff;
+export const isReplaceDiff = (diff: SseDetectedDiff): diff is { diff: { replace: string } } => {
+  return 'replace' in diff.diff;
 };

+ 34 - 7
apps/app/src/features/openai/server/routes/edit/index.ts

@@ -146,18 +146,16 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
               role: 'assistant',
               content: `You are an Editor Assistant for GROWI, a markdown wiki system.
               Your task is to help users edit their markdown content based on their requests.
+              Spaces and line breaks are also counted as individual characters.
 
               RESPONSE FORMAT:
               You must respond with a JSON object in the following format example:
               {
                 "contents": [
                   { "message": "Your brief message about the upcoming change or proposal.\n\n" },
-                  { "retain": 10 },
-                  { "insert": "New text 1" },
+                  { "replace": New text 1 },,
                   { "message": "Additional explanation if needed" },
-                  { "retain": 100 },
-                  { "delete": 15 },
-                  { "insert": "New text 2" },
+                  { "replace": "New text 2" },
                   ...more items if needed
                   { "message": "Your friendly message explaining what changes were made or suggested." }
                 ]
@@ -166,12 +164,41 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
               The array should contain:
               - [At the beginning of the list] A "message" object that has your brief message about the upcoming change or proposal. Be sure to add two consecutive line feeds ('\n\n') at the end.
               - Objects with a "message" key for explanatory text to the user if needed.
-              - Objects with "insert", "delete", and "retain" keys for replacements (Delta format by Quill Rich Text Editor)
+              - Edit markdown according to user instructions and include it line by line in the 'replace' object. Return original text for lines that do not need editing.
               - [At the end of the list] A "message" object that contains your friendly message explaining that the operation was completed and what changes were made.
 
-              If no changes are needed, include only message objects with explanations.
               Always provide messages in the same language as the user's request.`,
             },
+            // {
+            //   role: 'assistant',
+            //   content: `You are an Editor Assistant for GROWI, a markdown wiki system.
+            //   Your task is to help users edit their markdown content based on their requests.
+
+            //   RESPONSE FORMAT:
+            //   You must respond with a JSON object in the following format example:
+            //   {
+            //     "contents": [
+            //       { "message": "Your brief message about the upcoming change or proposal.\n\n" },
+            //       { "retain": 10 },
+            //       { "insert": "New text 1" },
+            //       { "message": "Additional explanation if needed" },
+            //       { "retain": 100 },
+            //       { "delete": 15 },
+            //       { "insert": "New text 2" },
+            //       ...more items if needed
+            //       { "message": "Your friendly message explaining what changes were made or suggested." }
+            //     ]
+            //   }
+
+            //   The array should contain:
+            //   - [At the beginning of the list] A "message" object that has your brief message about the upcoming change or proposal. Be sure to add two consecutive line feeds ('\n\n') at the end.
+            //   - Objects with a "message" key for explanatory text to the user if needed.
+            //   - Objects with "insert", "delete", and "retain" keys for replacements (Delta format by Quill Rich Text Editor)
+            //   - [At the end of the list] A "message" object that contains your friendly message explaining that the operation was completed and what changes were made.
+
+            //   If no changes are needed, include only message objects with explanations.
+            //   Always provide messages in the same language as the user's request.`,
+            // },
             {
               role: 'user',
               content: `Current markdown content:\n\`\`\`markdown\n${markdown}\n\`\`\`\n\nUser request: ${userMessage}`,

+ 7 - 4
apps/app/src/features/openai/server/services/editor-assistant/llm-response-stream-processor.ts

@@ -22,7 +22,8 @@ const isMessageItem = (item: unknown): item is LlmEditorAssistantMessage => {
  */
 const isDiffItem = (item: unknown): item is LlmEditorAssistantDiff => {
   return typeof item === 'object' && item !== null
-    && ('insert' in item || 'delete' in item || 'retain' in item);
+    // && ('insert' in item || 'delete' in item || 'retain' in item);
+    && ('replace' in item);
 };
 
 type Options = {
@@ -30,6 +31,7 @@ type Options = {
   diffDetectedCallback?: (detected: LlmEditorAssistantDiff) => void,
   dataFinalizedCallback?: (message: string | null, replacements: LlmEditorAssistantDiff[]) => void,
 }
+
 /**
  * AI response stream processor for Editor Assisntant
  * Extracts messages and diffs from JSON stream for editor
@@ -174,9 +176,10 @@ export class LlmResponseStreamProcessor {
    * Generate unique key for a diff
    */
   private getDiffKey(diff: LlmEditorAssistantDiff, index: number): string {
-    if ('insert' in diff) return `insert-${index}`;
-    if ('delete' in diff) return `delete-${index}`;
-    if ('retain' in diff) return `retain-${index}`;
+    // if ('insert' in diff) return `insert-${index}`;
+    // if ('delete' in diff) return `delete-${index}`;
+    // if ('retain' in diff) return `retain-${index}`;
+    if ('replace' in diff) return `replace-${index}`;
     return '';
   }