<?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: Dmitry Drepin</title>
    <description>The latest articles on DEV Community by Dmitry Drepin (@dmitryd).</description>
    <link>https://dev.to/dmitryd</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%2F717641%2Fb28f0113-8486-4f82-87ea-22535d0d178c.jpeg</url>
      <title>DEV Community: Dmitry Drepin</title>
      <link>https://dev.to/dmitryd</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dmitryd"/>
    <language>en</language>
    <item>
      <title>Spring AI: An Engineer’s Answer to the HR Black Hole</title>
      <dc:creator>Dmitry Drepin</dc:creator>
      <pubDate>Mon, 15 Sep 2025 00:05:16 +0000</pubDate>
      <link>https://dev.to/dmitryd/an-engineers-answer-to-the-hr-black-hole-4kkg</link>
      <guid>https://dev.to/dmitryd/an-engineers-answer-to-the-hr-black-hole-4kkg</guid>
      <description>&lt;h2&gt;
  
  
  Building a local AI candidate agent with RAG, Spring AI, and Ollama
&lt;/h2&gt;

&lt;p&gt;When you’re a candidate flooded with dozens of offers and messages every week, it’s nearly impossible to filter, prioritize, and respond smartly. Some opportunities deserve attention, but many don’t match your skills, goals, or expectations. Without a tool to manage this flow, your voice gets lost in the noise — and you risk missing the right role.&lt;br&gt;
That’s why I built a prototype of local-first AI assistant: not to replace interviews or negotiations, but to help candidates filter irrelevant offers early, highlight what matters, and keep control of their data.&lt;br&gt;
It was built to represent a candidate during pre-screening: automate repetitive Q&amp;amp;A (availability, salary range, basic skills), surface relevant opportunities, and schedule interviews — all while keeping candidate data local. This tool is explicitly for prescreening automation, not for replacing human interviews or “cheating.” Below I explain how the system is built, why the design choices matter, how components connect, and what consequences those choices have.&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%2F8g2cgu63u78twbka2gtq.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%2F8g2cgu63u78twbka2gtq.png" alt=" " width="800" height="1200"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  1. High-level goal &amp;amp; motivation
&lt;/h2&gt;

&lt;p&gt;I needed a way to rapidly filter and respond to dozens/hundreds of inquiries (think LinkedIn volume) without manually replying to each. The prototype’s aims were:&lt;br&gt;
• Give the candidate a consistent, privacy-preserving voice for pre-screening.&lt;br&gt;
• Automate repetitive tasks so HR and candidates get to real interviews faster.&lt;br&gt;
• Keep all sensitive data local (no third-party cloud inference by default).&lt;br&gt;
• Build something runnable and useful in a week — a practical POC, not a product.&lt;br&gt;
That constraint (local, fast, privacy-first) shaped every technical decision below.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. System architecture (five layers)
&lt;/h2&gt;

&lt;p&gt;I designed the prototype as a modular stack with clear separation of concerns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------------------------------------------+
|                        Candidate Frontend                     |
|  (web app, chat, CLI)                                         |
+------+------------------+-----------------+-------------------+
       |                  |                 |                   
       v                  v                 v                   
+-------------------+  +---------------------------+     
|  Spring Boot REST |  |   Streaming WebSocket     |     
|  (Spring AI App)  |  |      (Optional)          |     
+-------------------+  +--------------------------+      
       |                                              
       v                                              
+---------------------+         +-------------------+
|   Spring AI Layer   +--------&amp;gt;|   Tool/Advisor    |
|      (ChatClient,   |         |    Pattern        |
|   Advisors, RAG,    |         +-------------------+
|   Memory, Tooling)  |                 |
+---------------------+                 |
       |                (tool calls)    |
       v                 -------------- +---------------+
+-------------------+       +-------------------------+
|   Ollama Server   | &amp;lt;---&amp;gt; |     Vector Store/RAG    |
|   (Local LLM API) |       |  (ChromaDB/Pinecone/...)|
+-------------------+       +-------------------------+

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Backend&lt;/strong&gt; — Java 21+, Spring Boot, Spring REST, Spring Data/Hibernate.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Frontend&lt;/strong&gt; — React + Vite with Zustand; SSE (Server-Sent Events) for streaming responses.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Ollama (Local LLM runtime)&lt;/strong&gt; — hosts embedding and generative models locally and defines compute usage (CPU/GPU).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Data layer&lt;/strong&gt; — PostgreSQL with pgvector for embeddings and PostgresChatMemory for chat context.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Infrastructure / Postgres ops&lt;/strong&gt; — Docker / Docker Compose (Kubernetes-ready), Nginx reverse proxy, pgvector tuning and backups.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each part represents a responsibility; the system is designed so each piece can be replaced or scaled independently. &lt;/p&gt;

&lt;p&gt;The overall request path is:&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%2F2il5xe10vsyt6bhknob1.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%2F2il5xe10vsyt6bhknob1.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Core design pattern: advisors (Chain of Responsibility)
&lt;/h2&gt;

&lt;p&gt;Spring AI provides an advisors mechanism effectively — a Chain of Responsibility. In my app, &lt;code&gt;AdvisorsProvider&lt;/code&gt; is the single place where the chain is assembled and configured (system prompts, model configuration, chat memory, and each advisor tuning).&lt;/p&gt;

&lt;p&gt;Why use this? Because pre-screening conversation requires multiple small, ordered steps: expand the query, attach history, fetch facts, rerank, log, and finally generate. The chain makes that sequence explicit and easy to extend and manage. &lt;br&gt;
Each advisor:&lt;br&gt;
• Receives the request and context,&lt;br&gt;
• May read or write to the context (this is MCP-ready behavior),&lt;br&gt;
• May call out to external services (vector store, reranker),&lt;br&gt;
• Passes a modified request to the next advisor.&lt;br&gt;
This modularity makes it safe to add compliance checks, sentiment analysis, or anything else without changing the core &lt;code&gt;ChatClient&lt;/code&gt; logic.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. End-to-end workflow (concrete example)
&lt;/h2&gt;

&lt;p&gt;I'll follow one HR question end-to-end to make the flow concrete.&lt;br&gt;
&lt;strong&gt;HR:&lt;/strong&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is your salary expectations?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Flow:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A. UI → Spring REST&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;HR types the question; frontend opens an SSE connection to the backend endpoint &lt;code&gt;/chat/stream?question=....&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. ChatService wraps a &lt;code&gt;ChatClientRequest&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contains the original question,&lt;/li&gt;
&lt;li&gt;pulls recent session context from &lt;code&gt;PostgresChatMemory (max N messages)&lt;/code&gt;,&lt;/li&gt;
&lt;li&gt;sets up a response SSE emitter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;C. Advisors Chain (in order):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ExpansionQueryAdvisor&lt;/code&gt;: expands the question into a richer search query: &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;salary range expected compensation benefits negotiation developer role Europe Austria 5+ years experience Quarkus experience must&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Purpose&lt;/strong&gt;: increase RAG recall for position-specific facts.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;MessageChatMemoryAdvisor&lt;/code&gt;: consider last messages; ensures multi-turn context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SimpleLoggerAdvisor&lt;/code&gt;: logs query for observability (and metrics: RAG hits/misses).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;RagAdvisor&lt;/code&gt;: core retrieval logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use expanded query for &lt;code&gt;pgvector&lt;/code&gt; similarity search (vector store).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BM25&lt;/code&gt; rerank to boost short, high-precision documents.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Neural cross-encoder rerank&lt;/code&gt; (optional) for top-N results.&lt;/li&gt;
&lt;li&gt;Cache top results in a &lt;code&gt;ConcurrentHashMap&lt;/code&gt; to avoid repeated expensive retrieval.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FinalLoggerAdvisor&lt;/code&gt;: logs final documents passed to the model.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;D. Generative model (Spring AI ChatClient → Ollama):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;ChatClient&lt;/code&gt; submits prompt + retrieved context to the local Ollama model with configured options (temperature, topK, topP, repeatPenalty, model).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Model returns text incrementally; ChatClient streams tokens back over SSE.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;E. UI: HR gets a streamed, polished candidate reply:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Based on my experience and location, my expected salary range is 80–90k EUR. RECOMMENDATION: YES — aligns with the role.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;F. Memory update:&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;PostgresChatMemory&lt;/code&gt; persists the turn (question, expanded query, final response, RAG references). If MCP exists, it will version this turn and store document references.&lt;/p&gt;
&lt;h2&gt;
  
  
  5. The &lt;code&gt;RAGAdvisor&lt;/code&gt; internals and consequences
&lt;/h2&gt;

&lt;p&gt;What RAG does here, in details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Vector Search (pgvector)&lt;/code&gt;: I precompute embeddings for each candidate artifact (CV, cover letter, past notes, detailed candidate description, etc) at startup. A similarity search returns a candidate set for the expanded query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: Pre-embedding speeds retrieval, but embedding model choice (and embedding dimensionality) affects disk and RAM usage.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BM25 Rerank&lt;/code&gt;: BM25 provides keyword-based reweighting with tunable parameters:
&lt;code&gt;k (saturation)&lt;/code&gt; — how term frequency saturates,
&lt;code&gt;b&lt;/code&gt; (document length normalization),
&lt;code&gt;delta&lt;/code&gt; — a small boost to favor short documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: Good for short snippets (e.g., "expected: 80k"), BM25 is fast and cheap.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Neural rerank (cross-encoder)&lt;/code&gt;: Reorders top candidates by scoring query-document pairs jointly with a transformer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: Much higher CPU/GPU cost but increases precision when top N is noisy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Caching: If a query repeats (session-level), cache saves the reranked docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Consequence&lt;/strong&gt;: Faster responses and lower cost on repeated interactions; careful TTL and invalidation policies are required for correctness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it affects the model output&lt;/strong&gt;&lt;br&gt;
The generative model receives a compact &lt;strong&gt;“context bundle”&lt;/strong&gt; (top-K documents + system prompt + conversation history). If the RAG stack is precise and recall is high, the model produces factual answers grounded in candidate data. If RAG returns weak or unrelated documents, the model may hallucinate or produce vague responses — hence the importance of thresholds and reranking.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Embeddings layer and Postgres (&lt;code&gt;pgvector&lt;/code&gt;) considerations
&lt;/h2&gt;

&lt;p&gt;I use &lt;code&gt;mxbai-embed-large&lt;/code&gt; to encode candidate documents into vector embeddings and store them in PostgreSQL with pgvector.&lt;br&gt;
At startup, documents are converted and indexed; updates are supported (new CV versions/writes flush and re-embed).&lt;br&gt;
Similarity threshold filters irrelevant docs.&lt;/p&gt;
&lt;h3&gt;
  
  
  Embeddings &amp;amp; Postgres (pgvector) — Why They Matter
&lt;/h3&gt;

&lt;p&gt;When HR asks a question like “Do you have 5 years of Kubernetes experience?”, the system can’t just do a text search across your CV. Plain SQL queries or LIKE %Kubernetes% are too literal — they miss meaning, synonyms, and context.&lt;br&gt;
That’s where embeddings come in.&lt;br&gt;
Think of an embedding as &lt;strong&gt;a mathematical fingerprint&lt;/strong&gt; of a sentence or paragraph. Instead of storing words, we convert the text into a long vector of numbers — usually hundreds of dimensions. Texts that “mean” the same thing will have fingerprints that are close to each other in vector space.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“5 years of Kubernetes”&lt;/li&gt;
&lt;li&gt;“Half a decade running K8s clusters in production”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two sentences look very different in raw text but sit right next to each other when turned into embeddings.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why pgvector?
&lt;/h3&gt;

&lt;p&gt;Postgres is our database backbone, and pgvector is just an extension that lets Postgres understand and store these big vectors. More importantly, it can do vector similarity search — i.e., “find me the top 3 paragraphs in my CV that are closest in meaning to this recruiter’s question.”&lt;br&gt;
So instead of string-matching, we ask:&lt;br&gt;
“Which stored embeddings are nearest neighbors to the embedding of this recruiter’s question?”&lt;br&gt;
That gives us the right chunk of context to feed into the LLM, so the response is grounded in truth instead of hallucination.&lt;/p&gt;
&lt;h3&gt;
  
  
  Practical considerations
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Indexing&lt;/strong&gt;: pgvector supports indexes like IVFFlat that make similarity search fast even if you store millions of vectors. For my prototype (CVs, notes, prior chats), it’s small scale, but the same design scales to enterprise data.&lt;br&gt;
&lt;strong&gt;Chunking strategy&lt;/strong&gt;: documents must be split into sensible paragraphs or sections before embedding. &lt;strong&gt;Too big&lt;/strong&gt; = expensive + &lt;strong&gt;fuzzy&lt;/strong&gt;. &lt;strong&gt;Too small&lt;/strong&gt; = &lt;strong&gt;loses context&lt;/strong&gt;.&lt;br&gt;
&lt;strong&gt;Cost &amp;amp; performance tradeoff&lt;/strong&gt;: Every embedding call is extra compute. A local embedding model (like from Ollama) keeps it cheap, while API-based embeddings cost more but can be higher quality.&lt;br&gt;
So the embeddings layer is essentially the search engine brain of this system. Postgres + pgvector is the memory warehouse, embeddings are the fingerprints, and similarity search is how we fetch the right memories before answering HR.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Ollama &amp;amp; model parameter math (inline, practical + intuition)
&lt;/h2&gt;

&lt;p&gt;I run both embedding and generative models via Ollama locally. When configuring Ollama in the Spring AI ChatClient, a few parameters completely change how the model behaves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatClient.builder(chatModel)
    .defaultOptions(OllamaOptions.builder()
        .temperature(expansionQueryTemperature)
        .topK(expansionQueryTopK)
        .topP(expansionQueryTopP)
        .repeatPenalty(expansionQueryRepeatPenalty)
        .model(modelName)
        .build())
    .build()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, here’s the thing: each of these parameters shapes the model’s behavior in a very tangible way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;temperature&lt;/strong&gt; is like the creativity dial. With a low value the model always picks the “safest” word, so responses sound factual and consistent. Push it higher and the model starts getting creative, even a bit unpredictable. For prescreening, I keep it low so the agent doesn’t invent benefits or experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;topK&lt;/strong&gt; controls how many tokens the model can even look at. If you set it to 1, it’s basically locked to one possible choice every time. If you open it up (say 20–40), it can still vary phrasing without going off track.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;topP&lt;/strong&gt; works a bit differently: instead of counting words, it says “include just enough words until you cover, for example, 90% of the probability mass.” It makes responses more natural than just &lt;strong&gt;topK&lt;/strong&gt; alone. I usually pair it with topK for balance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;repeatPenalty&lt;/strong&gt; is the guardrail against loops. Without it, the model might say “I have 5 years, 5 years, 5 years…” forever. A gentle penalty (around 1.1) is enough to keep things fresh but still natural.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;model&lt;/strong&gt; is the biggest decision. Small ones (4B-7B) run on CPUs or modest GPUs but don’t go very deep. Larger ones (13B, 30B, 70B) give better answers but eat memory and VRAM quickly. For local-first HR prescreening, I’ve found 4B–13B hits the right balance between speed and quality.&lt;/p&gt;

&lt;p&gt;Together, these knobs give you fine-grained control over the “voice” of your candidate AI: factual and concise vs. more conversational and human-like.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. AdvisorsProvider — annotated core code (key excerpts)
&lt;/h2&gt;

&lt;p&gt;Below I include the essential, annotated pieces (edited for clarity). This is the class that configures the &lt;code&gt;ChatClient&lt;/code&gt; with advisors and system prompts.&lt;br&gt;
&lt;strong&gt;Initialization &amp;amp; properties&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Value("${spring.ai.ollama.chat.max-messages}")
public int MAX_MESSAGES_MEMORY;
@Value("${spring.ai.ollama.chat.model}")
private String modelName;
// expansion query config
@Value("${spring.ai.ollama.expansion-query-temperature}")
private Double expansionQueryTemperature;
@Value("${spring.ai.ollama.expansion-query-top-k}")
private int expansionQueryTopK;
@Value("${spring.ai.ollama.expansion-query-top-p}")
private Double expansionQueryTopP;
@Value("${spring.ai.ollama.expansion-query-repeat-penalty}")
private Double expansionQueryRepeatPenalty;
// chat client defaults
@Value("${spring.ai.ollama.chatclient-query-temperature}")
private Double chatclientQueryTemperature;
@Value("${spring.ai.ollama.chatclient-query-top-k}")
private int chatclientQueryTopK;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;// ...&lt;br&gt;
Values are loaded from application.properties/yaml. Profiles can override them (factual / creative / summarization), but I left profiles optional.&lt;br&gt;
&lt;strong&gt;System prompt &amp;amp; expansion prompt (two critical templates)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public static final PromptTemplate CV_SCREENING_EXPANSION_PROMPT = PromptTemplate.builder()
    .template("""...Question: {question} Expanded query:""").build();

public static final PromptTemplate SYSTEM_PROMPT = new PromptTemplate("""
system: |
You are %Your Name% a %your role%... Answer in first person, brief, and to the point.
... Strategy: Context fact → question → answer → position assessment
""");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;System prompt encodes identity, tone, and rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  System Prompt
&lt;/h3&gt;

&lt;p&gt;The system prompt is the foundation of the assistant’s behavior. It’s not about answering one question but about setting the overall role and rules of engagement. Imagine it as the constitution of the candidate’s AI voice: it defines tone, boundaries, and what the assistant will never do.&lt;br&gt;
For example, my assistant is always reminded:&lt;br&gt;
“You help filter and manage inbound offers. You never exaggerate, you never invent skills, you only prescreen and keep conversations polite but firm.”&lt;br&gt;
With this in place, no matter how many advisors or steps are involved later in the chain, the assistant always stays within these guardrails. That’s how I make sure it doesn’t accidentally oversell me or leak context it shouldn’t.&lt;/p&gt;
&lt;h3&gt;
  
  
  Expansion Prompt
&lt;/h3&gt;

&lt;p&gt;The expansion prompt works very differently — instead of defining rules, it takes a short, vague question and expands it into a rich, structured query. Recruiters often ask something minimal like:&lt;br&gt;
“What’s your salary range?”&lt;br&gt;
On its own, that’s too little for smart retrieval. The expansion prompt reformulates it into something much broader:&lt;br&gt;
“Salary range, expected compensation, benefits, negotiation, backend developer role, Europe, Austria, 5+ years experience required, Quarkus experience required.”&lt;/p&gt;

&lt;p&gt;Now the advisors can actually dig into my CV, prior negotiations, or stored context and find the right pieces to answer. Without this step, retrieval would miss half of the relevant details.&lt;/p&gt;

&lt;p&gt;So, in short: the system prompt keeps the assistant grounded and consistent, while the expansion prompt makes the conversation intelligent enough to find context in the first place. One sets the rules, the other makes the search smarter — together they form the backbone of the pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ChatClient bean (default options + advisors)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Bean
public ChatClient chatClient(ChatClient.Builder builder) {
    return builder.defaultAdvisors(getAdvisors())
            .defaultOptions(OllamaOptions.builder()
                    .temperature(chatclientQueryTemperature)
                    .topP(chatclientQueryTopP)
                    .topK(chatclientQueryTopK)
                    .repeatPenalty(chatclientQueryRepeatPenalty)
                    .model(modelName)
                    .build())
            .defaultSystem(SYSTEM_PROMPT.render())
            .build();
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This binds the advisor chain and default model options to the ChatClient.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advisors list (core)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private List&amp;lt;Advisor&amp;gt; getAdvisors(){
    return List.of(
        ExpansionQueryAdvisor.builder(
            ChatClient.builder(chatModel)
                .defaultOptions(OllamaOptions.builder()
                    .temperature(expansionQueryTemperature)
                    .topK(expansionQueryTopK)
                    .topP(expansionQueryTopP)
                    .repeatPenalty(expansionQueryRepeatPenalty)
                    .model(modelName).build())
                .build(), CV_SCREENING_EXPANSION_PROMPT).build(),

        MessageChatMemoryAdvisor.builder(getChatMemory()).order(AdvisorType.HISTORY.getOrder()).build(),

        SimpleLoggerAdvisor.builder().order(AdvisorType.LOGGER.getOrder()).build(),

        RagAdvisor.build(vectorStore, ChatClient.builder(chatModel)
                .defaultOptions(OllamaOptions.builder()
                    .temperature(expansionQueryTemperature).topK(expansionQueryTopK)
                    .topP(expansionQueryTopP).repeatPenalty(expansionQueryRepeatPenalty)
                    .model(modelName).build()).build())
            .bm25Engine(BM25RerankEngine.builder()
                .defaultK(bm25K).defaultB(bm25B).defaultDelta(bm25Delta).build())
            .searchRequest(SearchRequest.builder()
                .topK(searchRequestTopK)
                .similarityThreshold(searchRequestSimilarityThreshold).build())
            .build(),

        SimpleLoggerAdvisor.builder().order(AdvisorType.FINAL_LOGGER.getOrder()).build()
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This exact ordering (expansion → memory → logger → rag → final logger) is key: expansion helps retrieval; memory provides multi-turn context; BM25/NN rerank ensure quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Mock SSE snippet &amp;amp; annotated memory flow (compact)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Backend SSE endpoint:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux&amp;lt;ServerSentEvent&amp;lt;String&amp;gt;&amp;gt; streamChat(@RequestParam String question) {
    return chatService.streamAnswer(question).map(ans -&amp;gt; ServerSentEvent.builder(ans).build());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Frontend (React):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const es = new EventSource(`/chat/stream?question=${encodeURIComponent(q)}`);
es.onmessage = e =&amp;gt; appendMessage(e.data);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Annotated flow (memory/cache):&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;ChatService&lt;/code&gt; writes HR question to &lt;code&gt;PostgresChatMemory&lt;/code&gt;. &lt;code&gt;ExpansionQueryAdvisor&lt;/code&gt; generates expanded query; expanded query stored in context.&lt;br&gt;
&lt;code&gt;RagAdvisor&lt;/code&gt; checks session cache: if miss → pgvector similarity search → BM25 → optional neural rerank → put result into cache.&lt;br&gt;
&lt;code&gt;ChatClient&lt;/code&gt; generates text using retrieved docs; stream chunks to SSE.&lt;br&gt;
&lt;code&gt;PostgresChatMemory&lt;/code&gt; persists the final turn (with references to RAG docs).&lt;/p&gt;

&lt;h2&gt;
  
  
  10. MCP: technical integration and effects (how &amp;amp; why)
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) is on my roadmap — here’s how I’d integrate it and why it matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  How advisors would use MCP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Read/Write API: each advisor would be given an MCP client to read the latest context fragment and write updates atomically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Document references: &lt;code&gt;RAGAdvisor&lt;/code&gt; writes pointers (document id, offset) to MCP rather than full text, enabling traceability.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Versioning: each context update is versioned; ChatClient can request a specific version snapshot to reproduce past outputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Per-request metadata: MCP can store overrides (profile=“factual”), flags (sensitive=true), or QA checks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical consequences for RAG/model responses
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Better grounding: the generative model receives not just raw retrieved text, but MCP-provided provenance links and context snapshots.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auditability: every generated answer can be traced to the documents and the context version that produced it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dynamic updates: advisors can add facts mid-request (e.g., calendar availability) and MCP will ensure the generative model sees them.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Example (calendar integration)&lt;/strong&gt;&lt;br&gt;
Availability check: RAGAdvisor queries MCP for candidate calendar references (or calls calendar service); MCP writes back confirmed time slots; the ChatClient uses that to respond with precise start dates.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Resource trade-offs &amp;amp; operational notes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Embeddings vs Generative models:&lt;/strong&gt;&lt;br&gt;
Embeddings storage (pgvector) consumes disk and RAM for indexes; vector search is I/O and CPU bound depending on index type.&lt;br&gt;
Generative models (esp. &amp;gt;7B) need GPU or lots of RAM for responsive inference.&lt;br&gt;
Local-first reality: choose quantized or smaller generative models if you must run on constrained hardware, or provision GPU for heavier models.&lt;br&gt;
Scaling: containerized architecture is Kubernetes-ready; for enterprise you’ll move to orchestrated pods, GPU node pools, and autoscaling.&lt;br&gt;
Caching policy: tune cache TTLs to allow fresh context while avoiding repeated expensive reranks.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Use-case boundaries and ethical guardrails
&lt;/h2&gt;

&lt;p&gt;• This prototype is for prescreening automation. It’s designed to answer routine recruiter questions, schedule interviews, and filter irrelevant offers.&lt;br&gt;
• It does not replace human interviews or decisions. Final evaluation, negotiation, and cultural fit are human tasks.&lt;br&gt;
• Privacy-first: Candidate data is processed locally; nothing is sent to external services by default. If you integrate cloud models, document that risk explicitly and get consent.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Practical advice (what I’d tell engineers when they clone this repo)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; System prompt is critical. Think of it as your policy and persona. Test and iterate it. Use it to enforce rules (e.g., “don’t invent facts; say ‘I don’t know’”).&lt;/li&gt;
&lt;li&gt; Tune retrieval first. RAG quality dictates factuality. Get embeddings, similarity thresholds, and BM25 right before trying to tune temperature.&lt;/li&gt;
&lt;li&gt; Start small with models. Use a quantized 4B model locally; if you need better reasoning, move to GPU-backed 7B–13B models.&lt;/li&gt;
&lt;li&gt; Make memory explicit. Use &lt;code&gt;PostgresChatMemory&lt;/code&gt; and design your advisors to depend on versioned context (MCP-ready).&lt;/li&gt;
&lt;li&gt; Monitor metrics. Track RAG cache hits/misses, BM25 rerank times, and token latency to find bottlenecks.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  14. Conclusion — succinct
&lt;/h2&gt;

&lt;p&gt;I built a local, modular AI agent that represents a candidate during prescreening: it expands HR queries, retrieves relevant facts via RAG, refines those facts with BM25 and optional neural rerank, and uses a local Ollama model to generate concise replies. The system is privacy-first, extensible via advisors, and MCP-ready for future context/versioning/auditability. It speeds early-stage hiring workflows while preserving the human role in final decisions.&lt;br&gt;
P.S.&lt;br&gt;
Here are some resources to help you dive into the world of AI agents and RAG:&lt;br&gt;
&lt;a href="https://github.com/dzmdre/CVScreeningApplication.git" rel="noopener noreferrer"&gt;My local, modular AI agent POC&lt;/a&gt;&lt;br&gt;
“Spring AI” course by Evgeny Borisov: If you want to repeat the experiments and understand how to build AI applications, get a &lt;strong&gt;50% discount&lt;/strong&gt; with the coupon:&lt;br&gt;
&lt;a href="https://www.udemy.com/course/spring-ai-rag/?couponCode=64A6AF4E93B83B603DCA" rel="noopener noreferrer"&gt;Spring AI RAG&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.udemy.com/course/spring-ai-pro/?couponCode=968CD21C43545D21EB46" rel="noopener noreferrer"&gt;Spring AI Pro&lt;/a&gt;&lt;br&gt;
Book on RAG: For those who want to study the topic in more depth, I recommend Denis Rothman’s book “RAG and Generative AI” with a &lt;strong&gt;25% discount&lt;/strong&gt; using the coupon RAG.&lt;br&gt;
&lt;a href="https://www.piter.com/collection/all/product/rag-i-generativnyy-ii-sozdaem-sobstvennye-rag-payplayny-s-pomoschyu-llamaindex-deep-lake-i-pinecon" rel="noopener noreferrer"&gt;RAG and Generative AI (Piter)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://anthropic.skilljar.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Free&lt;/strong&gt; MCP courses&lt;/a&gt; or &lt;a href="https://developer.microsoft.com/en-us/reactor/series/S-1568/?wt.mc_id=seriespg_S-1568_webpage_reactor?wt.mc_id=gfeet_WW_LJ_wwl_Events" rel="noopener noreferrer"&gt;MCP Bootcamp&lt;/a&gt;: If you want to learn the principles of building systems like our Model Context Protocol, I suggest starting with this free course on working with vector databases.&lt;/p&gt;

</description>
      <category>rag</category>
      <category>spring</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>Cool readme on your github profile page with github actions.</title>
      <dc:creator>Dmitry Drepin</dc:creator>
      <pubDate>Sat, 30 Sep 2023 10:42:14 +0000</pubDate>
      <link>https://dev.to/dmitryd/cool-readme-on-your-github-profile-page-with-github-actions-1lp</link>
      <guid>https://dev.to/dmitryd/cool-readme-on-your-github-profile-page-with-github-actions-1lp</guid>
      <description>&lt;p&gt;In the world of coding, there's one task many of us dread - writing documentation. Yet, when you start a new project or set up a fresh GitHub repository, those all-important markdown files beckon. Enter the GitHub profile README, a clever feature that lets you infuse your personal touch into your GitHub page. And when you combine this with the power of GitHub Actions, something magical happens: you craft a captivating portfolio-CV page that paints a vivid picture of who you are as a coder.&lt;/p&gt;

&lt;p&gt;Creating an engaging GitHub profile with the help of GitHub Actions can be a game-changer for showcasing your skills and personality. While crafting a README for your profile might not be the most thrilling task, it's essential to present yourself effectively in the developer community. Combining GitHub Actions with your profile README allows you to create a dynamic portfolio-CV page that represents you authentically. In today's competitive job market, your CV is only part of the equation; your portfolio and interests matter too. &lt;/p&gt;

&lt;p&gt;Your CV is just the tip of the iceberg. Hiring managers are casting a wider net, scrutinizing your portfolio and even your interests. They rely on tools to pluck out pertinent information from resumes, but let's face it – most CVs are stubbornly resistant to easy parsing. Whether it's an ancient PDF or a beautifully designed but enigmatic image, it often doesn't cut it. The consequence? Even brilliant developers find themselves at the bottom of the hiring pile. That's where the profile README steps in. It's not just a CV; it's a user-friendly, easily indexed powerhouse of a developer's journey.It should include not just your CV but also links to your social profiles, blog articles, skills, certifications, GitHub stats, preferred repositories, and even a touch of personal flair to engage the reader.&lt;/p&gt;

&lt;p&gt;In my opinion, a README page should have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;    Social and contact links.&lt;/li&gt;
&lt;li&gt;    Blog articles on tech and ideas.&lt;/li&gt;
&lt;li&gt;    Skills and certifications.&lt;/li&gt;
&lt;li&gt;    GitHub stats and favorite repositories (if desired).&lt;/li&gt;
&lt;li&gt;    An auto-generated CV.&lt;/li&gt;
&lt;li&gt;    A relaxed section.&lt;/li&gt;
&lt;li&gt;    Contact details.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here in the article, I will try to describe my own path to create GitHub readme file. So, how do you create a GitHub README that shines? Start by establishing a profile repository bearing your GitHub username. My username is &lt;em&gt;dzmdre&lt;/em&gt;, so I created a repository with the name &lt;em&gt;dzmdre&lt;/em&gt;. I initialized the repository with &lt;em&gt;README&lt;/em&gt; and &lt;em&gt;.gitignore&lt;/em&gt; files. I cloned it down and opened it in my favorite editor. In this repository, introduce a &lt;em&gt;"readme.template"&lt;/em&gt; file, the secret sauce that fuels your dynamic README.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a README Template
&lt;/h3&gt;

&lt;p&gt;Create a new &lt;em&gt;“readme.template”&lt;/em&gt; at the root level of my repository. This is the file I will be editing manually, and readme will be auto-generated based on &lt;em&gt;“readme.template”&lt;/em&gt; and data received through the rest calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Banner Image
&lt;/h3&gt;

&lt;p&gt;Your README's visual appeal often starts with a banner image at the top. You can create a banner featuring a photo from a conference, along with your contact details. I haven’t found something suitable and have chosen some simple picture from &lt;a href="https://www.canva.com/"&gt;Canva&lt;/a&gt; site. I added some necessary contacts data and my banner was ready. Back in my repository, I created a new root-level directory named assets, placed the image in the folder and added it in the &lt;em&gt;“readme.template”&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[![Dmitry's GitHub Banner](./assets/GitHubHeader.png)](https://linktr.ee/drepin)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Social Badges
&lt;/h3&gt;

&lt;p&gt;Then, you can add social badges to your various online profiles using badges from &lt;a href="https://shields.io"&gt;shields.io&lt;/a&gt;.&lt;br&gt;
You should have something similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[![Twitter Badge](https://img.shields.io/badge/Twitter-Profile-informational?style=flat&amp;amp;logo=twitter&amp;amp;logoColor=white&amp;amp;color=1CA2F1)](https://twitter.com/drepindmitry)&lt;br&gt;
[![LinkedIn Badge](https://img.shields.io/badge/LinkedIn-Profile-informational?style=flat&amp;amp;logo=linkedin&amp;amp;logoColor=white&amp;amp;color=0D76A8)](https://linkedin.com/in/dmitry-drepin-77107336)&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  The Introduction Section
&lt;/h3&gt;

&lt;p&gt;Most of the GitHub readme files include only this main part.The main template is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Here are some ideas to get you started:
- 🔭 I’m currently working on ...
- 🌱 I’m currently learning ...
- 👯 I’m looking to collaborate on ...
- 🤔 I’m looking for help with ...
- 💬 Ask me about ...
- 📫 How to reach me: ...
- 😄 Pronouns: ...
- ⚡ Fun fact: ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;People generally prefer not to delve into extensive self-reflections; it's more effective to demonstrate skills through certifications and the portfolio section. If visitors seek additional information about me, they can explore &lt;a href="https://dzmdre.blogspot.com"&gt;my website&lt;/a&gt; or &lt;a href="https://linkedin.com/in/dmitry-drepin-77107336"&gt;LinkedIn profile&lt;/a&gt;. Keep it concise and straightforward.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Stats &amp;amp; Wakatime plugin
&lt;/h3&gt;

&lt;p&gt;You can add a lot of &lt;a href="https://github.com/anuraghazra/github-readme-stats"&gt;Anurag Hazra‘s GitHub ReadMe Stats&lt;/a&gt; features:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anuraghazra/github-readme-stats#github-stats-card"&gt;GitHub Stats Card&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default, the stats card only shows statistics like stars, commits and pull requests from public repositories. To show private statistics on the stats card, you should deploy your own instance using your own GitHub API token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anuraghazra/github-readme-stats#github-extra-pins"&gt;GitHub Extra Pins&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub extra pins allow you to pin more than 6 repositories in your profile using a GitHub readme profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anuraghazra/github-readme-stats#github-gist-pins"&gt;GitHub Gist Pins&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub gist pins allow you to pin gists in your GitHub profile using a GitHub readme profile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anuraghazra/github-readme-stats#top-languages-card"&gt;Top Languages Card&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The top languages card shows a GitHub user's most frequently used languages. Top Languages does not indicate the user's skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. This card shows languages usage only inside your own non-forked repositories, not depending from who is the author of the commits. It does not include your contributions into another users/organizations repositories. Currently this card shows data only about first 100 repositories. This is because GitHub API limitations which cause downtimes of public instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anuraghazra/github-readme-stats#wakatime-stats-card"&gt;Wakatime Stats Card&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://wakatime.com/"&gt;WakaTime&lt;/a&gt; is committed to making time tracking fully automatic for every programmer. By creating opensource plugins for IDEs and text editors, it gives powerful insights about how you code. It is possible now demonstrate these statistics in your GitHub profile.&lt;br&gt;
What’s next? Next up, showcase your skills, awards, and certifications.&lt;/p&gt;
&lt;h3&gt;
  
  
  Skills, Awards, and Certifications:
&lt;/h3&gt;

&lt;p&gt;Avoid using an unordered list for this section, as it can become challenging to read. Instead, the key is to categorize and group your skills and certifications, making them more organized and easier to manage. The specific edits required for this section depend on the number of skills, certifications, and other factors. If you have an extensive list, consider utilizing small badges from &lt;a href="https://shields.io"&gt;shields.io&lt;/a&gt; where applicable and hide outdated information.&lt;/p&gt;
&lt;h3&gt;
  
  
  Latest Blog Posts &amp;amp; random quote
&lt;/h3&gt;

&lt;p&gt;In today's world, having more than just an impressive CV is essential; it's equally crucial to maintain a personal IT blog, sharing your insights and opinions. For programmers, a personal IT blog serves as an invaluable platform for various reasons.&lt;br&gt;
Firstly, it's a stage to showcase their expertise, skills, and profound knowledge within the tech industry. Regular blog posts allow programmers to exhibit their problem-solving abilities, coding prowess, and innovative thinking, all of which are invaluable assets in their professional journey.&lt;br&gt;
Secondly, a personal IT blog keeps programmers in sync with the ever-evolving industry trends and technologies. Writing about new tools, frameworks, or programming languages compels them to thoroughly research and understand these innovations, enriching their knowledge base.&lt;br&gt;
Moreover, a blog becomes a repository for their learning experiences. Programmers can document their challenges, breakthroughs, and projects, creating a valuable resource not just for themselves but for the broader developer community. This knowledge sharing fosters collaboration, networking, and the overall growth of the IT community.&lt;br&gt;
Lastly, a well-maintained IT blog elevates a programmer's professional credibility and can open doors to consulting opportunities, speaking engagements, or even job offers. It acts as a digital portfolio that potential employers or clients can consult when assessing the programmer's skills and expertise.&lt;br&gt;
That's precisely why having links to your blog articles on your GitHub page is of paramount importance. It's a gateway for others to tap into your knowledge, experience, and thought leadership within the tech world.&lt;br&gt;
To automate the process of updating your README with blog posts and a random quote, you can create a GitHub Action workflow. The workflow will dynamically fetch data and inject it into your &lt;em&gt;"readme.template"&lt;/em&gt; file. This keeps your profile fresh and engaging.&lt;br&gt;
To inject my blog feed into the &lt;em&gt;README.md&lt;/em&gt; file, I followed &lt;a href="https://dev.to/renanfranca"&gt;Renan Franca&lt;/a&gt; article &lt;a href="https://dev.to/renanfranca/create-a-github-stunning-profile-by-dynamically-listing-your-recent-blog-posts-3g0j"&gt;Create a Github stunning profile&lt;/a&gt; 💫 (by dynamically listing your recent blog posts) &lt;br&gt;
The hard work will eventually be delegated to separate file that the GitHub Action workflow will run, but in order for that file to know where to inject the content, it requires a certain pattern in the template.&lt;br&gt;
For now, I put the following under the blog post section in the &lt;em&gt;„readme.template”&lt;/em&gt; file:&lt;/p&gt;



 

&lt;p&gt;Very much like the blog post section, a GitHub Action workflow will run a script that will look for a pattern in the &lt;em&gt;„readme.template”&lt;/em&gt; file and inject a random quote.&lt;br&gt;
I put the following pattern in the quote section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; {quote}
&amp;gt;
&amp;gt; &amp;lt;p&amp;gt;{quote_author}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make this happen a GitHub Action workflow will run a script that will look for a pattern in the &lt;em&gt;„readme.template”&lt;/em&gt; and inject a random quote.&lt;/p&gt;

&lt;p&gt;You should have 4 steps to create in &lt;em&gt;index.js&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;index.js&lt;/em&gt; File&lt;br&gt;
There are four steps this file needs to complete:&lt;br&gt;
·      Create a variable to reference the &lt;em&gt;README.template.md&lt;/em&gt; file&lt;/p&gt;

&lt;p&gt;·      Make the request to the rest service&lt;/p&gt;

&lt;p&gt;·      Look through the &lt;em&gt;„readme.template”&lt;/em&gt; content and replace the static patterns &lt;em&gt;({quote} &amp;amp; {quote_author})&lt;/em&gt; with the dynamic result of the API request&lt;/p&gt;

&lt;p&gt;·      Create/replace the contents of the &lt;em&gt;README.md&lt;/em&gt; file with the updated &lt;em&gt;„readme.template”&lt;/em&gt; reference variable.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Index.js&lt;/em&gt; file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require("isomorphic-unfetch");
const { promises: fs } = require("fs");
const path = require("path");

async function main() {
    const readmeTemplate = (
        await fs.readFile(path.join(process.cwd(), "./README.template.md"))
    ).toString("utf-8");

    const quote = await (
        await fetch("https://api.quotable.io/random")
    ).json();

    console.log(quote);

    const readme = readmeTemplate
        .replace("{quote}", quote.content)
        .replace("{quote_author}", `- ${quote.author}`)

    await fs.writeFile("README.md", readme);
}

main();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a workflow file &lt;em&gt;dynamic-injection-workflow.yml&lt;/em&gt; under the &lt;em&gt;.github/workflows&lt;/em&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Dynamic README injection
on:
  schedule: # Run workflow automatically
    # This will make it run every hour
    - cron: "0 * * * *"
    # Run workflow manually (without waiting for the cron to be called), through the Github Actions Workflow page directly
  workflow_dispatch:
permissions:
  contents: write # To write the generated contents to the readme

jobs:
  get-quotes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: 16.16.0
      - run: yarn
      - run: node .
      - name: Add to git repo
        run: |
          git config pull.rebase false
          git pull
          git add .
          git config --global user.name "GitHub Action"
          git config --global user.email "action@github.com"
          git commit -m "[Automated] README updated with new quote!"
      - name: Push
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{secrets.GITHUB_TOKEN}}
  update-readme-with-blog:
    needs: get-quotes
    name: Update this repo's README with latest blog posts
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: gautamkrishnar/blog-post-workflow@master
        with:
          # Replace this URL with your rss feed URL/s
          feed_list: "https://dzmdre.blogspot.com/feeds/posts/default?alt=rss,https://dev.to/feed/dmitryd"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will allow you to receive the latest blog posts from your site &amp;amp; update quote.&lt;br&gt;
And Node execution here will trigger the &lt;em&gt;index.js&lt;/em&gt; file to do its magic.&lt;br&gt;
Once you've added a workflow in GitHub, it becomes visible under &lt;strong&gt;'Actions'&lt;/strong&gt; &amp;gt; &lt;strong&gt;'All workflows.'&lt;/strong&gt; If you want to manually trigger the workflow and test its functionality, simply click on the &lt;strong&gt;'Run workflow&lt;/strong&gt;' dropdown button. You can then choose to run the workflow on the main branch. However, keep in mind that the cron job you've set up will automatically execute this workflow every hour, ensuring it runs periodically.&lt;br&gt;
Once the workflow successfully completes both of its designated tasks, you can view the final result on your GitHub profile. It's a seamless way to keep your profile updated and dynamic.&lt;/p&gt;
&lt;h3&gt;
  
  
  Publish resume in JSONResume format at the Github Page
&lt;/h3&gt;

&lt;p&gt;Another essential workflow involves the automatic generation of a CV file, which is based on a &lt;em&gt;resume.json&lt;/em&gt; file. JSON Resume stands as an open standard format, designed for crafting and sharing resumes or CVs in a structured and machine-readable manner. It harnesses the power of JSON to delineate various resume sections, encompassing personal details, educational background, work experience, skills, and more. JSON Resume empowers individuals to maintain a single source of truth for their professional history while facilitating the creation of customized resumes in diverse formats like HTML, PDF, or plain text. This versatility is bolstered by the availability of various themes and templates, ensuring a visually appealing and unique CV while preserving data in an organized format.&lt;br&gt;
This format's adaptability and developer-friendly nature have made it a preferred choice among tech-savvy individuals who aim to craft compelling resumes that stand out. &lt;a href="https://jsonresume.org"&gt;JSON Resume &lt;/a&gt; also fosters open-source collaboration, enabling users to contribute themes and tools to the community. This collaborative approach ensures ongoing enhancements and personalization of resumes in a standardized fashion.&lt;/p&gt;

&lt;p&gt;How to do this?&lt;br&gt;
Create a file &lt;em&gt;publish-resume.yml&lt;/em&gt;  under the &lt;em&gt;.github/workflows/&lt;/em&gt; directory with a following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
# example GitHub workflow
name: Publish resume in JSONResume format as Github Page

on:
  push:
    branches: [ master ]
  schedule: # Run workflow automatically
    - cron: "0 1 * * *"
    # Run workflow manually (without waiting for the cron to be called), through the Github Actions Workflow page directly
  workflow_dispatch:
permissions:
  contents: write # To write the generated contents to the readme

jobs:
  check_run:
    runs-on: ubuntu-latest
    if: "! contains(github.event.head_commit.message, '[Automated]')"
    steps:
      - run: echo "${{ github.event.head_commit.message }}"

  build:
    runs-on: ubuntu-latest
    needs: check_run
    steps:
      - uses: actions/checkout@v2
      - uses: kelvintaywl/action-jsonresume-export@v1
        name: Export resume as HTML
        with:
          theme: macchiato
          resume_filepath: resume.json
          # modifies the index.html in-place
          output_filepath: docs/index.html
      - name: Commit published HTML
        id: commit
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          if [ -n "$(git status --porcelain docs/index.html)" ]; then
            git add docs/index.html
            git commit -m "[Automated] chore(docs/index.html): update resume page"
            echo "{exit_code}={0}" &amp;gt;&amp;gt; $GITHUB_OUTPUT
          else
            echo "{exit_code}={1}" &amp;gt;&amp;gt; $GITHUB_OUTPUT
          fi
      - name: Push changes
        uses: ad-m/github-push-action@master
        if: steps.commit.outputs.exit_code == 0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.ref }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The workflow should automatically generate the CV file on a daily basis or update it whenever there's a push event. If you wish to alter this behavior, you can reconfigure the cron job accordingly. To change the CV's appearance, simply adjust the &lt;em&gt;"theme"&lt;/em&gt; property to select a different style.&lt;br&gt;
The generated CV file will be stored in the &lt;em&gt;"output_filepath"&lt;/em&gt; specified as &lt;em&gt;"docs/index.html."&lt;/em&gt; It's advisable to create an empty &lt;em&gt;"index.html"&lt;/em&gt; file within the directory beforehand.&lt;br&gt;
Once you've executed this GitHub workflow, your CV will be automatically generated based on the content in your &lt;em&gt;resume.json&lt;/em&gt; file. To provide a link to your CV, you can use the following prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"My auto-generated &amp;lt;a href="https://htmlpreview.github.io/?https://raw.githubusercontent.com/dzmdre/dzmdre/main/docs/index.html"&amp;gt;CV&amp;lt;/a&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you have missed something please take a look on &lt;a href="https://github.com/dzmdre"&gt;my Github page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In conclusion, a well-crafted &lt;strong&gt;GitHub profile README&lt;/strong&gt; with &lt;strong&gt;GitHub Actions&lt;/strong&gt; can help you stand out in the developer community. It's a versatile tool for showcasing your skills, sharing your thoughts, and presenting your professional history. By following these steps, you can create an impressive and dynamic GitHub profile that represents you effectively.&lt;br&gt;
Thanks for reading! If you liked this article and want more content like this, make sure to follow me on &lt;a href="https://twitter.com/drepindmitry"&gt;Twitter&lt;/a&gt;!A special thanks to &lt;a class="mentioned-user" href="https://dev.to/rahuldkjain"&gt;@rahuldkjain&lt;/a&gt; and &lt;a class="mentioned-user" href="https://dev.to/gautamkrishnar"&gt;@gautamkrishnar&lt;/a&gt;, &lt;a class="mentioned-user" href="https://dev.to/renanfranca"&gt;@renanfranca&lt;/a&gt; for building the awesome open source projects that I used on this post. Please, visit &lt;a href="https://github.com/rahuldkjain"&gt;rahuldkjain's project repository&lt;/a&gt; and &lt;a href="https://github.com/gautamkrishnar"&gt;gautamkrishnar's project repository&lt;/a&gt; and give it a star 🤩&lt;/p&gt;

</description>
      <category>github</category>
      <category>career</category>
      <category>readme</category>
      <category>markdown</category>
    </item>
  </channel>
</rss>
