Skip to content

Conversation

@sydney-runkle
Copy link
Collaborator

@sydney-runkle sydney-runkle commented Dec 1, 2025

This PR introduces a few significant changes

  • We introduce a new MCPToolArtifact type that contains structured_output from the MCP tool, if available
  • We now populate ToolMessage content with LangChain's standard content blocks. We do a best effort of the MCP content blocks onto these LC content blocks.
    • We used to pop all non-text content blocks off of content and pump them directly into the artifact. This content is intended to be in the chat history (images, files), so it makes more sense for it to be in content.
    • We used to do some parsing / simplification of text content blocks (single text content block converted to just a string, a list of dicts converted to a list of strings), we no longer do this, and instead just use the standard text content block. See this change clearly in the updated tests.
    • currently, audio content blocks aren't supported in tool messages (raise not implemented error)

Closes #283 (adding structured content support)
Closes #41 (supporting images + file content)
Fixes #295 (tool message contents should be serializable now)

@sydney-runkle sydney-runkle changed the title feat: structured output support feat: structured output support, using LC standard content blocks Dec 1, 2025
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

linting fixes

Comment on lines 101 to 105
if isinstance(content, ResourceLink):
block: ToolMessageContentBlock = {"type": "file", "url": str(content.uri)}
if content.mimeType:
block["mime_type"] = content.mimeType
return block
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to use a file for this?

Comment on lines -87 to -100
# Otherwise, convert from CallToolResult
text_contents: list[TextContent] = []
non_text_contents = []
for content in call_tool_result.content:
if isinstance(content, TextContent):
text_contents.append(content)
else:
non_text_contents.append(content)

tool_content: str | list[str] = [content.text for content in text_contents]
if not text_contents:
tool_content = ""
elif len(text_contents) == 1:
tool_content = tool_content[0]
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the big question, can we remove this funky str concat logic.

I think it'd be more consistent if we always populated tool messages w/ standard content blocks, and we're not on 1.0 yet, so we do have some room to make breaking changes.

On the other hand, the value here isn't that high, so I'm ok w/ leaving as is if desired to avoid pain for users.

cc @eyurtsev

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect this would break stuff? Would .text / .content continue working on tool messages as before? (with change being only on content_blocks?)

Copy link
Collaborator Author

@sydney-runkle sydney-runkle Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would break .content. So it's probably not worth the change.

BUT we are on 0.0.X here, so we have the ability to make this change if we want.

Copy link
Collaborator

@eyurtsev eyurtsev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, let's verify that content blocks work for a few different model providers (openai, anthropic) + chat history and that the trace looks OK on langsmith?

@sydney-runkle sydney-runkle merged commit 93b0283 into main Dec 2, 2025
6 checks passed
@sydney-runkle sydney-runkle deleted the sr/structured-output branch December 2, 2025 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants