[go: up one dir, main page]

Skip to content

Duo workflow responses do not comply with the Duo-UI data contract

Problem to solve

The response from api/v4/ai/duo_workflows does not match the format expected by Duo-UI components which seems to be the root cause behind the typebug GFM rendering is broken in messages (duo-ui#105 - closed).

The problem is that Duo-UI references several props on the message object which aren't defined in the API response. One such example is message.chunks which Duo-UI relies on to determine if a message is streaming or not.

The data

From the API

Response from api/v4/ai/duo_workflows. Does not contain or mention chunks or chunkId / chunk_id.

Click to expand
{
  requestID: "",
  newCheckpoint: {
    status: "INPUT_REQUIRED",
    checkpoint: {
      channel_values: {
        ui_chat_log: [
          {
            status: "success",
            content: "Hi",
            timestamp: "2025-10-15T12:19:50.449731+00:00",
            tool_info: null,
            message_type: "user",
            correlation_id: null,
            message_sub_type: null,
            additional_context: [
              {
                id: "",
                content: {
                  id: 622,
                  iid: 3,
                  description: "Hello, World!\n\nIt's nice to meet you. First we should:\n- [ ] Meet and greet\n- [ ] Get to know each other\n- [ ] Say our goodbyes",
                  title: "hello.txt 2",
                  time_estimate: 0,
                  total_time_spent: 0,
                  human_time_estimate: null,
                  human_total_time_spent: null,
                  state: "opened",
                  milestone_id: null,
                  updated_by_id: null,
                  created_at: "2025-09-25T11:38:37Z",
                  updated_at: "2025-10-03T13:04:37Z",
                  milestone: null,
                  labels: [],
                  lock_version: 0,
                  author_id: 1,
                  confidential: false,
                  discussion_locked: null,
                  assignees: [],
                  due_date: null,
                  project_id: 1000000,
                  moved_to_id: null,
                  duplicated_to_id: null,
                  web_url: "/gitlab-duo/test/-/issues/3",
                  current_user: {
                    can_create_note: true,
                    can_create_confidential_note: true,
                    can_update: true,
                    can_set_issue_metadata: true,
                    can_award_emoji: true
                  },
                  create_note_path: "/gitlab-duo/test/notes?target_id=622&target_type=issue",
                  preview_note_path: "/gitlab-duo/test/-/preview_markdown?target_id=3&target_type=Issue",
                  is_project_archived: false,
                  issue_email_participants: [],
                  type: "ISSUE",
                  weight: null,
                  issue_comments: []
                },
                category: "repository",
                metadata: {},
                type: "AdditionalContext"
              }
            ]
          },
          {
            status: "success",
            content: "Hello! I can see you're working on the Test project and there's an open issue (#3) titled \"hello.txt 2\" with a friendly greeting and some checklist items.\n\nHow can I help you today? Are you looking to work on this issue, or is there something else you'd like to do with your project?",
            timestamp: "2025-10-15T12:19:54.308217+00:00",
            tool_info: null,
            message_type: "agent",
            correlation_id: null,
            message_sub_type: null,
            additional_context: null
          },
          {
            message_type: "user",
            message_sub_type: null,
            content: "hi",
            timestamp: "2025-10-15T12:21:05.662013+00:00",
            status: "success",
            correlation_id: null,
            tool_info: null,
            additional_context: [
              {
                category: "repository",
                id: "",
                content: {
                  id: 622,
                  iid: 3,
                  description: "Hello, World!\n\nIt's nice to meet you. First we should:\n- [ ] Meet and greet\n- [ ] Get to know each other\n- [ ] Say our goodbyes",
                  title: "hello.txt 2",
                  time_estimate: 0,
                  total_time_spent: 0,
                  human_time_estimate: null,
                  human_total_time_spent: null,
                  state: "opened",
                  milestone_id: null,
                  updated_by_id: null,
                  created_at: "2025-09-25T11:38:37Z",
                  updated_at: "2025-10-03T13:04:37Z",
                  milestone: null,
                  labels: [],
                  lock_version: 0,
                  author_id: 1,
                  confidential: false,
                  discussion_locked: null,
                  assignees: [],
                  due_date: null,
                  project_id: 1000000,
                  moved_to_id: null,
                  duplicated_to_id: null,
                  web_url: "/gitlab-duo/test/-/issues/3",
                  current_user: {
                    can_create_note: true,
                    can_create_confidential_note: true,
                    can_update: true,
                    can_set_issue_metadata: true,
                    can_award_emoji: true
                  },
                  create_note_path: "/gitlab-duo/test/notes?target_id=622&target_type=issue",
                  preview_note_path: "/gitlab-duo/test/-/preview_markdown?target_id=3&target_type=Issue",
                  is_project_archived: false,
                  issue_email_participants: [],
                  type: "ISSUE",
                  weight: null,
                  issue_comments: []
                },
                metadata: {},
                type: "AdditionalContext"
              }
            ]
          },
          {
            message_type: "agent",
            message_sub_type: null,
            content: "Hi there! 👋 \n\nWhat would you like to work on today? I can help you with coding, reviewing issues, analyzing your project, or anything else related to your GitLab project.",
            timestamp: "2025-10-15T12:21:08.947510+00:00",
            status: "success",
            correlation_id: null,
            tool_info: null,
            additional_context: null
          }
        ],
        plan: {
          steps: []
        }
      }
    },
    goal: "hi",
    errors: []
  }
}

Duo-UI messages

This is a sample of message obj inside of the messages array that's passed from the monolith to Duo-UI. These examples are of the same message taken at different time snapshots. None of the examples contain or mention chunks or chunkId / chunk_id.

Click to expand
{
    "status": null,
    "correlation_id": null,
    "message_type": "agent",
    "message_sub_type": null,
    "timestamp": "2025-10-14T12:22:07.971538+00:00",
    "content": "Here",
    "tool_info": null,
    "additional_context": null,
    "requestId": "154-1",
    "role": "assistant"
}

Still streaming…

{
    "status": null,
    "correlation_id": null,
    "message_type": "agent",
    "message_sub_type": null,
    "timestamp": "2025-10-14T12:22:07.971538+00:00",
    "content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql",
    "tool_info": null,
    "additional_context": null,
    "requestId": "154-1",
    "role": "assistant"
}

Finished streaming

{
    "message_type": "agent",
    "message_sub_type": null,
    "content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql\ndisplay: list\nfields: title\nlimit: 25\nquery: state = closed\n```",
    "timestamp": "2025-10-14T12:22:08.690550+00:00",
    "status": "success",
    "correlation_id": null,
    "tool_info": null,
    "additional_context": null,
    "requestId": "154-1",
    "role": "assistant"
}

Expected output

I don't have an real example of what a message used to look like, but I expect it was something like this based on what Duo-UI attempts to reference.

{
    "message_type": "agent",
    "message_sub_type": null,
    "content": "Here are the GLQL queries you requested:\n\n```glql\ndisplay: list\nfields: title\nlimit: 10\nquery: state = opened\n```\n\n```glql\ndisplay: list\nfields: title\nlimit: 25\nquery: state = closed\n```",
    "timestamp": "2025-10-14T12:22:08.690550+00:00",
    "status": "success",
    "correlation_id": null,
    "tool_info": null,
    "additional_context": null,
    "requestId": "154-1",
    "role": "assistant"
    // MISSING ATTRS
    "chunks": [],
    "chunk_id: "123"
}

Further details

I don't know what the exact cause is related to the API response being parsed wrongly in the monolith? I am not a domain expert and I have not yet fully investigated this.

Open questions

Are there any integration tests to verify the data contract between model responses and Duo-UI?

Steps to reproduce

To view the API response:

  1. In .com or GDK open up Duo chat and say hi
  2. In your browser's network tab look for the socket response from api/v4/ai/duo_workflows.
  3. View the binary message - you may need to convert to UTF-8 and pretty print it.

To view the messages in the monolith frontend.

  1. In .com or GDK open up Duo chat and say hi
  2. Either use Vue dev tools to inspect the messages array OR paste this code to output the array to your browser console:
// Inside ee/app/assets/javascripts/ai/duo_agentic_chat/components/duo_agentic_chat.vue 
// Within the watch: {} object
messages(msgs) {
  console.log('messages: ', msgs);
},

Proposal

🚧

Edited by 🤖 GitLab Bot 🤖