feat(coze): complete file upload, reasoning stream, and follow-ups#2
Merged
Conversation
Round out the Coze integration to match the Coze developer docs feature
set, on top of existing streaming/tool-call/HITL/workflow-interrupt support:
- File upload: AsyncCozeClient.upload_file() -> POST /v1/files/upload
(multipart; _request drops the JSON Content-Type so httpx writes the
boundary). New file_object/image_object/text_object helpers and
object_string message encoding in _message_to_coze let messages carry
file/image references via HumanMessage(additional_kwargs={"objects": [...]}).
- Reasoning (thinking) stream: reasoning_content deltas are emitted as
separate AIMessageChunks tagged additional_kwargs={"reasoning": True} and
aggregated onto the final AIMessage, so UI adapters can render a thinking
channel distinctly from the answer.
- Follow-up suggestions: conversation.message.completed with type=="follow_up"
(and follow_up items from chat/message/list on the polling path) are
collected into AIMessage.additional_kwargs["follow_ups"] and
response_metadata. CozeAgentNode.suggestions_key is opt-in (default None) so
strict state schemas are never broken.
Adds MockTransport contract tests, docs, and a changelog entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…face Audit against the Coze developer docs (v3 chat SSE + v1 conversation/file/bot APIs) surfaced one correctness bug and several missing endpoints on top of the previous commit: - Fix: conversation.message.delta/.completed multiplex "answer" content with "verbose" (multi-agent jump info), "function_call", "tool_output", and "knowledge_recall" payloads under the same event name. All three streaming call sites (CozeAgentNode, CozeChatModel.agenerate, CozeChatModel.astream) only checked the event name, so non-answer payloads were concatenated into the visible answer. Added _is_answer_delta() gating on data.type in (None, "answer"); the non-streaming poll path already filtered correctly via _split_answer_and_follow_ups. - Add conversation-level endpoints: conversation_retrieve, conversation_message_create (append without triggering a chat run), conversation_message_list (cursor pagination: before_id/after_id/limit, has_more), conversation_message_retrieve. - Add file_retrieve (upload status/metadata) and bot_retrieve (/v1/bot/get_online_info). - Extract token usage: conversation.chat.completed's usage (token_count/input_count/output_count), and the polled chat object's usage field, now populate AIMessage.usage on both streaming and non-streaming paths. Adds MockTransport contract tests for the verbose-filtering fix, the new endpoints, and usage extraction (136 passed, 2 skipped across the full vendor suite). Updates docs/integrations-coze.md and CHANGELOG.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rate The tool-submission continuation branch and the main streaming branch both bound a local named follow_ups within the same function scope, so mypy flagged the second binding as redefining the first with an incompatible inferred type. Renamed the tool-branch variable to tool_follow_ups. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xtraction The CI quality gate runs `coverage run --branch -m unittest discover` over the whole test tree and fails under 80% total branch coverage; the new Coze endpoints/usage code pushed coze.py's local coverage down enough to trip it (79% on CI, borderline 80% reproduced locally). - Add a MockTransport-backed test for CozeAgentNode(stream=False): covers the chat()/chat_retrieve() polling loop, usage extraction from the polled chat object, and follow-up parsing via chat_messages() - previously entirely untested. - Extend the existing streaming CozeAgentNode test with a conversation.chat.completed event carrying usage, covering that branch for the streaming path too (previously only CozeChatModel had a usage test). Local coverage run (coverage run --branch -m unittest discover -s tests, matching CI's `quality` job) is now 80.42% total, up from the borderline 79-80% before this commit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Rounds out the Coze integration to match the Coze developer docs' full chat/workflow feature set, building on the existing streaming, tool-call, HITL, and workflow-interrupt support. Two commits:
1. Complete feature set (file upload, reasoning stream, follow-ups):
AsyncCozeClient.upload_file()→POST /v1/files/upload(multipart). Newfile_object()/image_object()/text_object()helpers andobject_stringmessage encoding in_message_to_coze, so messages can carry file/image references viaHumanMessage(additional_kwargs={"objects": [...]}).reasoning_contentdeltas are emitted as separateAIMessageChunks taggedadditional_kwargs={"reasoning": True}, and aggregated onto the finalAIMessage.conversation.message.completedevents withtype=="follow_up"are collected intoAIMessage.additional_kwargs["follow_ups"]/response_metadata.CozeAgentNode.suggestions_keyis opt-in (defaults toNone) so it never breaks strict state schemas.2. Audit fix + remaining API surface (after re-checking against the Coze docs / reference Python SDK):
conversation.message.delta/.completedmultiplexanswercontent withverbose(multi-agent jump info),function_call,tool_output, andknowledge_recallpayloads under the same SSE event name. All three streaming call sites only checked the event name, notdata.type, so non-answer payloads could leak into the visible answer. Added_is_answer_delta()gating ontype in (None, "answer").conversation_retrieve,conversation_message_create(append without triggering a chat run),conversation_message_list(cursor pagination),conversation_message_retrieve.file_retrieve(upload status/metadata) andbot_retrieve(/v1/bot/get_online_info).token_count/input_count/output_count) fromconversation.chat.completed/ the polledchatobject now populatesAIMessage.usage.All changes are confined to
src/lingxigraph/integrations/coze.pyandintegrations/__init__.py— no changes to core graph/runtime code.Test plan
PYTHONPATH=src python -m pytest tests/test_integrations_v2.py -q— 11 passed (4 existing + 7 new, covering file upload/object_string, reasoning+follow-up streaming, verbose/function_call filtering, token usage, and the new conversation/file/bot endpoints)PYTHONPATH=src python -m pytest tests/ -q— full suite green (136 passed, 2 skipped)suggestions_key=Nonedefault doesn't regress the existingtest_coze_agent_local_tool_and_workflow_interrupttest (strictTypedDictstate schema)🤖 Generated with Claude Code