<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sai Varsha Katikireddy</title>
    <description>The latest articles on DEV Community by Sai Varsha Katikireddy (@ksaivarsha).</description>
    <link>https://dev.to/ksaivarsha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4185635%2F1ccc9c11-81f8-4ae6-a6d1-62db9f582487.png</url>
      <title>DEV Community: Sai Varsha Katikireddy</title>
      <link>https://dev.to/ksaivarsha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ksaivarsha"/>
    <language>en</language>
    <item>
      <title>I added observability to my AI book app. It found two slow paths my own logs missed.</title>
      <dc:creator>Sai Varsha Katikireddy</dc:creator>
      <pubDate>Fri, 06 Nov 2026 02:33:01 +0000</pubDate>
      <link>https://dev.to/ksaivarsha/i-added-observability-to-my-ai-book-app-it-found-two-slow-paths-my-own-logs-missed-2h8a</link>
      <guid>https://dev.to/ksaivarsha/i-added-observability-to-my-ai-book-app-it-found-two-slow-paths-my-own-logs-missed-2h8a</guid>
      <description>&lt;p&gt;I built BookDNA, an AI book recommender that finds books by trope, setting, and tone, and checks every pick against Open Library so nothing is made up. I’d already spent a lot of time on speed: I timed every stage, tested 10 variants, and cut the time to the first result from about 30 seconds to 3.&lt;/p&gt;

&lt;p&gt;So I thought I knew where the time went. Then I added tracing with OpenTelemetry and SigNoz, and within minutes it showed me two things I’d missed.&lt;/p&gt;

&lt;p&gt;What I set up&lt;/p&gt;

&lt;p&gt;I used OpenTelemetry’s zero-code instrumentation for FastAPI and httpx, sent traces to SigNoz Cloud, and added a few custom spans by hand: one for each Open Library lookup, each Claude call, and each recommendation shelf.&lt;/p&gt;

&lt;p&gt;Two decisions mattered most:&lt;/p&gt;

&lt;p&gt;No prompts in my traces. Common LLM auto-instrumentation records prompts and responses by default. I only record the model, token counts, and the finish reason, never the text or my API key.&lt;br&gt;
One trace per search. A single search makes three separate requests from the browser. I generate one trace ID per search in the frontend and send it on all three, so SigNoz shows the whole search as one timeline. (SigNoz shows a “Missing Span” at the top, because the browser never sends its own span. That’s expected.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhbvm2wgj9uain5hba86.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnhbvm2wgj9uain5hba86.png" alt=" " width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finding 1: my homepage could take 26 seconds&lt;/p&gt;

&lt;p&gt;The “For You” shelf on my homepage blends a reader’s recent searches into personal picks. I had never timed it. The trace showed the request took about 26 seconds, and one Claude call took 23 of them. It’s an older code path: it waits for 18 books before returning anything, while my search page already streams results as they’re ready.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jdbltceluxkk982wceg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8jdbltceluxkk982wceg.png" alt=" " width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finding 2: a 13-second gap in every search&lt;/p&gt;

&lt;p&gt;My first shelf arrives in about 5 seconds, which is what my speed work was about. But the trace showed a quiet gap of about 13 seconds before the other three shelves arrived. They all come from one coordinated Claude call (so the shelves don’t repeat each other), and the validation checks under each shelf were fast. The real wait was Claude writing about 24 books with reasons.&lt;/p&gt;

&lt;p&gt;My timing logs measured the first shelf. The trace showed the whole experience.&lt;/p&gt;

&lt;p&gt;What tripped me up&lt;/p&gt;

&lt;p&gt;I kept a friction log while setting it up. A few highlights:&lt;/p&gt;

&lt;p&gt;Keeping the ingestion key in a .env file isn’t covered in the docs. The instrumentation reads settings before my app loads its .env file, so I start the server with dotenv run.&lt;br&gt;
On Windows, opentelemetry-instrument returns the prompt immediately while the server keeps running in the background. Confusing the first time.&lt;br&gt;
The setup command wanted about 20 instrumentation packages. I only needed FastAPI and httpx, so I pinned those.&lt;/p&gt;

&lt;p&gt;What’s next&lt;/p&gt;

&lt;p&gt;Streaming the For You shelves one at a time, the way search already works, then comparing the before-and-after traces in SigNoz.&lt;/p&gt;

&lt;p&gt;I built this with Claude Code, making the decisions and reviewing every change.&lt;/p&gt;

&lt;p&gt;Code: &lt;a href="https://github.com/ksaivarsha/bookdna" rel="noopener noreferrer"&gt;https://github.com/ksaivarsha/bookdna&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opentelemetry</category>
      <category>observability</category>
      <category>ai</category>
      <category>python</category>
    </item>
    <item>
      <title>I turned my book recommender into an AI app in a day. Here are the two bugs that taught me the most.</title>
      <dc:creator>Sai Varsha Katikireddy</dc:creator>
      <pubDate>Wed, 14 Oct 2026 23:33:41 +0000</pubDate>
      <link>https://dev.to/ksaivarsha/i-turned-my-book-recommender-into-an-ai-app-in-a-day-here-are-the-two-bugs-that-taught-me-the-most-2ff8</link>
      <guid>https://dev.to/ksaivarsha/i-turned-my-book-recommender-into-an-ai-app-in-a-day-here-are-the-two-bugs-that-taught-me-the-most-2ff8</guid>
      <description>&lt;p&gt;I read a lot of fantasy, especially romantasy, and I’ve always wanted to ask an AI for something specific, like “enemies to lovers in a fae court,” and get back real books, not made-up titles.&lt;/p&gt;

&lt;p&gt;So I built BookDNA: it reads a book’s “DNA” (its tropes, setting, and tone), recommends books that match, and checks every single suggestion against Open Library, so it never recommends a book that doesn’t exist.&lt;/p&gt;

&lt;p&gt;This week I turned it into an MCP server using mcp-use and deployed it on Manufact Cloud. Now any AI chat that supports MCP can use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rju8rm9ci4wkewwyw53.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3rju8rm9ci4wkewwyw53.png" alt=" " width="799" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkzi5p78q2wzwto1j7m3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgkzi5p78q2wzwto1j7m3.png" alt=" " width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;What I built&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Four tools an AI can call: search_book, get_book_dna, recommend, and recommend_by_tropes. The recommend tools return a small app that appears right inside the chat: a parchment shelf of book covers, each with a one-line reason it was picked.&lt;/p&gt;

&lt;p&gt;My favorite moment: I tested it in Manufact’s Inspector chat using a GPT model, not Claude, and it worked perfectly. One server, any AI. That’s the promise of MCP, and seeing it happen was really satisfying.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 1:&lt;/strong&gt; only one cover showed up&lt;/p&gt;

&lt;p&gt;My first shelf showed one cover and four broken images. Every book had a valid cover link, so the data wasn’t the problem.&lt;/p&gt;

&lt;p&gt;It turned out that older Open Library covers redirect to a different server on archive.org. MCP App widgets run with a strict security policy that lists which sites they’re allowed to load images from, and I’d only allowed Open Library. The redirected images were silently blocked. The one cover that loaded was newer and didn’t redirect.&lt;/p&gt;

&lt;p&gt;The fix was to allow archive.org too. I also added a fallback: if a cover ever fails, the card shows a parchment “title page” with the title and author instead of a broken image.&lt;/p&gt;

&lt;p&gt;Lesson: if your widget loads images, allow every site in the redirect chain, not just the first one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bug 2:&lt;/strong&gt; everyone shared one rate limit&lt;/p&gt;

&lt;p&gt;The server is public, so I added rate limits to protect my AI budget, including a per-user limit. Testing it against the live deployment showed something I didn’t expect: the platform’s proxy rewrites the header that identifies the user, and the address I was reading belonged to the proxy, not the user. So every user was sharing one limit.&lt;/p&gt;

&lt;p&gt;One small change fixed it, but I only found it because I tested against the real deployment instead of trusting my local setup.&lt;/p&gt;

&lt;p&gt;Lesson: test anything that depends on request headers against your real deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;How I built it&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built this with Claude Code. I made the decisions, reviewed every change, and tested it myself, and I kept a “friction log” of everything that confused me along the way. A few items are already in this post.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Try it&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://dev.tourl"&gt;github.com/ksaivarsha/bookdna&lt;/a&gt;&lt;br&gt;
MCP server: &lt;a href="https://dev.tourl"&gt;https://calm-wave-h30fs.run.mcp-use.com/mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you build MCP Apps, I’d love to hear what tripped you up.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cloud</category>
      <category>mcp</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
