Skip to main content

Posts

Showing posts with the label llm

Building a OTel sink that follows GenAI semantic conventions

I've been working on an OTel sink recently. To be more specific, it concerns receiving telemetry data from LLM interactions. Fortunately, there is a spec for all of this called GenAI Semantic Conventions . That is what I based my work on, so I can make assumptions about key attributes to highlight in the sink's UI, which is actually a CLI in my case. I dogfooded my handrolled sink using Pydantic AI (my favorite LLM framework), and all good, it follows the spec well. Key attributes flow in as specified. Pydantic AI also includes some of its own attributes, but that is expected. However, pick something like Claude Code, (via anthropic agent sdk), the reality is a bit different. It does not follow the spec at all. It has its own custom attribute layout . Same direction, different flavor with LangChain. The Python source has zero opentelemetry imports anywhere. None. The ls_* namespace ( ls_provider , ls_model_name , ls_temperature ) is its own thing, with LangSmith as the on...

How my work has changed in the past few years

When I started at my current job 3ish years ago, we had nothing but a few PowerPoint slides and a Figma prototype. I recall talking with one of the founders, whom I had worked with some 10 years ago, and pointing out that the very next step after PowerPoint and Figma was still the same as it had always been: translating those slides and mockups into CRUDs, forms, and other basic building blocks. Sure, all the new fancy tools and frameworks (we had jQuery then) help, but I still had to tinker with very basic primitives like tables and forms. I could not just say, "here is a table, here is a form." Well, yes, technically I could, but I still needed to write a lot of code to make a working form or table. This was around the time of GPT-3, Copilot beta, and very early Cursor. I did attempt to use them, but they could only perform very basic tasks and were pretty much useless for frontend work. We, being one of the first-wave "ChatGPT wrapper" startups has naturally pus...

HACKS.md

The most valuable comments I find in any given codebase look like this: Hack! This thing is weird because of this and that reason. I tried to implement a more elegant solution, but due to X and Y constraints, I failed. Hack! This is weird because there is a bug in library X that we depend on. See https://github.com/library/issues/420 Note! I tried options A, B, and C and decided to do this weird thing because, while it looks wrong, it turned out to be the best solution at the time of writing. These comments do not explain what the code does. They explain why the code looks the way it does. They bring into light historical context, failed attempts, and external constraints that are otherwise invisible. We all occasionally fail to communicate our intent to the next developer. That is normal and unavoidable. What matters is leaving a clear mark when something non-obvious or hacky is done on purpose. Increasingly, the “next developer” is a metal-headed clanker: an LLM. ...

PydanticAI + evals + LiteLLM pipeline

I gave a tech talk at a Python meetup titled "Overengineering an LLM pipeline". It's based on my experiences of building production-grade stuff with LLMs I'm not sure how overengineered it actually turned out. Experimental would be a better term as it is using PydanticAI graphs library, which is in its very early stages as of writing this, although arguably already better than some of the pipeline libraries. Anyway, here is a link to it. It is a CLI poker app where you play one hand against an LLM. The LLM (theoretically) gets better with a self-correcting mechanism based on the evaluation score from another LLM. It uses the annotated past games as an additional context to potentially improve its decision-making. https://github.com/juho-y/archipylago-poker

Async with Django

Working with Django in ASGI mode can benefit applications with long requests where the synchronous mode may be a bit wasteful, as you would likely need a ton of workers—especially if those workers are processes (e.g., Gunicorn). Your app may also run some fancy new LLM framework which is often async only. That said, async mode isn't a silver bullet for scaling. In Django 5.1, you still need threads to offload blocking database operations, which remain synchronous by design. Django provides async query wrappers like  aget , acount , and aall , but these are just syntactic sugar for offloading the operation to a worker thread via asgiref.sync_to_async. You can use the asgiref package's  sync_to_async utility to move blocking executions to the worker. By default, that is a ThreadPoolExecutor with one worker. This thread is shared by all sync_to_async calls during the request context, effectively meaning that each request spawns one worker thread for all sync work. So, to summar...