In event mode, a ToolMessage in the conversation costs the mistralai instrumentation its prompt events.
_emit_message_events at
packages/opentelemetry-instrumentation-mistralai/opentelemetry/instrumentation/mistralai/__init__.py:398
binds role and content in two branches and has no else:
for message in messages:
if isinstance(message, (UserMessage, AssistantMessage, SystemMessage)):
role = message.role
content = message.content
elif isinstance(message, dict):
role = message.get("role", "unknown")
content = message.get("content")
emit_event(MessageEvent(content=content, role=role or "unknown"), event_logger)
mistralai.models exports ToolMessage alongside those three, so a tool result matches neither branch.
Wrong behaviour, measured through an instrumented chat.complete with the repository's own
instrument_with_content and log_exporter fixtures, Python 3.10.21, mistralai 1.x, at 332599a:
$ python -m pytest tests/test_zz_toolmsg_probe.py -q -s
PROBE tool-first prompt events: []
PROBE swallowed: UnboundLocalError: local variable 'content' referenced before assignment
PROBE user-first prompt events: [('gen_ai.user.message', 'what is 2+2?'), ('gen_ai.user.message', 'what is 2+2?')]
A tool result after a user message repeats that user message, because the loop variables survive the
iteration, and the tool content never appears. A tool result first aborts the loop on iteration one, so the
genuine user prompt is lost too, and @dont_throw swallows it at debug level. _emit_choice_events is a
separate call from _handle_response at line 473, so the response event still lands: the span shows an
answer with no question in it.
Expected: a ToolMessage emits its own event with role="tool", and a message type nobody anticipated emits
one event carrying whatever role it has, never a copy of its predecessor.
Every claim above came from that run plus reading the module at 332599a. On Python 3.11 and later the same
failure reads "cannot access local variable 'content'". I did not check the other instrumentations in the
monorepo for the same pattern. The only ToolMessage hit in the tracker is the closed LangChain report #2938,
and the legacy-attribute path has the mirror gap, which #4509 is currently narrowing for dicts.
Would you take ToolMessage in the tuple, or an else that reads getattr(message, "role", None) and
getattr(message, "content", None) so the next SDK type cannot do this again?
In event mode, a
ToolMessagein the conversation costs the mistralai instrumentation its prompt events._emit_message_eventsatpackages/opentelemetry-instrumentation-mistralai/opentelemetry/instrumentation/mistralai/__init__.py:398binds
roleandcontentin two branches and has no else:mistralai.modelsexportsToolMessagealongside those three, so a tool result matches neither branch.Wrong behaviour, measured through an instrumented
chat.completewith the repository's owninstrument_with_contentandlog_exporterfixtures, Python 3.10.21, mistralai 1.x, at332599a:A tool result after a user message repeats that user message, because the loop variables survive the
iteration, and the tool content never appears. A tool result first aborts the loop on iteration one, so the
genuine user prompt is lost too, and
@dont_throwswallows it at debug level._emit_choice_eventsis aseparate call from
_handle_responseat line 473, so the response event still lands: the span shows ananswer with no question in it.
Expected: a
ToolMessageemits its own event withrole="tool", and a message type nobody anticipated emitsone event carrying whatever role it has, never a copy of its predecessor.
Every claim above came from that run plus reading the module at
332599a. On Python 3.11 and later the samefailure reads "cannot access local variable 'content'". I did not check the other instrumentations in the
monorepo for the same pattern. The only
ToolMessagehit in the tracker is the closed LangChain report #2938,and the legacy-attribute path has the mirror gap, which #4509 is currently narrowing for dicts.
Would you take
ToolMessagein the tuple, or anelsethat readsgetattr(message, "role", None)andgetattr(message, "content", None)so the next SDK type cannot do this again?