ó
    À‘´j±#  ã                  óè   • S r SSKJr  SSKrSSKrSSKJrJr  SSKJrJ	r	  SSK
JrJrJrJr  SSKJr  SSKJr  SS	 jrSS
 jr\R*                  " S5      rSS jrSS jr " S S5      r " S S5      rg)u  
Native LangSmith tracer for the extraction agents and the playground's
RAG agents. Each pydantic-ai agent class in `quber.agents` holds its own
instance. The playground's agents share one process-wide instance from
`quber.playground.tracing`. The cli backend (`ClaudeCLIClient`) and the
`quber.processors` classes built through `AgentFactory` are not traced.

Posts runs to LangSmith's first-class `/runs/multipart` API with explicit
`inputs` and `outputs` JSON â€” the same pattern as
`~/.claude/hooks/stop_hook.sh`. We do this instead of OTLP because the
OTel adapter on LangSmith's side has to introspect span attributes to
derive inputs/outputs, and Logfire/pydantic-ai emit attribute keys that
the adapter doesn't recognize. The result: traces land, but the LangSmith
UI shows empty input/output panels.

This tracer skips the translation entirely. We hand LangSmith the data
in the shape its UI renders directly.

Toggle via `TRACE_TO_LANGSMITH` env var; configured via
`CC_LANGSMITH_API_KEY`, `CC_LANGSMITH_PROJECT`, `CC_LANGSMITH_DEBUG`.
é    )ÚannotationsN)ÚasynccontextmanagerÚcontextmanager)ÚdatetimeÚtimezone)ÚAnyÚDictÚ	GeneratorÚOptional)Úlogger)Úget_settingsc                 óz   • [        5       R                  n U R                  =(       a    [        U R                  5      $ ©N)r   ÚobsÚtrace_to_langsmithÚboolÚlangsmith_api_key)r   s    Ú;/home/mande/repo/quber/src/quber/agents/langsmith_tracer.pyÚlangsmith_enabledr   $   s+   € Ü
‹.×
Ñ
€CØ×!Ñ!×A¤d¨3×+@Ñ+@Ó&AÐAó    c                 óJ   • [         R                  " [        R                  5      $ r   )r   Únowr   Úutc© r   r   Úutc_nowr   )   s   € Ü�<Š<œŸ™Ó%Ð%r   z-\d{8}$c                ó@   • [         R                  SU =(       d    S5      $ )aŽ  Strip the trailing `-YYYYMMDD` dated suffix from an Anthropic
model identifier.

LangSmith's pricing catalog and many of its UI affordances key on
the bare model name (e.g. `claude-haiku-4-5`, `claude-sonnet-4-6`).
The dated form Anthropic returns (`claude-haiku-4-5-20251001`)
doesn't match the catalog, so cost attribution silently breaks.

Same approach as `~/.claude/hooks/stop_hook.sh`'s sed.
Ú )Ú_DATED_SUFFIXÚsub)Úmodels    r   Úcanonical_model_namer!   0   s   € ô ×Ñ˜R §¨"Ó-Ð-r   c           
     ó\  • [        U SS5      =(       d    S[        U SS5      =(       d    S-   [        U SS5      =(       d    S-   n[        U5      [        [        U SS5      =(       d    S5      [        [        U SS5      =(       d    S5      [        [        U SS5      =(       d    S5      S.S.$ )a“  Translate a pydantic-ai `RunUsage` into LangSmith's `usage_metadata`
schema (the LangChain standard).

The reported `input_tokens` is the usage object's `input_tokens` plus
its `cache_read_tokens` and `cache_write_tokens`. A pydantic-ai
`RunUsage` built from an Anthropic response already counts cache reads
and writes in `input_tokens`, so with prompt caching active the cached
input is counted twice.
Úinput_tokensr   Úcache_read_tokensÚcache_write_tokensÚoutput_tokens)Ú
cache_readÚcache_creation)r#   r&   Úinput_token_details)ÚgetattrÚint)Úusager#   s     r   Úusage_metadata_fromr-   >   s¥   € ô 
�˜¨Ó	*×	/¨aÜ�5Ð-¨qÓ1×6°Qñ	8ä�5Ð.°Ó2×7°añ	9ð ô ˜LÓ)ÜœW U¨O¸QÓ?×DÀ1ÓEäœg eÐ-@À!ÓD×IÈÓJÜ!¤'¨%Ð1EÀqÓ"I×"NÈQÓOñ 
ñð r   c                  ó¨   • \ rS rSrSr   S	       S
S jjr      SS jr\  S         SS jj5       r\	  S       SS jj5       r
Srg)ÚLangSmithTraceréW   u;  Authors LangSmith runs directly via the SDK. Each pydantic-ai agent
class in `quber.agents` holds its own instance. The playground's agents
share one from `quber.playground.tracing`.

The tracer is a no-op if `TRACE_TO_LANGSMITH` is unset/false or
`CC_LANGSMITH_API_KEY` is missing â€” callers don't need to guard.
Nc                ó²  • [        5       U l        U R                  (       d  S U l        S U l        S U l        g SSKJn  [        5       R                  nU" SU=(       d    UR                  S9U l        U=(       d    UR                  U l        U=(       d    SU l        UR                  (       a-  [        R                  " SU R                  U R                  5        g g )Nr   )ÚClientzhttps://api.smith.langchain.com)Úapi_urlÚapi_keyÚquberz4LangSmith tracer initialized: project={} run_name={})r   ÚenabledÚclientÚprojectÚrun_nameÚ	langsmithr2   r   r   r   Úlangsmith_projectÚlangsmith_debugr   Úinfo)Úselfr8   r4   r9   r2   r   s         r   Ú__init__ÚLangSmithTracer.__init__`   s¥   € ô )Ó*ˆŒØ�|�|ØˆDŒKØˆDŒLØ ˆDŒMØå$ä‹n× Ñ ˆÙØ5Ø×4˜s×4Ñ4ñ
ˆŒð ×7 #×"7Ñ"7ˆŒØ ×+ GˆŒà××Ü�KŠKØFØ—‘Ø—‘õð r   c                ó    • U(       a  [        U5      OS nSS0nU(       a  X4S'   U(       a	  X:w  a  XS'   U(       a  UR                  U5        SU0$ )NÚls_providerÚ	anthropicÚls_model_nameÚls_model_full_nameÚmetadata)r!   Úupdate)r>   r    Úextra_metadataÚ	canonicalrF   s        r   Úbuild_extraÚLangSmithTracer.build_extra~   sS   € ö
 49Ô(¨Ô/¸dˆ	Ø$1°;Ð#?ˆÞØ(1�_Ñ%Þ�UÓ'Ø-2Ð)Ñ*ÞØ�O‰O˜NÔ+Ø˜HÐ%Ð%r   c              #  ó~  #   • [        5       nU R                  (       a  U R                  c  Uv •  g[        [        R
                  " 5       5      n[        5       nU(       a  [        U5      OSnU R                  X45      n	 U R                  R                  UUSUUU R                  U	U(       a  U/OSS9   Uv •   U R                  R                  UUR                  =(       d    0 [        5       S9  g! [         a%  n
[        R                  " SU
5        Uv •   Sn
A
gSn
A
ff = f! [         a!  n
[        R                  " SU
5         Sn
A
gSn
A
ff = f! [         aa  n
 U R                  R                  U[!        U
5      [        5       S9  e ! [         a!  n[        R                  " SU5         SnAe SnAff = fSn
A
ff = f7f)	a  Open an LLM-type run. Caller sets `.outputs` on the yielded
handle, and the tracer patches LangSmith on exit with the outputs
and the end timestamp. The run is recorded as failed only when an
exception escapes the `with` block. The tracer records its `repr`
and re-raises it.
NÚllm©ÚidÚnameÚrun_typeÚinputsÚ
start_timeÚproject_nameÚextraÚtagsúLangSmith create_run failed: {}©Úrun_idÚoutputsÚend_timeú$LangSmith update_run (ok) failed: {}©rY   Úerrorr[   ú'LangSmith update_run (error) failed: {}©Ú	RunHandler6   r7   ÚstrÚuuidÚuuid4r   r!   rJ   Ú
create_runr8   Ú	Exceptionr   ÚwarningÚ
update_runrZ   Úrepr©r>   rP   rR   r    rH   ÚhandlerY   rS   rI   rU   ÚexcÚ	patch_excs               r   Úllm_run_syncÚLangSmithTracer.llm_run_sync�   s�  é € ô “ˆØ�|�|˜tŸ{™{Ñ2ØŠLØä”T—Z’Z“\Ó"ˆÜ“Yˆ
Þ38Ô(¨Ô/¸dˆ	Ø× Ñ  Ó7ˆð	Ø�K‰K×"Ñ"ØØØØØ%Ø!Ÿ\™\ØÞ$-�i‘[°4ð #ñ 	ð	LØŠLðLØ—‘×&Ñ&Ø!Ø"ŸN™N×0¨bÜ$›Yð 'ò øô' ó 	Ü�NŠNÐ<¸cÔBØŠLÜûð	ûô0 ó LÜ—’ÐEÀs×KÑKûðLûô# ó 		ðUØ—‘×&Ñ&Ø!Ü˜s›)Ü$›Yð 'ñ ð øô ó UÜ—’ÐHÈ)×TÐTØûðUþð		üs–   ‚A;F=Á>4C/ Â3E Â86D! Ã.F=Ã/
DÃ9DÄF=ÄDÄF=Ä!
EÄ+EÅF=ÅEÅF=Å
F:Å,FÆF5Æ
F2ÆF-Æ(F5Æ-F2Æ2F5Æ5F:Æ:F=c               ó‚  #   • [        5       nU R                  (       a  U R                  c  U7v •  g[        [        R
                  " 5       5      n[        5       nU(       a  [        U5      OSnU R                  X45      n	 U R                  R                  UUSUUU R                  U	U(       a  U/OSS9   U7v •   U R                  R                  XeR                  =(       d    0 [        5       S9  g! [         a&  n
[        R                  " SU
5        U7v •   Sn
A
gSn
A
ff = f! [         a!  n
[        R                  " SU
5         Sn
A
gSn
A
ff = f! [         aa  n
 U R                  R                  U[!        U
5      [        5       S9  e ! [         a!  n[        R                  " SU5         SnAe SnAff = fSn
A
ff = f7f)	a
  Async sibling of `llm_run_sync`.

The langsmith Client's `create_run` / `update_run` are non-blocking
in practice (they enqueue onto a background batched sender), so we
can call them from async code without awaiting. The trace-side
I/O happens off the request path.
NrM   rN   rW   rX   r\   r]   r_   r`   rj   s               r   Úllm_runÚLangSmithTracer.llm_runË   s‚  é € ô “ˆØ�|�|˜tŸ{™{Ñ2Ø‹LØä”T—Z’Z“\Ó"ˆÜ“Yˆ
Þ38Ô(¨Ô/¸dˆ	Ø× Ñ  Ó7ˆð	Ø�K‰K×"Ñ"ØØØØØ%Ø!Ÿ\™\ØÞ$-�i‘[°4ð #ñ 	ð	LØ‹LðLØ—‘×&Ñ&¨f¿n¹n×>RÐPRÔ]dÓ]fÐ&Ògøô ó 	Ü�NŠNÐ<¸cÔBØ‹LÜûð	ûô  ó LÜ—’ÐEÀs×KÑKûðLûô ó 	ðUØ—‘×&Ñ&¨f¼DÀ»IÔPWÓPYÐ&ÑZð øô ó UÜ—’ÐHÈ)×TÐTØûðUþð	üs–   ‚A<F?Á?4C0 Â4E Â:5D# Ã/F?Ã0
D Ã:DÄF?ÄD Ä F?Ä#
EÄ-E	ÅF?Å	EÅF?Å
F<Å,F	ÆF7Æ	
F4ÆF/Æ*F7Æ/F4Æ4F7Æ7F<Æ<F?)r7   r6   r8   r9   )NNN)r8   úOptional[str]r4   rs   r9   rs   ÚreturnÚNone)r    rs   rH   úOptional[Dict[str, Any]]rt   úDict[str, Any])NN)
rP   rb   rR   rw   r    rs   rH   rv   rt   zGenerator['RunHandle'])rP   rb   rR   rw   r    rs   rH   rv   )Ú__name__Ú
__module__Ú__qualname__Ú__firstlineno__Ú__doc__r?   rJ   r   rn   r   rq   Ú__static_attributes__r   r   r   r/   r/   W   sú   † ñð "&Ø!%Ø"&ð	àðð ðð  ð	ð
 
õð<&àð&ð 1ð&ð 
ô	&ð ð
  $Ø37ð;Làð;Lð ð;Lð ð	;Lð
 1ð;Lð 
 ô;Ló ð;Lðz ð
  $Ø37ð4Làð4Lð ð4Lð ð	4Lð
 1ô4Ló ó4Lr   r/   c                  ó&   • \ rS rSrSrSrSS jrSrg)ra   i  z?Mutable handle the caller sets `outputs` on inside the context.©rZ   c                ó   • 0 U l         g r   r   )r>   s    r   r?   ÚRunHandle.__init__  s	   € Ø')ˆ�r   N)rt   ru   )rx   ry   rz   r{   r|   Ú	__slots__r?   r}   r   r   r   ra   ra     s   † ÙIà€I÷*r   ra   )rt   r   )rt   r   )r    rb   rt   rb   )r,   r   rt   rw   )r|   Ú
__future__r   Úrerc   Ú
contextlibr   r   r   r   Útypingr   r	   r
   r   Úlogurur   Úquber.settingsr   r   r   Úcompiler   r!   r-   r/   ra   r   r   r   Ú<module>rŠ      se   ðñõ, #ã 	Û ß :ß 'ß 1Ó 1å å 'ôBô
&ð —
’
˜:Ó&€ô.ô÷2iLñ iL÷X*ò *r   