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

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,
   SseMessageSchema,
   SseDetectedDiffSchema,
   SseDetectedDiffSchema,
   SseFinalizedSchema,
   SseFinalizedSchema,
-  isInsertDiff,
-  isDeleteDiff,
-  isRetainDiff,
+  isReplaceDiff,
+  // isInsertDiff,
+  // isDeleteDiff,
+  // isRetainDiff,
   type SseMessage,
   type SseMessage,
   type SseDetectedDiff,
   type SseDetectedDiff,
   type SseFinalized,
   type SseFinalized,
@@ -93,10 +94,9 @@ export const useEditorAssistant = (): {postMessage: PostMessage, processMessage:
   }, [codeMirrorEditor?.view, mutateIsEnableUnifiedMergeView]);
   }, [codeMirrorEditor?.view, mutateIsEnableUnifiedMergeView]);
 
 
   useEffect(() => {
   useEffect(() => {
-    const markdown = codeMirrorEditor?.getDoc();
 
 
     const pendingDetectedDiff: DetectedDiff | undefined = detectedDiff?.filter(diff => diff.applied === false);
     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
       // For debug
       // const testDetectedDiff = [
       // const testDetectedDiff = [
@@ -120,15 +120,18 @@ export const useEditorAssistant = (): {postMessage: PostMessage, processMessage:
       const ytext = ydocs.secondaryDoc.getText('codemirror');
       const ytext = ydocs.secondaryDoc.getText('codemirror');
       ydocs.secondaryDoc.transact(() => {
       ydocs.secondaryDoc.transact(() => {
         pendingDetectedDiff.forEach((detectedDiff) => {
         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
 export const LlmEditorAssistantDiffSchema = z
   .object({
   .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
 // Type definitions
 export type LlmEditorAssistantMessage = z.infer<typeof LlmEditorAssistantMessageSchema>;
 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>;
 export type SseFinalized = z.infer<typeof SseFinalizedSchema>;
 
 
 // Type guard for SseDetectedDiff
 // 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',
               role: 'assistant',
               content: `You are an Editor Assistant for GROWI, a markdown wiki system.
               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.
               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:
               RESPONSE FORMAT:
               You must respond with a JSON object in the following format example:
               You must respond with a JSON object in the following format example:
               {
               {
                 "contents": [
                 "contents": [
                   { "message": "Your brief message about the upcoming change or proposal.\n\n" },
                   { "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" },
                   { "message": "Additional explanation if needed" },
-                  { "retain": 100 },
-                  { "delete": 15 },
-                  { "insert": "New text 2" },
+                  { "replace": "New text 2" },
                   ...more items if needed
                   ...more items if needed
                   { "message": "Your friendly message explaining what changes were made or suggested." }
                   { "message": "Your friendly message explaining what changes were made or suggested." }
                 ]
                 ]
@@ -166,12 +164,41 @@ export const postMessageToEditHandlersFactory: PostMessageHandlersFactory = (cro
               The array should contain:
               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.
               - [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 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.
               - [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.`,
               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',
               role: 'user',
               content: `Current markdown content:\n\`\`\`markdown\n${markdown}\n\`\`\`\n\nUser request: ${userMessage}`,
               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 => {
 const isDiffItem = (item: unknown): item is LlmEditorAssistantDiff => {
   return typeof item === 'object' && item !== null
   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 = {
 type Options = {
@@ -30,6 +31,7 @@ type Options = {
   diffDetectedCallback?: (detected: LlmEditorAssistantDiff) => void,
   diffDetectedCallback?: (detected: LlmEditorAssistantDiff) => void,
   dataFinalizedCallback?: (message: string | null, replacements: LlmEditorAssistantDiff[]) => void,
   dataFinalizedCallback?: (message: string | null, replacements: LlmEditorAssistantDiff[]) => void,
 }
 }
+
 /**
 /**
  * AI response stream processor for Editor Assisntant
  * AI response stream processor for Editor Assisntant
  * Extracts messages and diffs from JSON stream for editor
  * Extracts messages and diffs from JSON stream for editor
@@ -174,9 +176,10 @@ export class LlmResponseStreamProcessor {
    * Generate unique key for a diff
    * Generate unique key for a diff
    */
    */
   private getDiffKey(diff: LlmEditorAssistantDiff, index: number): string {
   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 '';
     return '';
   }
   }