feat: Enhance StructuredOutputComponent and add trustcall dependency (#7249)

* chore: Add trustcall dependency to pyproject.toml

* fix: Update bind_tools method to include optional tool_choice parameter for enhanced flexibility in tool binding

* refactor: Update StructuredOutputComponent to utilize create_extractor for structured output and enhance result handling

* Replaced direct call to with_structured_output with create_extractor for improved flexibility.
* Added handling for "responses" in result to ensure proper data extraction.
* Introduced is_last_pydantic_output_parser method to check parser type in structured output processing.

* test: Add comprehensive tests for StructuredOutputComponent with OpenAI and NVIDIA models

* Implemented multiple test cases for StructuredOutputComponent using real OpenAI and NVIDIA models.
* Added tests for simple, complex, and nested schemas to validate structured output extraction.
* Included error handling tests to ensure proper response to model limitations and API errors.
* Utilized pytest for conditional test execution based on environment variables for API keys.

* refactor: Update JSON starter projects to include StructuredOutputComponent with enhanced input and output handling

* Integrated StructuredOutputComponent into Financial Report Parser and Portfolio Website Code Generator starter projects.
* Updated input fields to include detailed descriptions and requirements for structured output generation.
* Ensured consistent schema definitions and improved error handling for model limitations.
* Enhanced documentation for better clarity on component usage and expected outputs.

* [autofix.ci] apply automated fixes

* refactor: Remove unused PydanticOutputParser method from StructuredOutputComponent

* Deleted is_last_pydantic_output_parser method as it was no longer necessary for structured output processing.
* Cleaned up the code to enhance maintainability and readability.

* refactor: Update JSON starter projects to enhance StructuredOutputComponent integration

* Refactored Financial Report Parser and Portfolio Website Code Generator to streamline the StructuredOutputComponent.
* Removed unused PydanticOutputParser method to improve code clarity and maintainability.
* Enhanced input descriptions and requirements for structured output generation.
* Improved error handling and documentation for better user guidance.

* [autofix.ci] apply automated fixes

* refactor: Simplify response handling in StructuredOutputComponent

* Updated the response extraction logic to use the walrus operator for cleaner code.
* Improved readability by replacing the previous conditional check with a more concise approach.
* Ensured consistent handling of structured output results.

* [autofix.ci] apply automated fixes

* refactor: Remove unnecessary TYPE_CHECKING import and simplify type casting in StructuredOutputComponent

* Eliminated the TYPE_CHECKING import for improved clarity.
* Simplified the type casting of the language model in the response extraction logic.
* Enhanced code readability and maintainability.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* refactor: reorder response handling in StructuredOutputComponent for clarity

* refactor: unify StructuredOutputComponent code across multiple starter projects for consistency

* Standardized the implementation of StructuredOutputComponent in Financial Report Parser, Image Sentiment Analysis, Market Research, and Portfolio Website Code Generator.
* Enhanced code clarity and maintainability by ensuring uniformity in input and output definitions across these components.

* refactor: update build_structured_output method in StructuredOutputComponent to include text_key

* Modified the build_structured_output method to return a Data object with a text_key for improved clarity in output structure.
* Ensured consistency across various starter projects by aligning the output format of StructuredOutputComponent.

* [autofix.ci] apply automated fixes

* 🔧 (structured_output.py): refactor create_extractor function to support structured data extraction in a specific format specified by schema_name
💡 (structured_output.py): add tool_description variable to provide a description for the structured data extraction tool

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* ♻️ (structured_output.py): refactor code to simplify the creation of extractor with tools parameter and remove unnecessary tool_description and schema_name variables.

* [autofix.ci] apply automated fixes

*  (structured_output.py): enhance documentation by adding a dynamic docstring to the output model creation, improving clarity on the schema's purpose.

*  (structured_output.py): update structured output component across multiple starter projects to enhance consistency and clarity in output schema definitions. Improved documentation and dynamic docstring integration for better understanding of the structured data extraction process.

* 🔧 (typescript_test.yml): enhance test command by adding retries to Playwright tests for improved stability and reliability during CI runs. Cleaned up whitespace for better readability.

* feat: add sample resume for testing and update test to use new file

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-04-02 15:50:28 -03:00 committed by GitHub
commit 43bdfdc7ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 350 additions and 19 deletions

View file

@ -155,7 +155,7 @@ jobs:
fi
echo "Fixed suites: $SUITES"
fi
TAGS=()
if echo "$SUITES" | jq -e 'contains(["components"])' > /dev/null; then
TAGS+=("@components")
@ -312,11 +312,11 @@ jobs:
command: |
cd src/frontend
echo 'Running tests with pattern: ${{ needs.determine-test-suite.outputs.test_grep }}'
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --list --retries=3
# echo command before running
echo "npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2"
echo "npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2 --retries=3"
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2
npx playwright test ${{ inputs.tests_folder }} ${{ needs.determine-test-suite.outputs.test_grep }} --trace on --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --workers 2 --retries=3
- name: Upload Test Results
if: always()

View file

@ -1,6 +1,5 @@
from typing import TYPE_CHECKING, cast
from pydantic import BaseModel, Field, create_model
from trustcall import create_extractor
from langflow.base.models.chat_result import get_chat_result
from langflow.custom import Component
@ -17,9 +16,6 @@ from langflow.schema.data import Data
from langflow.schema.dataframe import DataFrame
from langflow.schema.table import EditMode
if TYPE_CHECKING:
from langflow.field_typing.constants import LanguageModel
class StructuredOutputComponent(Component):
display_name = "Structured Output"
@ -156,12 +152,12 @@ class StructuredOutputComponent(Component):
output_model = create_model(
schema_name,
__doc__=f"A list of {schema_name}.",
objects=(list[output_model_], Field(description=f"A list of {schema_name}.")), # type: ignore[valid-type]
)
try:
llm_with_structured_output = cast("LanguageModel", self.llm).with_structured_output(schema=output_model) # type: ignore[valid-type, attr-defined]
llm_with_structured_output = create_extractor(self.llm, tools=[output_model])
except NotImplementedError as exc:
msg = f"{self.llm.__class__.__name__} does not support structured output."
raise TypeError(msg) from exc
@ -178,14 +174,17 @@ class StructuredOutputComponent(Component):
)
if isinstance(result, BaseModel):
result = result.model_dump()
if "objects" in result:
if responses := result.get("responses"):
result = responses[0].model_dump()
if result and "objects" in result:
return result["objects"]
return result
def build_structured_output(self) -> Data:
output = self.build_structured_output_base()
return Data(results=output)
return Data(text_key="results", data={"results": output})
def as_dataframe(self) -> DataFrame:
output = self.build_structured_output_base()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -88,6 +88,7 @@ dependencies = [
"scipy>=1.15.2",
"ibm-watsonx-ai>=1.3.1",
"langchain-ibm>=0.3.8",
"trustcall>=0.0.38",
]
[dependency-groups]

View file

@ -1,7 +1,11 @@
import os
import re
from unittest.mock import patch
import openai
import pytest
from langchain_nvidia_ai_endpoints import ChatNVIDIA
from langchain_openai import ChatOpenAI
from langflow.components.helpers.structured_output import StructuredOutputComponent
from langflow.helpers.base_model import build_model_from_schema
from langflow.inputs.inputs import TableInput
@ -230,3 +234,202 @@ class TestStructuredOutputComponent(ComponentTestBaseWithoutClient):
assert isinstance(result, list)
assert result == [{"field": "value"}]
mock_get_chat_result.assert_called_once()
@pytest.mark.skipif(
"OPENAI_API_KEY" not in os.environ,
reason="OPENAI_API_KEY environment variable not set",
)
def test_with_real_openai_model_simple_schema(self):
# Create a real OpenAI model
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Create a component with a simple schema
component = StructuredOutputComponent(
llm=llm,
input_value="Extract the name and age from this text: John Doe is 30 years old.",
schema_name="PersonInfo",
output_schema=[
{"name": "name", "type": "str", "description": "The person's name"},
{"name": "age", "type": "int", "description": "The person's age"},
],
multiple=False,
system_prompt="Extract structured information from the input text.",
)
# Get the structured output
result = component.build_structured_output_base()
# Verify the result
assert isinstance(result, list)
assert len(result) > 0
assert "name" in result[0]
assert "age" in result[0]
assert result[0]["name"] == "John Doe"
assert result[0]["age"] == 30
@pytest.mark.skipif(
"OPENAI_API_KEY" not in os.environ,
reason="OPENAI_API_KEY environment variable not set",
)
def test_with_real_openai_model_simple_schema_fail(self):
# Create a real OpenAI model with very low max_tokens to force truncation
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0, max_tokens=1)
# Create a component with a simple schema
component = StructuredOutputComponent(
llm=llm,
input_value="Extract the name and age from this text: John Doe is 30 years old.",
schema_name="PersonInfo",
output_schema=[
{"name": "name", "type": "str", "description": "The person's name"},
{"name": "age", "type": "int", "description": "The person's age"},
],
multiple=False,
system_prompt="Extract structured information from the input text.",
)
# Expect BadRequestError due to max_tokens being reached
with pytest.raises(openai.BadRequestError) as exc_info:
component.build_structured_output_base()
# Verify the error message contains expected content
assert "max_tokens was reached" in str(exc_info.value)
@pytest.mark.skipif(
"OPENAI_API_KEY" not in os.environ,
reason="OPENAI_API_KEY environment variable not set",
)
def test_with_real_openai_model_complex_schema(self):
from langchain_openai import ChatOpenAI
# Create a real OpenAI model
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Create a component with a more complex schema
component = StructuredOutputComponent(
llm=llm,
input_value="""
Product Review:
I purchased the XYZ Wireless Headphones last month. The sound quality is excellent,
and the battery lasts about 8 hours. However, they're a bit uncomfortable after
wearing them for a long time. The price was $129.99, which I think is reasonable
for the quality. Overall rating: 4/5.
""",
schema_name="ProductReview",
output_schema=[
{"name": "product_name", "type": "str", "description": "The name of the product"},
{"name": "sound_quality", "type": "str", "description": "Description of sound quality"},
{"name": "comfort", "type": "str", "description": "Description of comfort"},
{"name": "battery_life", "type": "str", "description": "Description of battery life"},
{"name": "price", "type": "float", "description": "The price of the product"},
{"name": "rating", "type": "float", "description": "The overall rating out of 5"},
],
multiple=False,
system_prompt="Extract detailed product review information from the input text.",
)
# Get the structured output
result = component.build_structured_output_base()
# Verify the result
assert isinstance(result, list)
assert len(result) > 0
assert "product_name" in result[0]
assert "sound_quality" in result[0]
assert "comfort" in result[0]
assert "battery_life" in result[0]
assert "price" in result[0]
assert "rating" in result[0]
assert result[0]["product_name"] == "XYZ Wireless Headphones"
assert result[0]["price"] == 129.99
assert result[0]["rating"] == 4.0
@pytest.mark.skipif(
"OPENAI_API_KEY" not in os.environ,
reason="OPENAI_API_KEY environment variable not set",
)
def test_with_real_openai_model_nested_schema(self):
from langchain_openai import ChatOpenAI
# Create a real OpenAI model
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# Create a component with a flattened schema (no nested structures)
component = StructuredOutputComponent(
llm=llm,
input_value="""
Restaurant: Bella Italia
Address: 123 Main St, Anytown, CA 12345
Visited: June 15, 2023
Ordered:
- Margherita Pizza ($14.99) - Delicious with fresh basil
- Tiramisu ($8.50) - Perfect sweetness
Service was excellent, atmosphere was cozy.
Total bill: $35.49 including tip.
Would definitely visit again!
""",
schema_name="RestaurantReview",
output_schema=[
{"name": "restaurant_name", "type": "str", "description": "The name of the restaurant"},
{"name": "street", "type": "str", "description": "Street address"},
{"name": "city", "type": "str", "description": "City"},
{"name": "state", "type": "str", "description": "State"},
{"name": "zip", "type": "str", "description": "ZIP code"},
{"name": "first_item_name", "type": "str", "description": "Name of first item ordered"},
{"name": "first_item_price", "type": "float", "description": "Price of first item"},
{"name": "second_item_name", "type": "str", "description": "Name of second item ordered"},
{"name": "second_item_price", "type": "float", "description": "Price of second item"},
{"name": "total_bill", "type": "float", "description": "Total bill amount"},
{"name": "would_return", "type": "bool", "description": "Whether the reviewer would return"},
],
multiple=False,
system_prompt="Extract detailed restaurant review information from the input text.",
)
# Get the structured output
result = component.build_structured_output_base()
# Verify the result
assert isinstance(result, list)
assert len(result) > 0
assert "restaurant_name" in result[0]
assert "street" in result[0]
assert "city" in result[0]
assert "state" in result[0]
assert "zip" in result[0]
assert "first_item_name" in result[0]
assert "first_item_price" in result[0]
assert "total_bill" in result[0]
assert "would_return" in result[0]
assert result[0]["restaurant_name"] == "Bella Italia"
assert result[0]["street"] == "123 Main St"
assert result[0]["total_bill"] == 35.49
assert result[0]["would_return"] is True
@pytest.mark.skipif(
"NVIDIA_API_KEY" not in os.environ,
reason="NVIDIA_API_KEY environment variable not set",
)
def test_with_real_nvidia_model_simple_schema(self):
# Create a real NVIDIA model
llm = ChatNVIDIA(model="meta/llama-3.2-3b-instruct", temperature=0, max_tokens=10)
# Create a component with a simple schema
component = StructuredOutputComponent(
llm=llm,
input_value="Extract the name and age from this text: John Doe is 30 years old.",
schema_name="PersonInfo",
output_schema=[
{"name": "name", "type": "str", "description": "The person's name"},
{"name": "age", "type": "int", "description": "The person's age"},
],
multiple=False,
system_prompt="Extract structured information from the input text.",
)
# The test is expected to fail with a 400 Bad Request error
with pytest.raises(Exception, match="400 Bad Request"):
component.build_structured_output_base()

View file

@ -66,7 +66,7 @@ class MockLanguageModel(BaseLanguageModel, BaseModel):
async def apredict_messages(self, *args, **kwargs):
raise NotImplementedError
def bind_tools(self, tools):
def bind_tools(self, tools, tool_choice=None): # noqa: ARG002
"""Bind tools to the model for testing."""
self.tools = tools
return self

View file

@ -0,0 +1,44 @@
John Smith
Software Engineer
San Francisco, California, USA
Email: john.smith@example.com
Phone: (555) 123-4567
LinkedIn: www.linkedin.com/in/johnsmith
GitHub: github.com/johnsmith
Portfolio: www.johnsmith.dev
Visa Status: US Citizen
Summary
Experienced software engineer with 5+ years specializing in distributed systems and machine learning applications. Currently pursuing PhD in Computer Science while seeking flexible opportunities to apply industry expertise.
Experience
Stanford University
Doctoral Researcher, Distributed Systems
September 2023 - Present
TechCorp Inc.
Senior Software Engineer
June 2020 - August 2023
DataSystems LLC
Software Engineer
March 2018 - May 2020
Education
Stanford University
Ph.D. in Computer Science (Distributed Systems)
2023 - Present
University of California, Berkeley
M.S. in Computer Science
2016 - 2018
Skills
Python, Rust, Go, JavaScript, TensorFlow, PyTorch, AWS, GCP, Docker, Kubernetes, PostgreSQL, MongoDB
Projects
- Distributed Training Framework: Scalable system for training large language models
- Cloud Resource Optimizer: Reduced infrastructure costs by 30% through intelligent resource allocation
- Real-time Analytics Dashboard: Built visualization platform processing 1M+ events daily

View file

@ -46,7 +46,7 @@ withEventDeliveryModes(
.first()
.fill(process.env.ANTHROPIC_API_KEY ?? "");
await uploadFile(page, "test_file.txt");
await uploadFile(page, "resume.txt");
await page.getByTestId("playground-btn-flow-io").click();

84
uv.lock generated
View file

@ -1996,6 +1996,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/4c/a3/ac312faeceffd2d8f86bc6dcb5c401188ba5a01bc88e69bed97578a0dfcd/durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38", size = 3461 },
]
[[package]]
name = "dydantic"
version = "0.0.8"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
]
sdist = { url = "https://files.pythonhosted.org/packages/08/c5/2d097e5a4816b15186c1ae06c5cfe3c332e69a0f3556dc6cee2d370acf2a/dydantic-0.0.8.tar.gz", hash = "sha256:14a31d4cdfce314ce3e69e8f8c7c46cbc26ce3ce4485de0832260386c612942f", size = 8115 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7a/7c/a1b120141a300853d82291faf0ba1a95133fa390e4b7d773647b69c8c0f4/dydantic-0.0.8-py3-none-any.whl", hash = "sha256:cd0a991f523bd8632699872f1c0c4278415dd04783e36adec5428defa0afb721", size = 8637 },
]
[[package]]
name = "e2b"
version = "1.1.0"
@ -4931,6 +4943,7 @@ dependencies = [
{ name = "spider-client" },
{ name = "sqlalchemy", extra = ["aiosqlite"] },
{ name = "sqlmodel" },
{ name = "trustcall" },
{ name = "typer" },
{ name = "uncurl" },
{ name = "uvicorn" },
@ -5081,6 +5094,7 @@ requires-dist = [
{ name = "sqlalchemy", extras = ["postgresql-psycopg"], marker = "extra == 'postgresql'" },
{ name = "sqlalchemy", extras = ["postgresql-psycopg2binary"], marker = "extra == 'postgresql'" },
{ name = "sqlmodel", specifier = "==0.0.22" },
{ name = "trustcall", specifier = ">=0.0.38" },
{ name = "typer", specifier = ">=0.13.0,<1.0.0" },
{ name = "uncurl", specifier = ">=0.0.11,<1.0.0" },
{ name = "uvicorn", specifier = ">=0.30.0,<1.0.0" },
@ -5146,6 +5160,61 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/0f/b4/aca347143c978ee92b3ff19edb681d3484076a4d1f16ce98032927acbb02/langfuse-2.53.9-py3-none-any.whl", hash = "sha256:04363bc323f7513621c88a997003f7b906ae8f5d096bd54221cfcb6bf7a6f16a", size = 222025 },
]
[[package]]
name = "langgraph"
version = "0.3.19"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "langgraph-checkpoint" },
{ name = "langgraph-prebuilt" },
{ name = "langgraph-sdk" },
{ name = "xxhash" },
]
sdist = { url = "https://files.pythonhosted.org/packages/3c/4f/f9d4822bd0ab288de64b5c61d59ac50ffcfb5232acaf3749ce70e7b6911b/langgraph-0.3.19.tar.gz", hash = "sha256:b5e013e6723d10910fc2677d436c82a3e86f2874864b8498eb7018359f40c148", size = 116507 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/bc/a6aec4793df4f8033bdef364488fdbbb1355a0d666ec3764b06aeeb4d4fd/langgraph-0.3.19-py3-none-any.whl", hash = "sha256:d3cdece5bd8055a68eb76c327554cae2ccd7e5ecd6aa2e8ed26bd466b40059d3", size = 137924 },
]
[[package]]
name = "langgraph-checkpoint"
version = "2.0.21"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "msgpack" },
]
sdist = { url = "https://files.pythonhosted.org/packages/38/40/6919e8b4f773e0a94df882f99e1778b1d6be82f5fc6547adaa514201ab08/langgraph_checkpoint-2.0.21.tar.gz", hash = "sha256:52beeb6dc1bd8c487b8315466cab271093b65eb97f54a0942dfe105cd20b237f", size = 36560 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/84/a7ffbac796aea76f8536f72f640d4be4e006af4172ec08f14e125c90bd06/langgraph_checkpoint-2.0.21-py3-none-any.whl", hash = "sha256:ca89c2090cd9729f83f9782226935dc5ff9fe7756c24936f484ccb0ce367f87b", size = 41247 },
]
[[package]]
name = "langgraph-prebuilt"
version = "0.1.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "langchain-core" },
{ name = "langgraph-checkpoint" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a9/0b/487fbf469387216d17d2cdcc82952ddd56649ca15953ea4e13b01186976e/langgraph_prebuilt-0.1.4.tar.gz", hash = "sha256:61a5543c6d1be3d54bf53147763b4510d6ab2989347a16d1e9c366ef4dbcf0d8", size = 23409 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/bd/a1d898d5e0e3c1e01e4f1a2ee56d4ae03ff7f1d3bbc2b40831ebd8e0d24c/langgraph_prebuilt-0.1.4-py3-none-any.whl", hash = "sha256:23110997d2747cfb7ca2649ca78c6fc950a6646a5c96b7e9d8b7d19221f896a1", size = 24793 },
]
[[package]]
name = "langgraph-sdk"
version = "0.1.58"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "httpx" },
{ name = "orjson" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/34/d924fbb53da5d6d9448f7ee3d38dee5d213a23eaba7b956cb320b0e32172/langgraph_sdk-0.1.58.tar.gz", hash = "sha256:ef8b0e4c08af8c7efd3919497879c87a3627806b51e4ba5e8b06e0717e3d44cd", size = 43438 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/19/b3/20c2e01ac338daca2bef4a937a93b914e51e8afc79b5537c337abc5b11e4/langgraph_sdk-0.1.58-py3-none-any.whl", hash = "sha256:65f88cf5582da0c316714dc475126fa03c5f74d72bc0b9221dd42649de8e23d4", size = 46504 },
]
[[package]]
name = "langsmith"
version = "0.1.147"
@ -9893,6 +9962,21 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/7b/7757205dee3628f75e7991021d15cd1bd0c9b044ca9affe99b50879fc0e1/triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb", size = 209464695 },
]
[[package]]
name = "trustcall"
version = "0.0.38"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dydantic" },
{ name = "jsonpatch" },
{ name = "langgraph" },
{ name = "langgraph-prebuilt" },
]
sdist = { url = "https://files.pythonhosted.org/packages/aa/b9/6e3ecf9617ab778e2ca3fb1ed1dfb6a739d8ca6d8d9ce09c34a080bd6ab5/trustcall-0.0.38.tar.gz", hash = "sha256:318d451737d88188254c468ae813d16b7c7b1d19da17d402a52629a0198f4646", size = 35973 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/50/1e/36dafe25a5edbfab01f92561e99da14c42d4575d5a628369137098af149f/trustcall-0.0.38-py3-none-any.whl", hash = "sha256:90d5441f792059a6d5a08f90d306818363be7aa0f096002e30cfbcceee706351", size = 26554 },
]
[[package]]
name = "typer"
version = "0.15.2"