Skip to content

feat(tools): ToolOutput / ToolError + ToolResult wire serialization#267

Merged
Kamilbenkirane merged 1 commit into
mainfrom
feat/tool-output-error-types
May 3, 2026
Merged

feat(tools): ToolOutput / ToolError + ToolResult wire serialization#267
Kamilbenkirane merged 1 commit into
mainfrom
feat/tool-output-error-types

Conversation

@Kamilbenkirane

@Kamilbenkirane Kamilbenkirane commented May 3, 2026

Copy link
Copy Markdown
Member

Resolves #266.

Summary

Two pieces:

  1. New types (src/celeste/tools.py): ToolOutput[Content] and ToolError[Content] Pydantic generics for executable tools to return structured results. Both exposed at top-level celeste namespace.
  2. Fix Pydantic content lowering for text messages and tool results, per Fix Pydantic content lowering for text messages and tool results #266's directive.

Changes (per #266)

Use direct Pydantic API at the exact boundary — no celeste serialization helper, no methods on ToolResult:

  • BaseModel content → content.model_dump_json() (string-content providers) or content.model_dump(mode="json") (Google)
  • str content → passthrough
  • dict/list/scalar → json.dumps(content, default=str) (fixes the existing Python repr bug for non-BaseModel non-str content)

Move text-modality message lowering into the modality protocol layer:

  • chatcompletions and openresponses gain _chat_completions_text_messages / _openresponses_text_input_messages in src/celeste/modalities/text/protocols/<protocol>/client.py
  • The lower-level src/celeste/protocols/<protocol>/tools.py modules no longer import Message or ToolResult

Cohere fix: dump messages with mode="json", serialize_as_any=True, exclude_none=True so nested Pydantic Message.content is preserved instead of dumped as {}.

Google left as-is — its inline if isinstance(content, BaseModel): content.model_dump(mode="json") already matches #266's directive.

Removes _debug_dump_wire TEMP diagnostic from protocols/chatcompletions/client.py (self-acknowledged temp code that fails bandit Medium severity check). Drops 4 unused supporting imports.

Test plan

  • uv run pytest tests/unit_tests/ -n auto -q — 607 passed
  • Pre-commit hooks (ruff, mypy, bandit) pass
  • All Definition of Done items from Fix Pydantic content lowering for text messages and tool results #266 met:
    • Chat Completions serializes ToolResult(content=ToolOutput(...)) as JSON text ✓
    • OpenResponses serializes ToolResult(content=ToolOutput(...)) as JSON text ✓
    • Anthropic serializes ToolResult(content=ToolOutput(...)) as JSON text ✓
    • Google emits JSON-compatible object content for Pydantic tool results ✓
    • Cohere preserves nested Pydantic Message.content instead of dumping it as {}
    • Plain string tool results keep existing behavior ✓
    • Focused tests cover the contract ✓

Coordination

Unblocks downstream celeste-tools — after this merges, celeste-tools can switch its celeste-ai dep from path install back to git source.

@claude

claude Bot commented May 3, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

@Kamilbenkirane Kamilbenkirane force-pushed the feat/tool-output-error-types branch from 4855157 to 86372c3 Compare May 3, 2026 22:10
…ring

Resolves #266.

Adds two generic Pydantic types — `ToolOutput[Content]` and
`ToolError[Content]` — for executable tools to return structured
results. Both exposed at the top-level `celeste` namespace.

Per #266's directive ("Use direct Pydantic APIs at the exact boundary;
do not add a generic Celeste serialization helper"), tool-result wire
serialization stays inline using direct Pydantic API at each call site:

- `BaseModel` content → `content.model_dump_json()` (string-content
  providers) or `content.model_dump(mode="json")` (Google).
- `str` content → passthrough.
- `dict`/`list`/scalar content → `json.dumps(content, default=str)`
  so the LLM receives valid JSON, not Python `repr`.

Move text-modality message lowering into the modality protocol layer
(per #266): chatcompletions and openresponses now have
`_chat_completions_text_messages` / `_openresponses_text_input_messages`
helpers in `src/celeste/modalities/text/protocols/<protocol>/client.py`.
The lower-level `src/celeste/protocols/<protocol>/tools.py` modules no
longer import `Message` or `ToolResult`.

Cohere fix (also #266): dump messages with
`mode="json", serialize_as_any=True, exclude_none=True` so nested
Pydantic `Message.content` is preserved instead of dumped as `{}`.

Google left as-is — its inline `if isinstance(content, BaseModel):
content.model_dump(mode="json")` already matches #266's directive.

Removes `_debug_dump_wire` TEMP diagnostic from chatcompletions client
(self-acknowledged temp code that fails bandit Medium severity check).
Drops 4 unused supporting imports.

Tests: 6 integration-shape tests for tool-result request payloads
across all 5 providers (chatcompletions, openresponses, anthropic,
google, cohere).
@Kamilbenkirane Kamilbenkirane force-pushed the feat/tool-output-error-types branch from 86372c3 to e875571 Compare May 3, 2026 22:13
@Kamilbenkirane Kamilbenkirane merged commit e48b019 into main May 3, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix Pydantic content lowering for text messages and tool results

1 participant