<?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: Aman</title>
    <description>The latest articles on DEV Community by Aman (@onlyoneaman).</description>
    <link>https://dev.to/onlyoneaman</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F250698%2Fd899494b-6757-481e-98a5-3fa7619bf708.jpeg</url>
      <title>DEV Community: Aman</title>
      <link>https://dev.to/onlyoneaman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onlyoneaman"/>
    <language>en</language>
    <item>
      <title>Building Agent Skills from Scratch</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Fri, 26 Dec 2025 08:20:23 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/building-agent-skills-from-scratch-lbl</link>
      <guid>https://dev.to/onlyoneaman/building-agent-skills-from-scratch-lbl</guid>
      <description>&lt;p&gt;There's a lot written about agent skills, but not much about actually implementing them.&lt;/p&gt;

&lt;p&gt;This post shows you how they work and how to integrate them into your existing agent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;View the complete implementation on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Agent Skills?
&lt;/h2&gt;

&lt;p&gt;Agent skills solve a simple problem: your system prompt gets bloated when you try to make your agent good at everything.&lt;/p&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You're an expert at code review, git, file organization, API testing...
[2000 lines of instructions]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You have access to these skills:
- code-review: Reviews code for bugs and security
- git-helper: Git workflows and troubleshooting
- file-organizer: Organizes files intelligently
- api-tester: Tests REST APIs

Load them when needed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Core Idea
&lt;/h2&gt;

&lt;p&gt;Skills are markdown files that live in a directory. Each skill has two parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;YAML frontmatter with name and description (see &lt;a href="https://amankumar.ai/blogs/wtf-is-frontmatter" rel="noopener noreferrer"&gt;this guide on frontmatter&lt;/a&gt; if you're new to it)&lt;/li&gt;
&lt;li&gt;Markdown body with detailed instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When the agent needs expertise, it loads the relevant skill on the fly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Review this code for SQL injection"
  ↓
Agent: "I need the code-review skill"
  ↓
System: [Loads SKILL.md with security guidelines]
  ↓
Agent: [Follows those guidelines]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key insight is that skills are just structured prompts. But they're modular, discoverable, and loaded on demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Actually Works
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Discovery
&lt;/h3&gt;

&lt;p&gt;Scan a directory for SKILL.md files and parse their frontmatter. You only load the metadata initially, not the full content. This keeps memory usage low. (&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;See the discovery implementation&lt;/a&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;skills/
├── code-review/
│   └── SKILL.md        # name: code-review, description: ...
├── git-helper/
│   └── SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During discovery, you extract just the YAML frontmatter (name, description). The full markdown content stays on disk until needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Tool Registration
&lt;/h3&gt;

&lt;p&gt;Convert each skill into an &lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI function tool&lt;/a&gt;. The LLM sees these as callable functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;activate_skill_code_review: "Reviews code for bugs, security, best practices"
activate_skill_git_helper: "Git workflows and troubleshooting"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The description is critical because it's what the LLM uses to decide which skill to activate. Be specific and clear.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Activation
&lt;/h3&gt;

&lt;p&gt;When the LLM calls a skill function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the full &lt;code&gt;SKILL.md&lt;/code&gt; content from disk&lt;/li&gt;
&lt;li&gt;Add it to the conversation as a tool result&lt;/li&gt;
&lt;li&gt;Let the LLM continue with those instructions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is lazy loading. You only fetch content when actually needed. If you have 20 skills but only use 2, you've only loaded 2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Execution
&lt;/h3&gt;

&lt;p&gt;The LLM reads the skill instructions and follows them. The skill acts like a temporary system prompt for that specific task. Once the task is done, the skill instructions fade from context (unless you keep them for multi-turn conversations).&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Skill Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;code-review&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Reviews code for bugs, security, and best practices&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Code Review Skill&lt;/span&gt;

You are an expert code reviewer.

&lt;span class="gu"&gt;## Check For&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; &lt;span class="gs"&gt;**Security**&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; SQL injection in queries
&lt;span class="p"&gt;   -&lt;/span&gt; XSS in user inputs
&lt;span class="p"&gt;   -&lt;/span&gt; Auth bypasses
&lt;span class="p"&gt;
2.&lt;/span&gt; &lt;span class="gs"&gt;**Quality**&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; Readability
&lt;span class="p"&gt;   -&lt;/span&gt; Maintainability
&lt;span class="p"&gt;   -&lt;/span&gt; DRY violations
&lt;span class="p"&gt;
3.&lt;/span&gt; &lt;span class="gs"&gt;**Performance**&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; N+1 queries
&lt;span class="p"&gt;   -&lt;/span&gt; Memory leaks
&lt;span class="p"&gt;   -&lt;/span&gt; Inefficient algorithms

&lt;span class="gu"&gt;## Response Format&lt;/span&gt;

&lt;span class="gs"&gt;**Summary**&lt;/span&gt;: Brief assessment
&lt;span class="gs"&gt;**Critical Issues**&lt;/span&gt;: Security problems (if any)
&lt;span class="gs"&gt;**Improvements**&lt;/span&gt;: Suggestions for better code
&lt;span class="gs"&gt;**Positives**&lt;/span&gt;: What works well
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the structure: clear sections, bullet points, and expected output format. The LLM follows structured instructions much better than prose. (For more on crafting effective prompts, see &lt;a href="https://amankumar.ai/blogs/anatomy-of-a-prompt-the-complete-guide-to-crafting-effective-ai-chatgpt-prompts" rel="noopener noreferrer"&gt;this guide&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Pattern Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Context Efficiency&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of loading 10KB of instructions upfront, you load 100 bytes of metadata. Full instructions only come in when needed. This matters when you're paying per token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Modularity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each skill is independent. Add a new one by dropping in a &lt;code&gt;SKILL.md&lt;/code&gt; file. No code changes needed. Want to remove a skill? Delete the directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Clarity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When debugging, you can see exactly which skill was activated and what instructions it provided. This makes troubleshooting much easier than a monolithic prompt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Reusability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Share skills across projects. Someone else's &lt;code&gt;api-tester&lt;/code&gt; skill works in your agent with zero modification. Skills become a shared library of expertise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Lazy Loading
&lt;/h3&gt;

&lt;p&gt;Don't load all skills into memory at startup. This defeats the purpose because you're back to loading everything upfront.&lt;/p&gt;

&lt;p&gt;Do load on demand. Parse frontmatter during discovery, but keep full content on disk until the LLM actually requests it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Function Naming
&lt;/h3&gt;

&lt;p&gt;Prefix skill functions clearly: &lt;code&gt;activate_skill_code_review&lt;/code&gt;. This makes it obvious in logs what's happening. When you see &lt;code&gt;activate_skill_*&lt;/code&gt; in your logs, you know a skill was activated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conversation Flow
&lt;/h3&gt;

&lt;p&gt;The exact sequence matters. Here's what happens:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User sends message&lt;/li&gt;
&lt;li&gt;LLM responds with tool_calls (requesting a skill)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Critical&lt;/strong&gt;: Add assistant message with tool_calls to conversation&lt;/li&gt;
&lt;li&gt;Add tool message with skill content&lt;/li&gt;
&lt;li&gt;LLM continues with skill instructions&lt;/li&gt;
&lt;li&gt;Final response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you skip step 3, OpenAI will reject your request. The tool_calls must be properly formatted with a type field and nested function object. This is a common gotcha. (See &lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI's tools documentation&lt;/a&gt; for details.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Looping for Multiple Tool Calls
&lt;/h3&gt;

&lt;p&gt;Skills can chain. A skill might activate code execution, which might need another skill. Your agent should loop until there are no more tool calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_calls&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="nf"&gt;handle_tool_calls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Always pass tools in every call, even after skill activation. Otherwise, skills can't use other tools like code execution. (&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;See full implementation&lt;/a&gt; for the complete loop logic.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical Considerations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Skill Scope&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One skill equals one domain. Keep them focused.&lt;/p&gt;

&lt;p&gt;Good examples: code-review, git-helper, api-tester&lt;br&gt;
Bad example: developer-tools (too broad)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill Structure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Use clear sections with examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What the skill does&lt;/li&gt;
&lt;li&gt;How to approach tasks&lt;/li&gt;
&lt;li&gt;Expected output format&lt;/li&gt;
&lt;li&gt;Examples of good results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A wall of text doesn't work. Structure helps the LLM follow instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What if a skill doesn't exist? Return a helpful error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Skill 'xyz' not found. Available: code-review, git-helper"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Common Mistakes &amp;amp; Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Loading Everything Upfront
&lt;/h3&gt;

&lt;p&gt;Some implementations load all skills at startup. This defeats the purpose because you're back to loading everything upfront, wasting memory and context tokens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Load metadata only during discovery. Activate skills when needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vague Skill Descriptions
&lt;/h3&gt;

&lt;p&gt;The LLM uses skill descriptions to decide which to activate. Be specific.&lt;/p&gt;

&lt;p&gt;❌ "Helps with code"&lt;br&gt;
✅ "Reviews Python/JavaScript code for security vulnerabilities, PEP 8 compliance, and performance issues"&lt;/p&gt;

&lt;p&gt;Include what the skill does, what types of tasks it handles, and key capabilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong Tool Calls Format
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Error&lt;/strong&gt;: Missing required parameter: messages[1].tool_calls[0].type&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause&lt;/strong&gt;: OpenAI requires a specific nested structure. The tool_calls must have a type field and nest the function details under a function key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Use the correct format with type: "function" and nested function object. Don't flatten it. See &lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI's tools documentation&lt;/a&gt; for the exact message format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Forgetting to Include Tools After Skill Activation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: After activating a skill, the LLM can't use other tools like code execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt;: Always pass tools in every LLM call. Don't remove tools after skill activation because skills might need them.&lt;/p&gt;

&lt;h3&gt;
  
  
  No Structure in Skills
&lt;/h3&gt;

&lt;p&gt;A wall of text doesn't work. Use clear headings, bullet points, code examples, and expected output formats. The LLM follows structured instructions much better than prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Skills Make Sense
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-domain agents that handle code, git, and devops&lt;/li&gt;
&lt;li&gt;Agents with specialized workflows&lt;/li&gt;
&lt;li&gt;Teams sharing common patterns&lt;/li&gt;
&lt;li&gt;When you hit context limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not needed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-purpose agents&lt;/li&gt;
&lt;li&gt;Agents with small, focused prompts&lt;/li&gt;
&lt;li&gt;Prototypes and experiments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't over-engineer. If your system prompt is small and manageable, you probably don't need skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Standard
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;AgentSkills.io&lt;/a&gt; defines the open format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SKILL.md naming convention&lt;/li&gt;
&lt;li&gt;YAML frontmatter schema&lt;/li&gt;
&lt;li&gt;Directory structure&lt;/li&gt;
&lt;li&gt;Best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Following the standard means your skills work with other implementations. Skills become portable across projects and teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building Your First Skill
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create the directory: &lt;code&gt;mkdir -p skills/my-first-skill&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create SKILL.md with YAML frontmatter and markdown instructions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Integrate SkillsManager into your agent (&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;see GitHub repo for full code&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test it by asking your agent to use the skill and verifying it activates&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No code changes needed to add new skills. Just drop in a SKILL.md file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Agent skills are structured prompts with a loading mechanism.&lt;/p&gt;

&lt;p&gt;The pattern works because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It keeps context lean by only loading what you need&lt;/li&gt;
&lt;li&gt;It makes agents modular since skills are independent&lt;/li&gt;
&lt;li&gt;It enables skill reuse so you can share skills across projects&lt;/li&gt;
&lt;li&gt;It simplifies debugging with clear activation logs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can build a working implementation in an afternoon. The core SkillsManager is about 130 lines of Python. (&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;View the implementation&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Start with one skill. See if it helps. Expand from there.&lt;/p&gt;

&lt;p&gt;The complete working implementation is available on &lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. Use it as a reference or starting point for your own agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;AgentSkills.io&lt;/a&gt; - Official specification&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-skills" rel="noopener noreferrer"&gt;Claude Skills&lt;/a&gt; - Anthropic's skill examples&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/samuelint/open-agent-skills" rel="noopener noreferrer"&gt;Open Agent Skills&lt;/a&gt; - Community skill library&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/onlyoneaman/agent-skills" rel="noopener noreferrer"&gt;Working Implementation&lt;/a&gt; - Complete code from this tutorial&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amankumar.ai/blogs/wtf-is-frontmatter" rel="noopener noreferrer"&gt;WTF is Frontmatter?&lt;/a&gt; - Understanding frontmatter/metadata in markdown files&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://amankumar.ai/blogs/anatomy-of-a-prompt-the-complete-guide-to-crafting-effective-ai-chatgpt-prompts" rel="noopener noreferrer"&gt;Anatomy of a Prompt&lt;/a&gt; - Complete guide to crafting effective AI prompts with structured approaches&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://platform.openai.com/docs/guides/function-calling" rel="noopener noreferrer"&gt;OpenAI Function Calling&lt;/a&gt; - Official OpenAI tools documentation&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>skills</category>
    </item>
    <item>
      <title>I cut my site’s image payload by 97.7% (83.57 MB 1.89 MB)</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Tue, 23 Dec 2025 06:36:46 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/i-cut-my-sites-image-payload-by-977-8357-mb-189-mb-3338</link>
      <guid>https://dev.to/onlyoneaman/i-cut-my-sites-image-payload-by-977-8357-mb-189-mb-3338</guid>
      <description>&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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2Aa9R-cspPepIXAYRJ" 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%2Fmiro.medium.com%2Fv2%2Fresize%3Afit%3A1400%2Fformat%3Awebp%2F0%2Aa9R-cspPepIXAYRJ" alt="captionless image" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://amankumar.ai/" rel="noopener noreferrer"&gt;amankumar.ai&lt;/a&gt; on Next.js. Pages would load, layouts would appear, and React would do its thing, but something still felt slow.&lt;/p&gt;

&lt;p&gt;The problem wasn’t that the page didn’t render.&lt;/p&gt;

&lt;p&gt;The problem was this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The page renders, but images stay blank for a moment. Then they drop in one by one. And the site doesn’t feel visually complete until later.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That gap, where placeholders sit there and images arrive late, is subtle, but once you notice it, you can’t unsee it. It makes the site feel heavier than it should.&lt;/p&gt;

&lt;p&gt;So I stopped guessing and looked at the boring part I’d been ignoring: &lt;strong&gt;image payloads&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this keeps happening on modern sites (without anyone messing up)
&lt;/h2&gt;

&lt;p&gt;This problem shows up a lot more today, even on well-built sites, and it’s not because people are careless.&lt;/p&gt;

&lt;p&gt;A few things are happening at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Design tools export big images by default&lt;/strong&gt; Posters, UI mockups, and screenshots often come out as large PNGs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;AI-generated images are heavy by nature&lt;/strong&gt; High detail, high resolution, no concern for delivery size.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Frameworks don’t change asset intent&lt;/strong&gt; Next.js can optimize &lt;em&gt;delivery&lt;/em&gt;, but if the source image is huge, it still has to download.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Image bloat accumulates quietly&lt;/strong&gt; No errors, no warnings, just slower visual completion over time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is exactly what I was seeing: the page loads, but images arrive last.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the problem on amankumar.ai
&lt;/h2&gt;

&lt;p&gt;Before touching anything, I measured the image assets in the repo.&lt;/p&gt;

&lt;p&gt;They were a mix of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  PNGs (most of them)&lt;/li&gt;
&lt;li&gt;  some JPG/JPEGs&lt;/li&gt;
&lt;li&gt;  different resolutions&lt;/li&gt;
&lt;li&gt;  photos, posters, and UI graphics, all mixed together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After running a single optimization pass, the numbers looked like this:&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.amazonaws.com%2Fuploads%2Farticles%2Fg68kirvfcjy1jgk58qak.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.amazonaws.com%2Fuploads%2Farticles%2Fg68kirvfcjy1jgk58qak.png" alt="Optimization Results for amankumar.ai" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;repo-level image weight&lt;/strong&gt;, not “every page ships 83 MB.” But it clearly showed the root issue. I was carrying a lot of unnecessary image data, and the browser was paying for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineering question I cared about
&lt;/h2&gt;

&lt;p&gt;I didn’t want a clever setup. I wanted something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  repeatable&lt;/li&gt;
&lt;li&gt;  boring&lt;/li&gt;
&lt;li&gt;  safe for mixed assets&lt;/li&gt;
&lt;li&gt;  easy to re-run later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the real question became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What’s the simplest image pipeline that’s correct most of the time?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First principle: pixels matter more than formats
&lt;/h2&gt;

&lt;p&gt;Before debating PNG vs WebP vs AVIF, the biggest issue was &lt;strong&gt;pixel count&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If an image is 2400px wide but is never rendered above ~800px, shipping the extra pixels is pure waste.&lt;/p&gt;

&lt;p&gt;So I made one hard rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cap the max dimension at 800px.&lt;/strong&gt; If an image is larger, downscale it. If it’s smaller, leave it alone.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This single decision removes a surprising amount of weight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Image formats: quality vs size (same perceived quality)
&lt;/h2&gt;

&lt;p&gt;Once dimensions are sane, format choice actually matters.&lt;/p&gt;

&lt;p&gt;Here’s the practical ranking when comparing images at roughly the same visual quality:&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.amazonaws.com%2Fuploads%2Farticles%2Fsgm47m2amdjoxkf94l0e.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.amazonaws.com%2Fuploads%2Farticles%2Fsgm47m2amdjoxkf94l0e.png" alt="Comparing Image Formats" width="800" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;AVIF genuinely compresses better than WebP in many cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I didn’t standardize on AVIF (yet)
&lt;/h2&gt;

&lt;p&gt;This is important, because AVIF &lt;em&gt;is&lt;/em&gt; genuinely impressive.&lt;/p&gt;

&lt;p&gt;The reason I didn’t standardize on it is &lt;strong&gt;not quality&lt;/strong&gt;. It’s &lt;strong&gt;practical browser support and operational safety&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;According to current browser compatibility data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  AVIF support is &lt;strong&gt;good&lt;/strong&gt;, but not universal&lt;/li&gt;
&lt;li&gt;  Some older browsers, embedded webviews, and edge cases still fall back poorly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the current state clearly here: &lt;a href="https://caniuse.com/?search=avif" rel="noopener noreferrer"&gt;https://caniuse.com/?search=avif&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For this project, I didn’t want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  extra format negotiation&lt;/li&gt;
&lt;li&gt;  more fallbacks&lt;/li&gt;
&lt;li&gt;  “why didn’t this image load on X device?” debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the decision wasn’t “AVIF is bad.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Aman Kumar’s stories in your inbox
&lt;/h2&gt;

&lt;p&gt;Join Medium for free to get updates from this writer.&lt;/p&gt;

&lt;p&gt;Subscribe&lt;/p&gt;

&lt;p&gt;Subscribe&lt;/p&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;WebP is the safest modern default today.&lt;/strong&gt; AVIF can be layered later if needed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The tricky part: photos vs UI/posters
&lt;/h2&gt;

&lt;p&gt;My repo wasn’t just photos.&lt;/p&gt;

&lt;p&gt;It had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  UI graphics&lt;/li&gt;
&lt;li&gt;  posters with text&lt;/li&gt;
&lt;li&gt;  logos and illustrations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you treat everything like a photo and apply lossy compression everywhere, UI assets suffer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  fuzzy text&lt;/li&gt;
&lt;li&gt;  halos around edges&lt;/li&gt;
&lt;li&gt;  cheap-looking posters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebP helped here because it supports &lt;strong&gt;both lossy and lossless modes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The challenge was choosing between them &lt;em&gt;without manual tagging&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The simple rule that worked
&lt;/h2&gt;

&lt;p&gt;I used one clean signal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;If the image has transparency (alpha)&lt;/strong&gt;, likely UI or graphic, use &lt;strong&gt;lossless WebP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;If it’s fully opaque&lt;/strong&gt;, likely a photo, use &lt;strong&gt;lossy WebP&lt;/strong&gt; (quality ~80)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is this perfect? No. Is it correct often enough to automate safely? Yes.&lt;/p&gt;

&lt;p&gt;That single rule handled mixed assets without human intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metadata: invisible weight
&lt;/h2&gt;

&lt;p&gt;Many images carry metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  camera EXIF&lt;/li&gt;
&lt;li&gt;  editing history&lt;/li&gt;
&lt;li&gt;  embedded profiles&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this helps a webpage load faster or look better.&lt;/p&gt;

&lt;p&gt;So I strip metadata unconditionally. Sometimes the savings are small; sometimes they’re large. Either way, it’s free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sanity check on a very different repo
&lt;/h2&gt;

&lt;p&gt;To make sure this wasn’t a one-off, I ran the &lt;strong&gt;same script, unchanged&lt;/strong&gt;, on another repo: &lt;a href="https://promptsmint.com/" rel="noopener noreferrer"&gt;promptsmint.com&lt;/a&gt;, an AI prompts library with only AI-generated images.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  92 images&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;228.12 MB total&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;2.48 MB per image (avg)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  92 images&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;4.28 MB total&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;~50 KB per image (avg)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;98.0% reduction&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;223.84 MB saved&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different site. Different content. Same outcome.&lt;/p&gt;

&lt;p&gt;That confirmed this wasn’t luck. It was just removing waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;

&lt;p&gt;I packaged the whole pipeline into a single script and hosted it here:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;[other]script[/other]&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  finds all &lt;em&gt;jpg/jpeg/png/webp&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  outputs &lt;em&gt;*-optimized.webp&lt;/em&gt; next to originals&lt;/li&gt;
&lt;li&gt;  caps size at &lt;strong&gt;800px&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  strips metadata&lt;/li&gt;
&lt;li&gt;  uses &lt;strong&gt;lossless WebP for transparent images&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  uses &lt;strong&gt;lossy WebP for opaque images&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No clever tricks. Just consistent rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual outcome
&lt;/h2&gt;

&lt;p&gt;I didn’t magically make Next.js faster.&lt;/p&gt;

&lt;p&gt;What changed is simpler and more important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Images arrive earlier. Placeholders disappear sooner. Pages feel visually complete faster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was the slowness I was noticing, and this fixed it.&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you found this useful, you can follow or connect with me here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://x.com/onlyoneaman/" rel="noopener noreferrer"&gt;x.com&lt;/a&gt; &lt;a href="https://www.linkedin.com/in/onlyoneaman/" rel="noopener noreferrer"&gt;linkedin&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>web</category>
      <category>webp</category>
      <category>images</category>
    </item>
    <item>
      <title>I Tested 7 Python PDF Extractors So You Don’t Have To (2025 Edition)</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Thu, 18 Dec 2025 13:14:00 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/i-tested-7-python-pdf-extractors-so-you-dont-have-to-2025-edition-akm</link>
      <guid>https://dev.to/onlyoneaman/i-tested-7-python-pdf-extractors-so-you-dont-have-to-2025-edition-akm</guid>
      <description>&lt;h3&gt;
  
  
  Why This Even Matters
&lt;/h3&gt;

&lt;p&gt;PDF extraction sounds boring until you need it. Then it becomes the bottleneck in everything you’re trying to build.&lt;/p&gt;

&lt;p&gt;Maybe you’re building a document search system and need clean text for indexing. Maybe you’re creating embeddings for a RAG pipeline, and garbage text means garbage vectors. Maybe you’re processing invoices, analysing research papers, or just trying to get data out of that quarterly report someone sent you.&lt;/p&gt;

&lt;p&gt;For small PDFs? Sure, you can often just pass the whole thing to Claude or GPT-4. But when you’re dealing with hundreds of documents, building search systems, or need structured data for processing, that’s when extraction quality actually matters.&lt;/p&gt;

&lt;p&gt;So I decided to test the most popular Python libraries the way most developers would actually use them: minimal setup, basic extraction, real-world document.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Tested
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Document&lt;/strong&gt;: A typical business PDF — one page with headers, body text, a six-column table, and an image. The kind of thing that shows up in email attachments daily. You can find it &lt;a href="https://bella.amankumar.ai/examples/pdf/11-google-doc-document/google-doc-document.pdf" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Environment&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  MacBook M2 Pro, 16GB RAM, macOS 15&lt;/li&gt;
&lt;li&gt;  Fresh Python 3.11 virtual environment&lt;/li&gt;
&lt;li&gt;  Clean pip installs, no optimisations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can find the code for this test &lt;a href="https://gist.github.com/onlyoneaman/a479b3875524d39cee234f013866015e" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing Approach&lt;/strong&gt;: I used the simplest possible implementation for each library — the approach you’d try first when you’re just getting started. Most of these packages have advanced configuration options, specialised table extraction methods, and layout analysis features that could dramatically change results. But I wanted to see what you get with minimal effort.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Measured&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Speed&lt;/strong&gt;: How fast can it process a single page?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Text Quality&lt;/strong&gt;: Is the output readable and properly formatted?&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Table Handling&lt;/strong&gt;: Do tables survive extraction in usable form?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Libraries (How to test quickly &amp;amp; Honest First Impressions)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  pypdfium2 — The Speed Champion
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install pypdfium2
import pypdfium2 as pdfium
text = "\n".join(
    p.get_textpage().get_text_range() 
    for p in pdfium.PdfDocument("doc.pdf")
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Clean, readable text in 0.004 seconds. No formatting, no table structure — just fast, basic extraction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: High-volume processing, simple content indexing, when speed matters more than structure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt; you need any formatting preservation or structured data extraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  pypdf — The Reliable Default
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install pypdf
from pypdf import PdfReader
reader = PdfReader("doc.pdf")
text = "\n".join(p.extract_text() for p in reader.pages)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Solid text extraction with occasional spacing quirks. Works everywhere, no C dependencies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: Lambda functions, containerised apps, environments where you can’t compile extensions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: Text fidelity is critical for your downstream processing.&lt;/p&gt;

&lt;h3&gt;
  
  
  pdfplumber — The Data Extraction Tool
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install pdfplumber
import pdfplumber
with pdfplumber.open("doc.pdf") as pdf:
    first_page = pdf.pages[0]
    text = first_page.extract_text()
    table = first_page.extract_table()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Basic text had some concatenation issues, but the table extraction worked well. This library has extensive options for fine-tuning that I didn’t explore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: When you specifically need tabular data, coordinate-based extraction, or detailed layout control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: You just need clean text without heavy configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  pymupdf4llm — The Markdown Generator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install pymupdf4llm
import pymupdf4llm
markdown = pymupdf4llm.to_markdown("doc.pdf")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Clean markdown output in 0.14 seconds with proper headings and table formatting. Surprisingly good results.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: Content systems, documentation processing, when you need structured text that preserves hierarchy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: You’re dealing with complex multi-column layouts that might get scrambled.&lt;/p&gt;

&lt;h3&gt;
  
  
  unstructured — The Semantic Chunker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install "unstructured[all-docs]"
from unstructured.partition.auto import partition
blocks = partition(filename="doc.pdf")
for block in blocks:
    print(f"{block.category}: {block.text}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Semantically labelled chunks (Title, NarrativeText, etc.) in 1.11 seconds. Perfect for downstream processing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: RAG systems, document analysis, when you need meaningful content boundaries for embeddings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: You just need raw text content without semantic analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  marker-pdf — The Layout Perfectionist
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install marker-pdf
from marker.converters.pdf import PdfConverter
from marker.models import create_model_dict
from marker.output import text_from_rendered
text, _, _ = text_from_rendered(
    PdfConverter(create_model_dict())("doc.pdf")
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Stunning layout-perfect markdown with inline images. Takes 12 seconds and downloads a 1GB model on first run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: When layout fidelity is critical, vision model inputs, and high-quality document conversion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: You’re processing documents in real-time or have resource constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  textract — The Universal Handler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pip install textract  # Requires Tesseract
import textract
text = textract.process("doc.pdf").decode()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What I got&lt;/strong&gt;: Fast extraction (0.05s) with automatic OCR fallback capability. Handles many file formats beyond PDF.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good for&lt;/strong&gt;: Mixed document types, when some files might be scanned, and building robust document processing pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consider if&lt;/strong&gt;: You only handle digital PDFs and want to avoid additional dependencies.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Performance Results
&lt;/h2&gt;

&lt;p&gt;Here’s what actually happened with my test document:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;marker-pdf&lt;/strong&gt; (11.3s): Perfect structure preservation, ideal for high-quality conversions, long time though&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pymupdf4llm&lt;/strong&gt; (0.12s): Excellent markdown output, great balance of speed and quality&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;unstructured&lt;/strong&gt; (1.29s): Clean semantic chunks, perfect for RAG workflows&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;textract&lt;/strong&gt; (0.21s): Fast with OCR capabilities, minor formatting variations&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pypdfium2&lt;/strong&gt; (0.003s): Blazing speed, clean basic text, no structure&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pypdf&lt;/strong&gt; (0.024s): Reliable extraction, occasional spacing artifacts&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;pdfplumber&lt;/strong&gt; (0.10s): Good for tables, text extraction needs configuration&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important caveat&lt;/strong&gt;: These results reflect basic usage with minimal configuration. Each library has advanced features that could significantly change performance for specific use cases. You can find the link to all results in the references.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Context matters more than raw performance&lt;/strong&gt;. The “best” extractor depends entirely on what you’re building and how you’ll use the extracted text.&lt;br&gt;
&lt;strong&gt;Simple often wins&lt;/strong&gt;. For many use cases, basic text extraction is perfectly adequate. Don’t over-engineer unless you actually need the advanced features.&lt;br&gt;
&lt;strong&gt;Test with your data&lt;/strong&gt;. PDF structures vary wildly. What works great on my test document might fail on your quarterly reports.&lt;br&gt;
&lt;strong&gt;Have a fallback plan&lt;/strong&gt;. For production systems, consider hybrid approaches: fast extraction first, and more sophisticated methods for edge cases.&lt;br&gt;
&lt;strong&gt;Advanced features exist&lt;/strong&gt;. This comparison only scratched the surface. Most libraries have configuration options that could completely change the results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next Steps
&lt;/h3&gt;

&lt;p&gt;The scope of this experiment was limited, but it could be useful for most of the basic use cases around extraction. Next, I will try to solve more problems like the ones listed below. If you have anything else we should add, please let me know.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;More Document types (DOC, DOCX, …)&lt;/strong&gt; are also very common document formats we come across, we either need to convert them to some common format or check the package for compatibility with it&lt;/li&gt;
&lt;li&gt;  Handling &lt;strong&gt;Password-Protected PDFs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Dealing with OCR Text&lt;/strong&gt;: PDF files may contain scanned images of text, which cannot be extracted using standard methods. To handle OCR (Optical Character Recognition) text, specialised libraries like pytesseract (a wrapper for Google’s Tesseract OCR engine) can be used to extract text from the images.&lt;/li&gt;
&lt;li&gt;  Even in PDFs / Documents, there are more edge cases like handling images inside documents, OCR, vector drawings, and more.&lt;/li&gt;
&lt;li&gt;  Forms, especially with checkboxes&lt;/li&gt;
&lt;li&gt;  Rotated PDFs&lt;/li&gt;
&lt;li&gt;  Right-to-left script (Arabic/Hebrew).&lt;/li&gt;
&lt;li&gt;  DOCX containing elements like an embedded Excel chart or a floating text box.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;Pick the tool that fits your actual requirements, not the one with the highest benchmark scores.&lt;/p&gt;

&lt;p&gt;For most document processing needs, &lt;strong&gt;pymupdf4llm&lt;/strong&gt; hits the sweet spot of speed and quality. For RAG systems, &lt;strong&gt;unstructured&lt;/strong&gt; gives you better semantic chunks. For pure speed, &lt;strong&gt;pypdfium2&lt;/strong&gt; is hard to beat.&lt;/p&gt;

&lt;p&gt;But honestly? The extraction is usually the easy part. The real work happens in how you process, chunk, and use that text afterwards.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Found different results with your documents or discovered better approaches? I’d love to hear about it — this space moves fast, and real-world feedback keeps comparisons honest.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I hope you have found this article useful 😄. Thank you for reading. I am&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://medium.com/" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; for more articles. Also, let’s connect &lt;a href="https://twitter.com/onlyoneaman" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/onlyoneaman/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;. ☕️&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://bella.amankumar.ai/examples/pdf/11-google-doc-document/google-doc-document.pdf" rel="noopener noreferrer"&gt;https://bella.amankumar.ai/examples/pdf/11-google-doc-document/google-doc-document.pdf&lt;/a&gt; (Test Python File)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/onlyoneaman/a479b3875524d39cee234f013866015e" rel="noopener noreferrer"&gt;https://gist.github.com/onlyoneaman/a479b3875524d39cee234f013866015e&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final Results ZIP: &lt;a href="http://bella.amankumar.ai/examples/pdf/1.zip" rel="noopener noreferrer"&gt;http://bella.amankumar.ai/examples/pdf/1.zip&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/py-pdf/benchmarks?source=post_page-----c88013922257---------------------------------------" rel="noopener noreferrer"&gt;GitHub - py-pdf/benchmarks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pymupdf.readthedocs.io/en/latest/pymupdf4llm/?source=post_page-----c88013922257---------------------------------------" rel="noopener noreferrer"&gt;pymupdf.readthedocs.io&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/marker-pdf/?source=post_page-----c88013922257---------------------------------------" rel="noopener noreferrer"&gt;marker-pdf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://pypi.org/project/aiwand/?source=post_page-----c88013922257---------------------------------------" rel="noopener noreferrer"&gt;aiwand - pypi.org&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://amankumar.ai/?source=post_page-----c88013922257---------------------------------------" rel="noopener noreferrer"&gt;amankumar.ai&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>extraction</category>
    </item>
    <item>
      <title>Export Your Brain: A Simple Way To Make Any AI “Know You” From Day One</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Wed, 17 Dec 2025 06:24:56 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/export-your-brain-a-simple-way-to-make-any-ai-know-you-from-day-one-2bnd</link>
      <guid>https://dev.to/onlyoneaman/export-your-brain-a-simple-way-to-make-any-ai-know-you-from-day-one-2bnd</guid>
      <description>&lt;p&gt;Every new AI tool treats you like a stranger.&lt;/p&gt;

&lt;p&gt;New chat. New model. New product.&lt;br&gt;
Same ritual:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“I’m a dev, I use React/TS/Tailwind.”&lt;/li&gt;
&lt;li&gt;“Keep it short, show code first.”&lt;/li&gt;
&lt;li&gt;“Don’t give me motivational quotes.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You’ve already trained one AI on all of this.&lt;br&gt;
It knows your stack, your tone, your quirks, your life context.&lt;/p&gt;

&lt;p&gt;The question is: &lt;strong&gt;how do you steal that “you” and carry it anywhere?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That’s what this article is about:&lt;br&gt;
Not “exporting ChatGPT data”, but &lt;strong&gt;exporting your persona&lt;/strong&gt; so any AI, any agent, any system can plug into it.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Real Problem: Your Context Is Trapped
&lt;/h2&gt;

&lt;p&gt;We’re in a multi-AI world:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ChatGPT for ideation / debugging&lt;/li&gt;
&lt;li&gt;Claude / Gemini for writing&lt;/li&gt;
&lt;li&gt;Cursor / Copilot / Replit for coding&lt;/li&gt;
&lt;li&gt;Internal bots at work&lt;/li&gt;
&lt;li&gt;Random tools for notes, docs, email, scheduling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one gets a tiny slice of you.&lt;br&gt;
None of them truly “know you”.&lt;/p&gt;

&lt;p&gt;Your preferences, your working style, your constraints, your goals – they’re all split across chats, tabs, and products.&lt;/p&gt;

&lt;p&gt;What we actually want:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“My AI context should be as portable as my email address.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Until the tools natively support that, we hack it:&lt;br&gt;
we create a &lt;strong&gt;Persona Memo&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Idea: A Persona Memo You Can Plug Anywhere
&lt;/h2&gt;

&lt;p&gt;Instead of trying to export raw chat history, you ask your main AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Look at everything you know about me.&lt;br&gt;
Compress it into a memo + JSON that any other system can use to work with me.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This does three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Gives &lt;em&gt;you&lt;/em&gt; a clean “Operating Manual” of yourself.&lt;/li&gt;
&lt;li&gt;Gives &lt;em&gt;other AIs&lt;/em&gt; a high-signal context to start from.&lt;/li&gt;
&lt;li&gt;Works &lt;strong&gt;across tools&lt;/strong&gt;: ChatGPT, Claude, Grok, Gemini, Cursor, internal agents, whatever.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You run one good prompt → get back a human-readable memo + machine-readable persona.&lt;/p&gt;

&lt;p&gt;That’s your portable brain.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Master Prompt (Use This On Whatever Knows You Best)
&lt;/h2&gt;

&lt;p&gt;Take this and paste it into the AI where you’ve spent the most time (ChatGPT, Claude, etc.):&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;




&lt;p&gt;What you get back:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Operating Manual&lt;/strong&gt; – human-readable, like a one-pager about “How to work with ”.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persona JSON&lt;/strong&gt; – machine-readable, ready to be plugged into agents, system prompts, configs, etc.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is already a massive upgrade over “Hi, I like React and short answers”.&lt;/p&gt;




&lt;h2&gt;
  
  
  How To Use This Persona In The Wild
&lt;/h2&gt;

&lt;p&gt;Once you’ve generated it, you now have a &lt;strong&gt;portable context pack&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Start any new AI chat like this&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;“Here’s my persona. Assume all of this as background whenever you answer.”&lt;br&gt;
&lt;em&gt;[paste Operating Manual or a trimmed version]&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Set it as “custom instructions” / “system prompt”&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Many tools let you define persistent instructions.&lt;br&gt;
   Drop your memo there and stop repeating yourself.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Wire it into your own agents&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Store the JSON in your DB or config&lt;/li&gt;
&lt;li&gt;Load it into your backend when instantiating an LLM&lt;/li&gt;
&lt;li&gt;Prepend it in the system or developer prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your internal bots now behave like they’ve worked with you for months.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Use it as onboarding material&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can even share a cleaned-up version with collaborators:&lt;br&gt;
   “Here’s how I work, this will save both of us time.”&lt;/p&gt;


&lt;h2&gt;
  
  
  Keep It Tight: Version, Don’t Worship It
&lt;/h2&gt;

&lt;p&gt;A few practical notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Update it every 1–3 months&lt;/strong&gt;&lt;br&gt;
Your stack, goals, priorities shift. Rerun the prompt or manually edit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Treat it like config, not scripture&lt;/strong&gt;&lt;br&gt;
If something feels wrong, change it. This is &lt;em&gt;your&lt;/em&gt; OS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Be selective with where you paste it&lt;/strong&gt;&lt;br&gt;
Don’t feed deeply personal / sensitive stuff to random SaaS forms.&lt;br&gt;
Keep a “public-safe” persona and a more detailed internal one if needed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Quick “Lite” Prompt (When You Don’t Need JSON)
&lt;/h2&gt;

&lt;p&gt;Sometimes you just want a small memo you can paste into a one-off tool.&lt;/p&gt;

&lt;p&gt;Here’s a shorter version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an expert integration developer and personal-knowledge summariser.

Based on everything you know about me in this workspace, create a concise "Operating Manual" for working with me that I can paste into ANY AI tool.

Include sections:
- Who I am (1–2 lines)
- How I work best
- How to talk to me
- My tech / domain preferences
- What to double-check with me
- Things to avoid
- What I’m currently optimising for

Maximum 350 words. Use direct, confident language. Prioritise stable patterns over one-off details.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is your “carry in pocket” version.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Point Of All This
&lt;/h2&gt;

&lt;p&gt;You’re already training these models every day just by talking to them.&lt;/p&gt;

&lt;p&gt;Might as well &lt;strong&gt;capture&lt;/strong&gt; that and reuse it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One good prompt&lt;/li&gt;
&lt;li&gt;One persona memo&lt;/li&gt;
&lt;li&gt;Works across models, products, and your own agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So next time you open a new AI tool, you’re not introducing yourself.&lt;/p&gt;

&lt;p&gt;You’re just handing over your Operating Manual and saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Start here. We’ve got work to do.”&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>prompt</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>How to Make Rails Response Faster</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Thu, 30 Mar 2023 20:16:17 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/how-to-make-rails-response-faster-19ff</link>
      <guid>https://dev.to/onlyoneaman/how-to-make-rails-response-faster-19ff</guid>
      <description>&lt;p&gt;As your document or response size increases 📈, it can result in much slower response time 📈 📈 , even in seconds 😿 which results in a pretty bad user experience. Speed is the key here 🚤, the quicker your site is the more your users like it. PageSpeed and Responses Size are also responsible for SEO Rankings and is a major part of &lt;a href="https://developers.google.com/speed" rel="noopener noreferrer"&gt;LightSpeed&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the below section, we explore how this is achieved and tested, if you have a code to fix 🐛, Just Jump to Implementation here and check this out later.&lt;/p&gt;

&lt;h3&gt;
  
  
  So, How do we achieve this?
&lt;/h3&gt;

&lt;p&gt;I found Rack::Deflater recently, and regret how I wasn’t using it for a long time. I experimented over Rack::Deflater by sending a heavy json as response and comparing the performance of rails app with and without Rack::Deflater . Below we can see the Package Size of 8.3 Mb which was transferred without Rack::Deflater and 359 Kb which was transferred with Rack::Deflater. The Actual Factor and the ratio of Compression can vary a bit, it consistently results in a faster response, thus undoubtedly Rack::Deflater does makes the user experience better by compressing and sending resources.&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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AKbgjoCoVJFU89m8W876fEw.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%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AKbgjoCoVJFU89m8W876fEw.png" alt="Size of package transferred over network with and without Rack::Deflater" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So how does Rack::Deflater does this. Rackk Deflate compresses the body of web page before responding to a request. When the response is then received by the client, it sees the gzip compression enabled via the header Content-Encoding=gzip and unzips it before rendering it. Though it sounds like a lot of trouble, remember generally CPUs are fast, and networks are slow. Thus, It’s much faster and more efficient to just send less data “over the internet” even if we spend time compressing and expanding that data.&lt;/p&gt;

&lt;p&gt;“Transferred” is the compressed size of all resources. You can think of it as the amount of upload and download data that a mobile user will use in order to load this page. “Resources” is the uncompressed size of all resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  Implementation
&lt;/h3&gt;

&lt;p&gt;Just add the below line in your application.rb file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*config.middleware.use Rack::Deflater*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your file should be like this after it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;....
class Application &amp;lt; Rails::Application
  ...
  *config.middleware.use Rack::Deflater
  ...
end
....*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  TroubleShooting
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Check if you have placed the middleware line correctly in code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you are using ActionDispatch::Static in your app. Then,Rack::Deflater should be placed after ActionDispatch::Static. The reasoning is that if your app is also serving static assets (e.g., Heroku), when assets are served from disk they are already compressed. Inserting it before would only end up in Rack::Deflater attempting to re-compress those assets. Therefore as a performance optimisation:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    # application.rb

    config.middleware.insert_after ActionDispatch::Static, Rack::Deflater 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;If you are using any other middleware it might conflict withRack::Deflater , it should work if you use insert_before (instead of "use"), to place it near the top of the middleware stack, prior to any other middleware that might send a response. .use places it at the bottom of the stack. Let’s say, the topmost middleware is Rack::Sendfile. So we would use:&lt;/p&gt;

&lt;p&gt;config.middleware.insert_before(Rack::Sendfile, Rack::Deflater) &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can get the list of middleware in order of loading by doing rake middleware from the command line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you have found this useful 😄. Thank you for reading.&lt;br&gt;
Drop a Response or 📬 if you have any queries or just wanna connect ☕️.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
    </item>
    <item>
      <title>React Hooks and Lifecycle Methods</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Wed, 29 Mar 2023 09:19:16 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/react-hooks-and-lifecycle-methods-1kdo</link>
      <guid>https://dev.to/onlyoneaman/react-hooks-and-lifecycle-methods-1kdo</guid>
      <description>&lt;p&gt;React State and Lifecycle are very useful methods and with the advancement of React hooks and when a developer uses hooks instead of traditional React classes the most important question becomes how one is gonna implement the lifecycle methods offered by React classes in Hooks. We will look after the Hooks implementation of various lifecycle methods in this blog.&lt;/p&gt;

&lt;p&gt;If you are new to state and lifecycle, Have a look at the official docs.&lt;br&gt;
&lt;a href="https://reactjs.org/docs/state-and-lifecycle.html" rel="noopener noreferrer"&gt;React State and Lifecycle&lt;/a&gt;. Briefly, these are the methods which can be useful if you wanna execute a function when a component is mounted or unmounted or at every render of the component.&lt;/p&gt;

&lt;p&gt;But we cannot use any of the existing lifecycle methods (componentDidMount, componentDidUpdate, componentWillUnmount etc.) in a hook. They can only be used in class components. And with Hooks, you can only use in functional components. The line below comes from the React doc:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h1&gt;
  
  
  &lt;em&gt;If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.&lt;/em&gt;
&lt;/h1&gt;
&lt;/blockquote&gt;

&lt;p&gt;suggest is, you can mimic these lifecycle methods from a class component in a functional component.&lt;/p&gt;

&lt;h2&gt;
  
  
  ComponentDidMount
&lt;/h2&gt;

&lt;p&gt;Code inside &lt;strong&gt;componentDidMount&lt;/strong&gt; runs once when the component is mounted. useEffect hook equivalent for this behaviour is&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // Your code here
}, []);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;Notice the second parameter here (empty array). This will run only once.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ComponentDidUpdate
&lt;/h2&gt;

&lt;p&gt;Without the second parameter the useEffect hook will be called on every render of the component.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  // Your code here
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  ComponentWillUnmount
&lt;/h2&gt;

&lt;p&gt;componentWillUnmount is used for cleanup (like removing event listeners, cancel the timer etc). Say you are adding an event listener in componentDidMount and removing it in componentWillUnmount as below.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;componentDidMount() {
  window.addEventListener('mousemove', () =&amp;gt; {})
}

componentWillUnmount() {
  window.removeEventListener('mousemove', () =&amp;gt; {})
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Hooks equivalent of the above code will be as follows&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useEffect(() =&amp;gt; {
  window.addEventListener('mousemove', () =&amp;gt; {});

  // returned function will be called on component unmount
  return () =&amp;gt; {
    window.removeEventListener('mousemove', () =&amp;gt; {})
  }
}, [])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Hope this was helpful 😄&lt;/p&gt;

</description>
      <category>react</category>
      <category>reacthooks</category>
    </item>
    <item>
      <title>Unlocking the Power of Redis: Storing Any Object as Cache in Ruby on Rails</title>
      <dc:creator>Aman</dc:creator>
      <pubDate>Tue, 28 Mar 2023 16:12:26 +0000</pubDate>
      <link>https://dev.to/onlyoneaman/unlocking-the-power-of-redis-storing-any-object-as-cache-in-ruby-on-rails-929</link>
      <guid>https://dev.to/onlyoneaman/unlocking-the-power-of-redis-storing-any-object-as-cache-in-ruby-on-rails-929</guid>
      <description>&lt;h3&gt;
  
  
  Unlock the full potential of Redis for storing any object as cache in Ruby on Rails. Learn how Redis works as a cache, how we can store classes and prevent threads fighting over single Redis connection.
&lt;/h3&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.amazonaws.com%2Fuploads%2Farticles%2Fdw6geki0b0a1bbvwjbq6.jpeg" 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.amazonaws.com%2Fuploads%2Farticles%2Fdw6geki0b0a1bbvwjbq6.jpeg" alt="Unlocking the Power of Redis: Storing Any Object as Cache in Ruby on Rails" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Redis is an open-source, in-memory data structure store that can be used for caching, messaging, and real-time analytics. It has become increasingly popular in recent years because it is extremely fast, scalable, and easy to use. In this blog, we will be focusing on using Redis as a cache for storing any object in Ruby on Rails. We will explore the many benefits of using Redis as a cache and demonstrate how to integrate it into your Ruby on Rails application. We will also look into storing complex classes and arrays into redis efficiently, and finally how can we prevent different threads fighting over single redis connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Redis?
&lt;/h3&gt;

&lt;p&gt;Redis stands for Remote Dictionary Server. It is an in-memory data structure store that is used as a database, cache, and message broker. It is an open-source project written in ANSI C. Redis supports a wide range of data structures such as strings, hashes, lists, sets, and sorted sets. Its amazing speed comes from its ability to keep the data in memory, which allows read/write operations to be performed with incredibly low latency.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Use Redis as a Cache?
&lt;/h3&gt;

&lt;p&gt;Caching is a mechanism for improving the performance of your application by storing frequently accessed data in memory. This reduces the number of times your application has to access the database, which in turn reduces the response time of your application. Redis is an excellent choice for caching because it is incredibly fast and can store any type of data. Redis is also very easy to use and can be integrated into your Ruby on Rails application with minimal effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Redis Works as a Cache
&lt;/h3&gt;

&lt;p&gt;Redis works by storing the data in key-value pairs in memory. When you request data from Redis, it checks if the data is already in memory, and if it is, it returns the data from memory. If the data is not in memory, Redis will fetch it from the database, store it in memory, and return it to you. This process is known as caching. When you modify the data, Redis will write the changes to the database and update the cached version in memory. This ensures that the cached version is always updated with the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Use Redis in Ruby on Rails
&lt;/h3&gt;

&lt;p&gt;Using Redis in Ruby on Rails is very easy. First, you need to add the Redis gem to your Gemfile:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'redis'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, you need to configure Redis in your environment file.&lt;br&gt;
Initialize a redis.rb file inside config/initializers with the below content.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REDIS_CLIENT = Redis.new(url: "YOUR_REDIS_URL", timeout: 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This configuration sets up Redis as the REDIS_CLIENT for your application. You can then use REDIS_CLIENT.set and REDIS_CLIENT.get to store and retrieve data from Redis. Here is an example of how to use Redis to cache data:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = {test: "hello"}
REDIS_CLIENT.set("test_key", data, ex: 60)
cached_user = REDIS_CLIENT.get("test_key")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this example, we are caching the object with an expiration time of 1minutes. When we retrieve the object from the cache, we get the cached version if it is still within the 1-minute expiration time, and if not, we fetch it from the database and store it in the cache again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Storing Objects (Arrays / JSON / other classes) in redis.
&lt;/h3&gt;

&lt;p&gt;Storing plain hashes or simple objects like strings, numbers is straightforward. But things get complicated as we try storing different classes or arrays using redis.&lt;/p&gt;

&lt;p&gt;E.g.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = User.first
REDIS_CLIENT.set("test_key", data, ex: 60)
cached_user = REDIS_CLIENT.get("test_key")
# Result -&amp;gt;  "#&amp;lt;V1User:0x000055e224d30520&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, We need attributes like email, name, id associated with User to get stores in cache, but Redis stored the class instance simple. To avoid this, we need to convert such classes into attributes and pass a simple hash to Redis instead of Class.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = User.first
data_to_store = JSON.dump(data.attributes)
REDIS_CLIENT.set("test_key", data_to_store, ex: 60)
cached_user = REDIS_CLIENT.get("test_key")
proper_cached_user = JSON.parse(cached_user)
# Result -&amp;gt;   {"id"=&amp;gt;1, "email"=&amp;gt;"aman@gmail.com"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Similarly, there could be attributes which contain classes as a child, Let’s create a function which drills down below the object, converting it suitable to be stored in Redis.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def create_redis_cache
  key = "test_key"
  items = [User.first, User.second, {test: User.third}]
  items_to_store = attributes_from_item(items)
  REDIS_CLIENT.set(key, JSON.dump(items_to_store), ex: 60)
end

def get_redis_cache
  key = "test_key"
  items = REDIS_CLIENT.get(key)
  final_items = JSON.parse(items)
end

 def attributes_from_item(item)
    return item unless item.respond_to?(:attributes)
    new_item = {}
    item.attributes.each do |k, v|
      if v.is_a?(Hash)
        new_item[k] = attributes_from_item(v)
      elsif v.is_a?(Array)
        new_item[k] = v.map{|e| attributes_from_item(e)}
      else
        new_item[k] = v
      end
    end
    new_item
  end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use the above create_redis_cache and get_redis_cache methods to store and retrieve cache with any data type without worrying about validity of cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managing Concurrency
&lt;/h3&gt;

&lt;p&gt;When deploying rails, we often have a side worker like sidekiq, which runs parallel, doing tasks without disturbing the web application. When sidekiq opens many threads, your redis can start misbehaving due to the high number of open connections (sidekiq threads + puma threads) threads fighting over one connection (since the Redis Client only runs one command at a time, using a Monitor.). To solve this, You use a separate global connection pool for your application code.&lt;/p&gt;

&lt;p&gt;Add connection_pool to your Gemfile.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gem 'connection_pool'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and then create limited pools in redis like this in your redis.rb initializer:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require 'connection_pool'

REDIS = ConnectionPool.new(size: 10) { Redis.new, timeout: 1 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This ensures that even if you have lots of concurrency, you’ll only have 10 connections open to memcached per Sidekiq process.&lt;/p&gt;

&lt;p&gt;Now in your application code anywhere, you can do this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REDIS.with do |conn|
  # some redis operations
  r = conn.get(redis_key)
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You’ll have up to 10 connections to share amongst your puma/sidekiq workers. This will lead to better performance since, as you correctly note, you won’t have all the threads fighting over a single Redis connection.&lt;/p&gt;

&lt;p&gt;If you have survived till here, here is an &lt;a href="https://onlyoneaman.medium.com/how-to-make-rails-response-faster-a8cc5f1242d" rel="noopener noreferrer"&gt;article&lt;/a&gt; which will help you decrease your API Response time multi-folds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Features of Redis
&lt;/h3&gt;

&lt;p&gt;Redis is an incredibly versatile tool and has many advanced features that can be used to improve performance and functionality. Here are some of the most useful advanced features of Redis:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Pub/Sub&lt;br&gt;
Redis supports Publish/Subscribe messaging which allows you to set up a messaging system between your application and other external sources. This feature can be used to push data from your application to external sources or to receive data from external sources in real-time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lua Scripting&lt;br&gt;
Redis supports Lua scripting which allows you to execute complex operations on the server-side. This can be used to perform complex calculations, transformations, or data manipulations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Transactions&lt;br&gt;
Redis supports transactions which allow you to execute multiple commands in a single operation. This feature is useful when you need to ensure that a set of commands are executed atomically, meaning that either all of them are executed or none of them are executed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TTL&lt;br&gt;
Redis allows you to set an expiration time for the data in the key-value pair. This is known as Time to Live (TTL). When the TTL expires, Redis automatically removes the key-value pair from memory. This feature can be used to reduce memory usage and to ensure that the cached data is always up to date.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Redis is an excellent choice for caching frequently accessed data in your Ruby on Rails application. It is fast, scalable, and easy to use. Redis can store any type of data in memory and can be integrated into your application with minimal effort. It has many advanced features that can be used to improve performance and functionality. Using Redis as a cache can significantly improve the response time of your application and reduce the load on your database. Redis is a powerful tool that can help your application keep up with the demands of your users and stay ahead of the competition.&lt;/p&gt;

&lt;p&gt;Did the article help or anything else you would like to suggest or add? Add a response or drop a message below to me. 😉&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://onlyoneaman.medium.com/" rel="noopener noreferrer"&gt;Medium&lt;/a&gt; for more articles. Also, Let’s connect if we haven’t yet &lt;a href="https://twitter.com/onlyoneaman" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, &lt;a href="https://www.linkedin.com/in/onlyoneaman/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://onlyoneaman.medium.com/how-to-make-rails-response-faster-a8cc5f1242d" rel="noopener noreferrer"&gt;&lt;strong&gt;How to Make Rails Response Faster&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/mperham/sidekiq/wiki/Advanced-Options#connection-pooling" rel="noopener noreferrer"&gt;&lt;strong&gt;Advanced Options&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/redis/redis-rb" rel="noopener noreferrer"&gt;&lt;strong&gt;GitHub - redis/redis-rb: A Ruby client library for Redis&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://api.rubyonrails.org/v7.0.4/classes/ActiveSupport/Cache/Store.html" rel="noopener noreferrer"&gt;&lt;strong&gt;ActiveSupport::Cache::Store&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redis.io/docs/" rel="noopener noreferrer"&gt;&lt;strong&gt;Documentation&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
