Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
147622 stories
·
33 followers

Introducing lazyqmd: a tui for QMD - and a little more...

1 Share

What's it all about?

QMD is a quite popular mini cli search engine for your docs, knowledge bases, meeting notes, whatever.

qmd has been created by Tobi Luetke, the founder of Shopify and can be found on GitHub.

It allows you to index Markdown files from several locations on your computer.

You can search with keywords or natural language.

QMD combines BM25 full-text search, vector semantic search, and LLM re-ranking—all running locally via node-llama-cpp with GGUF models.

After installing it, you can add a new collection like this:

qmd collection add ~/notes --name notes

Now all Markdown files under ~/notes will be indexed and be searched like this:

qmd search -c notes "search term"

If you have multiple collections, you can do a global search:

qmd search "search term"

You can query or do a vector search (see docs for more details).

qmd also provides a mcp server, so you can integrate it as a memory server into your agentic workflows:

qmd mcp --http

This will run start the mcp server on port 8181.

Of course you can specify another port and you can also run it in daemon mode:

qmd mcp --daemon --http --port 9000

So if I have a collection named blog which points to the source repository of my blog on my computer, I can run a search like this:

qmd search tmux

This will search all collections, hence also my collection named blog and the results will look like this:

qmd://blog/articles/my-tmux-tmuxinator-rails-ai-development-setup/index.md:2 #b9e642
Title: So what's tmux?
Score: 92%

@@ -1,4 @@ (0 before, 156 after)
---
title: "My tmux + Rails + AI TUIs development setup"
date: 2026-02-11T22:00:00
layout: default

Earlier today I was curious if I could display the whole Markdown file instead of result shown above and I came up with the idea of building a Temrinal UI (TUI) for for qmd - lazyqmd was born.

Introducing lazyqmd

As with qmd itself, lazyqmd can be installed using bun:

bun install -g lazyqmd

To use all of the lazyqmd features, make sure to start the qmd mcp server in daemon mode as shown before. If you stick with the default port, you're good to go.

If you set another port, you can configure lazyqmd to use this one in the ~/.config/lazyqmd/options.json file (for more details, take a look at the README).

Now we're ready to start lazyqmd:

lazyqmd

If you're starting from scratch without prior usage of qmd itself, you'll have no collections at hand and lazyqmd will look like this:

As can bee seen from the bottom bar in the screenshot, there's a command Add which can be invoked by pressing the a key - this brings up a little dialog to add a new collection:

Please notice, that you get tab completion for the path.

Please wait until the Indexing... message disappears. Once, the collection has been indexed, you can start using it. lazyqmd should look similar to this now:

You can navigte the collections in the left sidebar using up and down arrows and you can start a new search by either sticking with the All selection for a global search or you can select a particular collection and search this one.

Let's select blog and hit / to bring up the search dialog:

Now lets type "tmux":

Htting "Enter" will bring up the search results for this particular search term:

The first result seems interesting as it has a relevance of 86%. So lets hit <tab> to jump to the results list follow by Enter to open that particular document:

Now you can scroll inside this document and as can be seen, there's some syntax highlighting for Markdown and YAML frontmatter.

At this point I thought it would be nice to have a HTML preview at hand, so I've added it. Within the Markdown preview just hit <p>:

As expected, this brings up a Chrome, Chromium or Brave instance in app mode. If you're living the dream and run Omarchy, everything will be auto aligned nicely thanks to hyprland.

Now I wanted to go a little further: What if I could just edit the file and get a sort of hot reload of the HTML?

Lets focus the lazyqmd window and hit <e> for "Edit":

If your $EDITOR is nvim, this will seamlessly open the Markdown file in neovim.

Now lets make a little change to our Markdown file:

As you can see, when saving the change, the change is reflected in the HTML preview.

Quitting nvim will bring you back to lazyqmd where you left off.

Looking at the search screen again, you might have noticed, there's a "Mode" command show in the command bar at the bottom:

By hitting <CTRL+T> you can switch between regular search, vsearch (QMD Vector search) and query.

If you want to use vsearch, make sure you've created the Embeddings. If you didn't so far, you can just hit <e> on the main screen with a collection selected. This will run qmd ebmed for you and create the Embeddings.

After finishing this, I noticed that this little TUI + QMD might replace LogSeq and Obsidian for me: file based, run locally and I can have my files where they belong to- lets see how this works in daily use.

For more features, please have a look at the README on GitHub.

As this project is pretty new, expect not everything to be perfect right now.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

I threw thousands of files at Astro and you won't believe what happened next...

1 Share

Ok, forgive me for the incredibly over the top title there. Yes, it's clickbait, but I'm also tired after a very long week and feeling a little crazy, so just go with me here a bit, I promise it will be worth it. I was curious how well Astro could handle a large amount of data and I thought - what happens if I threw this blog (well, the Markdown files) at it and tried to render out a site? Here's what I did wrong and what eventually worked (better than I expected).

Round One

I began by creating a soft link locally from my blog's repo of posts to the src/pages/posts of a new Astro site. My blog currently has 6742 posts (all high quality I assure you). Each one looks like so:

---
layout: post
title: "Creating Reddit Summaries with URL Context and Gemini"
date: "2026-02-09T18:00:00"
categories: ["development"]
tags: ["python","generative ai"]
banner_image: /images/banners/cat_on_papers2.jpg
permalink: /2026/02/09/creating-reddit-summaries-with-url-context-and-gemini
description: Using Gemini APIs to create a summary of a subreddit.
---

Interesting content no one will probably read here...

In my Astro site's index.astro page, I tried this first:

const allPosts = Object.values(import.meta.glob('./posts/**/*.md', { eager: true }));

And immediately ran into an issue with the layout front matter. Astro parses this and expects to find a post component in the same directory. My "fix" was to... remove the symbolic link and make a real copy and then use multi-file search and replace to just delete the line.

That worked... but was incredibly slow. I'd say it took about 70 or so seconds for each load.

This was... obviously... the wrong approach.

Round Two - The Right Approach

The solution was simple - use content collections. This involved moving my content out of the src/pages directory and creating a file, src/content.config.js to define the collection:

import { defineCollection } from 'astro:content';

import { glob, file } from 'astro/loaders';


const blog = defineCollection({ 
    loader: glob({pattern:"**/*.md",base:"./posts"})
 });

export const collections = { blog  };

You an see where I define my blog collection using a glob pattern and a base directory. That's literally it. This still took a few seconds to load, but was cached and future reloads were zippy zippy.

But wait... there's more

With this working, I began building out a few pages just to see things in action. First, a home page that shows ten recent posts with excepts:

---
import BaseLayout from '../layouts/BaseLayout.astro';
import { formatDate, excerpt } from "../utils/formatters.js"

import { getCollection } from 'astro:content';

const posts = await getCollection('blog');
const sortedPosts  = posts.sort((a, b) => {
	return new Date(b.data.date)-new Date(a.data.date);
}).slice(0,10);

---

<BaseLayout pageTitle="Blog">

	{ sortedPosts.map((post:any) => 
	<div>
		<h3><a href={ `/posts/${post.id}` }>{post.data.title}</a></h3>
		<p><i>Published { formatDate(post.data.date)}</i></p>
		<p set:html={ excerpt(post.rendered.html)}></p>
	</div>
	)}
	<p>
		<a href="all.html">Every Post Ever</a>
	</p>
</BaseLayout>

I think the import bits here are on top. You can see I need to sort my posts and I do so such that the most recent posts are on top. (In theory this sort would be faster if I pre-processed the string based dates into Date objects once, but the demo was working so fast now I didn't bother.)

Now note the the link. To make this work, I created a new file, src/pages/posts/[...id].astro. The rest parameter in the filename (...id) is important. I'll explain after sharing the file contents:

---
import BaseLayout from '../../layouts/BaseLayout.astro';

import { getCollection, render } from 'astro:content';

export async function getStaticPaths() {
  const posts = await getCollection('blog');

  return posts.map(post => ({
    params:{ id: post.id },
    props: { post },
  }));

}

const { post } = Astro.props;
const { Content } = await render(post);
---

<BaseLayout pageTitle={post.data.title}>
  <Content />
</BaseLayout>

My id values are coming from the permalink of my blog posts and look like so: permalink: /2026/02/09/creating-reddit-summaries-with-url-context-and-gemini. Notice the forward slashes? This was throwing errors in Astro when I originally named my file [id].astro. The rest parameter version fixed that immediately.

That's almost the last issue. With this in place, I could browse a few blog posts and see how they looked. I noticed something odd though. I had a header with three dots in it:

## Temporal is Coming...

And when rendered out, it turned into trash. I went to Gemini, asked about it, and it turned out to be an issue with Astro's Markdown processor considering the three dots a Unicode ellipsis character. My app didn't have a "real" HTML layout at this point (I added BaseLayout later) and was missing:

<meta charset="utf-8" />

As soon as that was added, it rendered just fine!

Blog home page

And how well did it perform when building? At near seven thousand pages, npm run build took...

Pause for effect

8 seconds. That's pretty dang good I'd say.

So, if you want to try this yourself, you can find the source here: https://github.com/cfjedimaster/astro-tests/tree/main/rayblog

Note! I thought it was a bit of a waste to check in all of my blog posts in this repo so I filtered it down to the last three years. If you want to recreate that I did (and heck, you can probably make it quicker, if you do, drop me a line!), you can clone my posts here: https://github.com/cfjedimaster/raymondcamden2023

Photo by Jeremy Thomas on Unsplash

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

File archive in OneDrive

1 Share

Stephen and Arvind welcome back Trent Green for a deep dive into one of the most requested follow-ups to site archive: file-level archive. 

Trent explains how this new capability gives IT admins a more precise way to manage storage—archiving individual files or folders within active sites instead of entire sites. The team walks through how it works, what the end-user experience looks like, and how archived files can be reactivated (including a built-in seven-day undo window). 

They also cover public preview timing, consumption-based billing, and what's coming next—like admin-driven policies based on last access dates. If you're looking for smarter ways to manage aging content without disrupting collaboration, this episode breaks it down clearly and practically! 





Download audio: https://traffic.libsyn.com/clean/secure/syncup/RP4380_MCAGS_SyncUpPodcast_012026.mp3?dest-id=1486229
Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Daily Reading List – February 13, 2026 (#721)

1 Share

Shorter list today as I begin my trip to Delhi. I’m in NYC at this very moment, and getting ready for the next segment. Expect regular reading lists next week, just at a more unusual time (for me).

[blog] Gemini 3 Deep Think: Advancing science, research and engineering. Fantastic performance on sincerely challenging tasks.

[blog] In defense of not reading the code. This isn’t a glib take by someone outside of tech. It’s a well-reasoned point and I enjoyed the challenge to the conventional wisdom.

[blog] Besieged. Good piece. AI is impacting so many dimensions of work and community. Plenty of it is positive, but there’s a lot of unknowns.

[article] MIT’s new fine-tuning method lets LLMs learn new skills without losing old ones. Still early days. We’ll keep seeing advances that make long-running interactions better.

[blog] Learn fundamentals, not frameworks. Someone needs to know frameworks in 2026, but that’s not me. I’m definitely trying to invest more in fundamentals.

[blog] Codelab — Gemini for Developers. Gemini covers a lot of territory for us, and this new codelab goes through the full spectrum of products.

[blog] 5 Things That Cause High Latency in Your APIs (and How to Fix Them). See, these are good fundamentals! Regardless of language or stack, these are durable architectural ideas to store away.

[blog] Don’t Miss a Beat: Scaling GKE with VPA, Size Recommender and In-Place Pod Resizing. Vertical pod autoscaling is a useful feature for your platform teams to turn on.

Want to get this update sent to you every day? Subscribe to my RSS feed or subscribe via email below:



Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

Some Git

1 Share
Configuration Rewrite history (rebase -i) with VSCode A rebase with VSCode does not work, as the editor window is immediately closed and the command is ended. Unless you add this to your config. [sequence] editor = code --wait --reuse-window Aliases Here are some aliases that I want to remember. Squash-commits I have two commits and thing they should be one. The message of the last commit is used for the new commit.
Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete

DPO Fine-Tuning Using Microsoft Foundry SDK

1 Share

In the rapidly evolving landscape of large language models (LLMs), achieving precise control over model behavior while maintaining quality has become a critical challenge. While models like GPT-4 demonstrate impressive capabilities, ensuring their outputs align with human preferences—whether for safety, helpfulness, or style—requires sophisticated fine-tuning techniques. Direct Preference Optimization (DPO) represents a breakthrough approach that simplifies this alignment process while delivering exceptional results.

This comprehensive guide explores DPO fine-tuning, explaining what it is, how it works, when to use it, and how to implement it using Microsoft Foundry SDK. Whether you’re building a customer service chatbot that needs to be consistently helpful, a content generation system that should avoid harmful outputs, or any AI application where response quality matters, understanding DPO will empower you to create better-aligned models.

What is Direct Preference Optimization (DPO)?

Direct Preference Optimization is an innovative technique for training language models to align with human preferences without the complexity of traditional Reinforcement Learning from Human Feedback (RLHF). Introduced in the groundbreaking paper Direct Preference Optimization: Your Language Model is Secretly a Reward Model” by Rafailov, Sharma,…, DPO fundamentally reimagines how we teach models to generate preferred outputs.

Unlike traditional supervised fine-tuning where you show a model “what to say,” DPO teaches models by showing them comparative examples: “this response is better than that one.” For each prompt, you provide:

  • Preferred response: A high-quality, desirable output
  • Non-preferred response: A lower-quality or undesirable output

dpo image image

The model learns to increase the likelihood of generating preferred responses while decreasing the probability of non-preferred ones, all without requiring explicit reward modeling or complex reinforcement learning pipelines.

Best Use Cases for DPO:

  • Response Quality & Accuracy Improvement
  • Reading Comprehension & Summarization
  • Safety & Harmfulness Reduction
  • Style, Tone, & Brand Voice Alignment
  • Helpfulness & User Preference Optimization

How Direct Preference Optimization Works

The following code demonstrates DPO fine-tuning using the Microsoft Foundry Projects SDK:

import os
from dotenv import load_dotenv
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient

# Load environment variables
load_dotenv()

endpoint = os.environ.get("AZURE_AI_PROJECT_ENDPOINT")
model_name = os.environ.get("MODEL_NAME")

# Define dataset file paths
training_file_path = "training.jsonl"
validation_file_path = "validation.jsonl"

credential = DefaultAzureCredential()
project_client = AIProjectClient(endpoint=endpoint, credential=credential)
openai_client = project_client.get_openai_client()

# Upload training and validation files
with open(training_file_path, "rb") as f:
    train_file = openai_client.files.create(file=f, purpose="fine-tune")

with open(validation_file_path, "rb") as f:
    validation_file = openai_client.files.create(file=f, purpose="fine-tune")

openai_client.files.wait_for_processing(train_file.id)
openai_client.files.wait_for_processing(validation_file.id)

# Create DPO Fine Tuning job
fine_tuning_job = openai_client.fine_tuning.jobs.create(
    training_file=train_file.id,
    validation_file=validation_file.id,
    model=model_name,
    method={
        "type": "dpo",
        "dpo": {
            "hyperparameters": {
                "n_epochs": 3,
                "batch_size": 1,
                "learning_rate_multiplier": 1.0
            }
        }
    },
    extra_body={"trainingType": "GlobalStandard"}
)

DPO Fine-Tuning Results:

print(f"Testing fine-tuned model via deployment: {deployment_name}")

response = openai_client.responses.create(
    model=deployment_name,
    input=[{"role": "user", "content": "Explain machine learning in simple terms."}]
)

print(f"Model response: {response.output_text}")

Inference result:

Model response: Machine learning is like teaching a computer to learn from experience, similar to how people do. Instead of programming specific instructions for every task, we give the computer a lot of data and it figures out patterns on its own. Then, it can use what it learned to make decisions or predictions. For example, if you show a machine learning system lots of pictures of cats and dogs, it will learn to recognize which is which by itself.

Data format example:

{
  "input": {
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user", "content": "What is the capital of France?"}
    ]
  },
  "preferred_output": [
    {"role": "assistant", "content": "The capital of France is Paris."}
  ],
  "non_preferred_output": [
    {"role": "assistant", "content": "I think it's London."}
  ]
}

Comparing DPO to Other Methods

Aspect DPO SFT RFT
Learning signal Comparative preferences Input-output pairs Graded exploration
Data requirement Preference pairs Example demonstrations Problems + grader
Best for Quality alignment Task learning Complex reasoning
Computational cost Moderate Low High

Learn more

The post DPO Fine-Tuning Using Microsoft Foundry SDK appeared first on Microsoft Foundry Blog.

Read the whole story
alvinashcraft
5 hours ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories