This article explains how to use TX Text Control to mail merge JSON data into a document template and submit the merged document to the SignFabric API. It shows how your own application can create an envelope workflow by sending the document, signer metadata, and workflow options through the API.
Learn about natural language processing and explore the Hugging Face Transformers library in Python for tasks like text classification and summarization.
Natural Language Processing (NLP) focuses on enabling computers to understand, analyze and generate human language.
Modern NLP systems are largely powered by transformer architectures, which introduced the self-attention mechanism to model relationships between words regardless of their position in a sentence. This approach significantly improved performance on tasks such as text classification, summarization, translation and question answering, while also enabling models to capture long-range context more efficiently than earlier methods, such as recurrent neural networks (RNNs).
Popular transformer models include:
BERT (Bidirectional Encoder Representations from Transformers)
GPT (Generative Pre-trained Transformer)
RoBERTa
DistilBERT
T5
These models are pretrained on massive text datasets and can be fine-tuned for specific NLP tasks.
Hugging Face has become one of the most popular platforms for working with these models, providing easy-to-use tools, pretrained transformer models and Python libraries that allow developers to build powerful NLP applications with minimal code.
In this article, we will explore the basics of NLP using the Hugging Face Transformers library in Python, demonstrating how to implement common tasks such as text classification and text summarization using modern transformer models.
The Hugging Face Transformers library includes thousands of pretrained models that can be easily downloaded and used with just a few lines of Python code.
Key components of the Hugging Face ecosystem include:
Transformers library for pretrained models
A dataset library for loading NLP datasets
Tokenizers for efficient text processing
Hugging Face Hub for sharing models and datasets
The library supports both PyTorch and TensorFlow, making it flexible for different deep learning workflows.
Setting Up the Environment
In this tutorial, I assume you are new to Python. First, verify that Python is installed on your system. Then run the following commands to check the Python version and create or activate a virtual environment before installing the required dependencies.
python3 --version
python3 -m venv .venv
source .venv/bin/activate
After that, run the command to install transformers in the project.
pip3 install transformers torch
After that, add requirement.txt file in your project, and add entry:
pandas>=2.0.0
And then again run the command pip3 install -r requirements.txt.
At this point, you have installed all the required dependencies and set up the Python project environment. Now we can start building some NLP pipelines using the libraries we configured.
Text Classification
Text classification is one of the most common NLP tasks. It involves assigning predefined categories to text. Examples include sentiment analysis, spam detection and topic classification.
Using Hugging Face, we can create a text classification model in just a few lines of Python, as shown below.
from transformers import pipeline
import pandas as pd
classifier = pipeline("text-classification")
output = classifier("I am excited for final cricket match")
df = pd.DataFrame(output)print(df)
You should get the output shown below.
In this example, the pipeline automatically loads a pretrained transformer model that performs sentiment analysis. The model analyzes the text and predicts whether the sentiment is positive or negative.
Pipeline
The pipeline is a helper function, that performs three tasks.
Preprocess – turn raw input into what model needs
Run the model – run the model with given input
Postprocess – turn model output into label, score or translated text
Essentially it takes a raw input and gives you clean output.
Named Entity Recognition
Using the pipeline wrapper, it is very easy to work with Named Entity Recognition (NER).
from transformers import pipeline
import pandas as pd
tags = pipeline("ner",aggregation_strategy="simple")
customer_feedback ="Barack Obama visited Tokyo in April 2014 and met with Shinzo Abe. The two leaders discussed trade agreements between the United States and Japan. Later, Obama gave a speech at the Roppongi Hills complex. The New York Times covered the event."
output = tags(customer_feedback)
df = pd.DataFrame(output)print(df)
We are using Hugging Face NER pipeline to process feedback text, automatically identify named entities (such as people, locations and organizations). After converting the extracted entities into a tabular format using the pandas data frame=, you should get this output:
In the example above, we use the NER pipeline to identify named entities in text, such as people, places and organizations.
The aggregation_strategy="simple" option is used to merge subword tokens back into complete entities. Without this setting, some models may split words into subword pieces (for example, “Obama” may appear as “Ob” and “##ama”). Using the “simple” aggregation strategy keeps these fragments combined and returned as a single entity span (such as “Obama”).
Text Summarization
Let’s try text summarization without using pipeline. We can do that in three steps as shown below:
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
model_name ="facebook/bart-large-cnn"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSeq2SeqLM.from_pretrained(model_name)
text ="""
your text
"""
inputs = tokenizer(
text,
return_tensors="pt",
max_length=1024,
truncation=True,
padding=True,)
outputs = model.generate(**inputs,
max_length=150,
min_length=30,
num_beams=4,
early_stopping=True,)
summary = tokenizer.decode(outputs[0], skip_special_tokens=True)print(summary)
The example above follows these steps:
Loading the model
Loading the tokenizer
Tokenizing the input
Generating the summary
Decoding the output and printing
We are following all steps of preprocessing, running the model and the postprocessing as we are not using pipeline. This should give you an idea about pipeline utility function.
So, transformers are particularly powerful for NLP because they process text in parallel and capture context efficiently. The self-attention mechanism allows the model to determine which words in a sentence are most relevant when interpreting meaning.
For example, in the sentence: “Apple released a new product.” The word Apple could refer to a fruit or a technology company. A transformer model can use surrounding words like released and product to infer that the sentence refers to the company.
This contextual understanding is one of the major breakthroughs that transformer models introduced.
Transformer-based NLP systems are now used in many real-world applications:
Chatbots and virtual assistants
Automatic document summarization
Email spam filtering
Customer sentiment analysis
Search engines
Language translation
Code generation tools
Companies increasingly rely on transformer models to analyze large volumes of text data and automate tasks that previously required human interpretation.
I hope that you now have a solid understanding of transformers. Thanks for reading this article.
Learn how to handle mutations with TanStack Query in Vue to be able to send data back the server and keep the whole app in sync.
In my previous article on Data Fetching with TanStack Query for Vue, we went through all the things TanStack Query gives us for free when it comes to pulling data from an API: caching, automatic refetching on window focus, stale times, polling and a much cleaner template than the usual ref plus onMounted dance.
But of course, fetching data is only half of the story. At some point you’re going to need to actually send data back to the server. Creating users, updating records, deleting items, triggering actions. That’s where mutations come in!
In this article we’ll take a look at how to handle mutations with TanStack Query in Vue, how to hook into their lifecycle, how to keep the rest of your app in sync after something changes, and a little bit on optimistic updates.
What Is a Mutation?
In the context of TanStack Query, a mutation is any operation that changes server state. POST, PUT, PATCH, DELETE, basically anything that isn’t a GET. Queries fetch data; mutations change it.
The reason TanStack Query treats mutations as their own concept instead of just “another query” is because they behave very differently:
Queries run automatically when a component mounts or when their key changes.
Mutations are fired manually, usually as a reaction to a user action like submitting a form or clicking a button.
Queries are cached. Mutations are not.
After a mutation succeeds, other queries might now hold outdated data that we need to refresh.
We’ll address all of these, let’s start with the basics.
Your First Mutation with useMutation
The composable we’re going to use is useMutation, and the API is refreshingly simple. Let’s build a form that creates a new user.
Let’s break it down. useMutation takes a configuration object with one essential property:
mutationFn: The async function that actually performs the mutation. It must return a promise. If the promise rejects, TanStack Query considers the mutation failed.
Notice that unlike useQuery, there’s no mutationKey required here. Mutations can be given a key for advanced scenarios, but for most applications you’ll never need one.
The composable returns several reactive properties, and the ones you’ll end up using the most are:
mutate: The function you call to actually trigger the mutation. Any arguments you pass here are forwarded as the first parameter of your mutationFn.
isPending: A boolean that is true while the mutation is in flight. This one used to be called isLoading in older versions, so if you come across old examples online, that’s why.
isError / isSuccess: Booleans that reflect the final state of the last attempt.
error: Whatever error was thrown from the mutationFn if it failed.
data: The resolved value from the mutationFn if it succeeded.
I can’t stress enough how much cleaner this ends up being compared to writing your own try/catch plus a loading ref plus an error ref every single time you need a form submission.
mutate vs. mutateAsync
useMutation actually returns two different functions that you can use to trigger the mutation: mutate and mutateAsync. They do the same thing but in different ways.
mutate(variables) is fire and forget. It does not return a promise. Errors are swallowed by TanStack Query and surfaced through the reactive isError and error properties. You cannot await it.
mutateAsync(variables) returns a promise that you can await. If the mutation fails, the promise rejects, and you’re expected to handle the error yourself with a try/catch.
I normally use mutate most of the time because the reactive state is usually all I need. mutateAsync is really nice when you need to chain multiple operations together or when you’re inside a larger async flow and you want the mutation to behave like any other awaited call.
const{ mutateAsync }=useMutation({ mutationFn: createUser })const onSubmit =async()=>{try{const user =awaitmutateAsync({ name: name.value })
router.push(`/users/${user.id}`)}catch(err){
console.error('Could not create user', err)}}
A word of caution: if you use mutateAsync and forget the try/catch, you’ll get an unhandled promise rejection warning in your console.
Lifecycle Callbacks
Let’s dive into the juicy bits of mutations now.
One of the things that makes useMutation so useful in real applications is the set of lifecycle callbacks it exposes. You can pass them either when you create the mutation or when you call mutate.
const{ mutate }=useMutation({
mutationFn: createUser,
onSuccess:(data, variables)=>{// data is what mutationFn returned
console.log('User created!', data)},
onError:(error, variables)=>{
console.error('Something broke', error)},
onSettled:(data, error, variables)=>{// Runs no matter what, after onSuccess or onError},})
The three callbacks you’ll use the most are:
onSuccess: Fires when the mutation resolves successfully. Perfect for showing a toast, closing a modal, resetting a form or navigating away.
onError: Fires when the mutation rejects. Great for surfacing error messages to the user.
onSettled: Fires after either onSuccess or onError. Ideal for cleanup logic that needs to run regardless of outcome.
Keep in mind that you can also pass these callbacks directly to the mutate:
If you define callbacks in both places, both will run. The ones on useMutation fire first, then the ones passed to mutate. Remember this if you end up seeing duplicate toasts or double navigations!
Normally you would use the mutate call callbacks whenever you need a specific call of the mutation to do something additional to what is already defined on the useMutation call.
Invalidating Queries After a Mutation
Here’s where mutations really start to shine, and where most of the real world value of TanStack Query lives.
Picture this scenario. You have a user list page that uses useQuery with the key ['users']. The user clicks a button to create a new user, the mutation fires, the backend responds happily with a 201, and… queue drumroll, the list on the screen still shows the old data. The cache has no idea anything changed.
The fix is to tell TanStack Query that the ['users'] query is now stale and should be refetched. We do that via the query client’s invalidateQueries method.
Any query with a matching key is marked stale and, if it is being observed by a mounted component, refetched immediately. If no component is currently observing it, it will simply be refetched the next time it gets mounted.
Pay close attention to what you set to the queryKey!
invalidateQueries({ queryKey: ['users'] }) will also invalidate ['users', 1], ['users', 2] and so on. This default behavior is usually what you will want for you applications, and you will end up setting up keys with specific words like users that you want to invalidate in groups.
If you only want to invalidate exact matches, you can pass exact: true.
I’ve found that 95% of the mutations I write end up following the exact same shape: do the thing, then invalidate the related list and/or detail queries. Once this clicks for you, you’ll start thinking of your data in terms of which queries each mutation affects.
Optimistic Updates
Sometimes waiting for the server feels sluggish. A user clicks “like” on a post, and the heart icon just sits there for 300ms while the request round trips. The fix for this is optimistic updates: update the UI as if the mutation already succeeded, then reconcile with the server afterward.
TanStack Query has first class support for this through the onMutate callback. The general idea goes like this:
On onMutate, cancel any in flight queries for the related key, grab a snapshot of the current cached data, and write the optimistic value into the cache.
Return the snapshot from onMutate so you can use it later.
On onError, roll back to the snapshot.
On onSettled, invalidate the query to make sure the real server data eventually wins.
constlikePost=()=>{someApiCall()}const queryClient =useQueryClient()const{ mutate }=useMutation({
mutationFn: likePost,
onMutate:async(postId)=>{await queryClient.cancelQueries({ queryKey:['posts']})const previousPosts = queryClient.getQueryData(['posts'])// We find the postId and add +1 the likes in the query data
queryClient.setQueryData(['posts'],(old)=>
old.map((p)=> p.id === postId ?{...p, likes: p.likes +1}: p))// Don't forget to retun the modified data!return{ previousPosts }},
onError:(err, variables, context)=>{// The `context` here is what we returned in onMutate
queryClient.setQueryData(['posts'], context.previousPosts)},
onSettled:()=>{
queryClient.invalidateQueries({ queryKey:['posts']})},})
To be quite honest with you, optimistic updates are one of those features that sound wonderful on paper but that I only reach for when the UX genuinely demands it.
For the vast majority of forms and buttons in a typical CRUD app, the plain old “show a spinner, invalidate on success” flow is perfectly fine and a lot easier to reason about. Reach for optimistic updates when the snappiness actually matters, not by default.
Wrapping Up
Mutations are the missing half of the TanStack Query story, and once you pair them with invalidateQueries, the library really starts to pay for itself. You get a consistent way to handle every form, every button, every “do something on the server” action across your entire app. No more scattered loading refs, no more forgetting to refetch data after an update.
Today we covered the basics of useMutation, the difference between mutate and mutateAsync, the lifecycle callbacks, query invalidation and a quick look at optimistic updates. From here I’d recommend poking around the official mutations guide to see the more advanced patterns like mutation scopes and retry configuration.
Meta has launched its first smart glasses without Ray-Ban branding. Starting at $299, they're cheaper than the Ray-Ban Meta Gen 2 while retaining EssilorLuxottica as a design and manufacturing partner. The Verge reports: As far as style and specs, the Meta Glasses aren't that different from Ray-Bans. The internal specs are the same as the recently released Ray-Ban Meta Optics Styles, with slightly longer battery life. The Adventurer models have thinner rims, while the Fury models hew a bit closer to the Meta Ray-Ban Display with a bolder, chunkier frame. You could describe the Adventurer as square, and the Fury as even more square. The Kylie glasses sport a more unique design with a distinct Y2K flavor that I'm told is meant to be worn lower on your nose. [...] While playing around with the Meta Glasses, it was hard not to notice that the camera appears smaller than in previous Ray-Ban glasses. Technically, Himel tells me, that's not new to these Meta Glasses. It was actually introduced back in March with the prescription-optimized Optics Styles.
[...] Meta is quadrupling down on AI. The new Meta Glasses will all launch with Muse Spark, the first model out of Meta's Superintelligence Labs. (It'll also be arriving on older Ray-Ban and Oakley glasses in the US and Canada via a software update.) Supposedly, that means more helpful glasses. At my hands-on, I was told that Meta AI would now be less stiff. I'd be able to talk to it more naturally and get smarter responses. The AI now supports 14 more languages, including Arabic, Japanese, Mandarin, Hindi, and Korean. Pedestrian turn-by-turn navigation is also coming to Meta's displayless glasses. Later this month, there'll be a new "dynamic photo" feature that automatically takes multiple frames and then recommends the best one.
Here’s the thing about senior leadership that nobody warns you about: the job isn’t hard because of any single task. It’s hard because your work lives in fifteen different places and your brain is the only system connecting them.
Meetings bleed into each other. Decisions are made in threads without you. Someone mentioned your name in a planning meeting, and now there’s an action item living in a doc you’ve never seen. You’ll find out about it in two weeks when someone casually asks for an update. Fun.
Last year, my team almost missed a performance review deadline because it was announced in a channel nobody was watching. One person spent ten minutes searching Slack and couldn’t find it. Another found the date in a random, unrelated channel. I ended up posting “I’ll admit we dropped the ball on following up in Slack, so that’s on me.” That’s the kind of thing that keeps happening when your brain is the only system connecting everything.
I was spending so much energy on context-switching that I had nothing left for the thinking, connecting, and creating that my role actually requires (and that’s the work I actually like doing). But I started using automations in the GitHub Copilot app, and it changed my entire workflow. Bear with me.
What automations actually are
The GitHub Copilot app is a standalone desktop app for macOS, Windows, and Linux, built for working with agents, not just talking to them. You can run parallel sessions across repositories, each on its own branch and worktree. You can see what agents are doing in real time through canvases, which are bidirectional work surfaces where you and the agent operate on the same plan, terminal, or browser session. Progress is visible and steerable, not buried in chat history.
Automations are scheduled prompts that run against your real work context: your calendar, your email, your messages, your GitHub repos. They connect through MCP servers and integrations, so they can see what’s happening across all the places your work lives. They tell me what actually needs my attention, which lets me ignore the rest.
Think of them as agents with a standing brief. You tell them what to care about, how to think, and when to run. Then they just… do it. Every day. Without you remembering to ask. Which is good, because you won’t.
What this looks like
I’m a senior director at GitHub. I lead developer relations. My scope is wide, my calendar is full, and my brain works differently than most people assume. I’m AuDHD, which means I’m good at pattern recognition and deep focus, but genuinely terrible at remembering which thread I promised to follow up on three days ago.
I didn’t set out to build 40 automations. I was curious about the automations tab, asked the app what it could do, and it suggested things I hadn’t thought of. The first time I set one up, I opened a chat and said something like: “Look across all of my work surfaces, my calendar, my email, my messages, and figure out where I’m dropping balls, where I might need help, and suggest automations that would be useful.”
It immediately suggested about six. The first drafts weren’t perfect, and that’s okay. You refine them. You give them voice. You teach them how you think. Once I saw what was possible, I kept going. Now I have about 40. (I know. I know.)
I’m not going to walk through all of them. (You’re welcome.) But here are the categories that matter most, and some highlights from each.
The morning brief
Every day before I open anything, several automations have already run. Meeting Prep pulls my calendar and builds context for every meeting, with different formats for one-on-ones vs. large syncs vs. external calls. By the time I sit down, I know what each meeting is about and what I need to bring. Pre-Meeting Access Check verifies I actually have access to the docs and links referenced in the invite. No more showing up and realizing the agenda doc is locked. If you’ve never experienced that particular panic, honestly, must be nice. Daily Triage Digest sweeps GitHub, email, and messages for anything that needs my attention.
The cumulative effect is that my mornings went from “frantically opening twelve tabs while pretending I’ve read the agenda” to “reading a few summaries with coffee.” It’s a different life.
Staying current
I cannot be surprised by our own launches. That’s literally the job.
Ship Decoder finds everything GitHub shipped in the last 24 hours and explains it to me in plain language. This is real context I can use in conversations. Launch Radar runs weekly and surfaces upcoming launches that touch my team’s space so I’m never blindsided. These two alone probably save me an hour a day of scrolling through channels trying to piece together what happened. I used to spend that hour. I did not enjoy that hour.
Career architecture
This is the category that surprised me most. I built automations that actively work on career development, and if that sounds weird, stay with me.
Daily Wins Recap runs every evening and summarizes what I actually accomplished. This one matters more than it sounds. My default mode is to check something off and immediately move to the next thing. I don’t sit with it. I don’t recognize it. I just keep going. Then performance review season comes around. I have to articulate my impact, and I’m panic-staring at a blank doc trying to remember eight months of work.
This automation keeps a running record so I don’t have to. Think of it as a gratitude practice backed by real data rather than a task list. It counters the “what did I even do today?” spiral that hits hardest on the busy days. On the days when imposter syndrome is loud, I need something that talks back to it with facts. The robot believes in me even when I don’t. That’s oddly moving? I don’t know. It works.
Team and people
This is where I want to be really honest, because I know you might be thinking: is she automating the human parts of her job?
No. And that distinction matters to me more than anything else in this post.
Commitments and Follow-Up Tracker searches my own messages for things I said I’d do and flags what I haven’t done yet. This one is humbling. And essential. Because when I tell someone “I’ll look into this” and then forget, that’s a trust problem. The automation protects the trust.
The kudos I write are still mine. The noticing is still mine. The automation just makes sure my brain doesn’t steal it from the people who deserve it.
These automations don’t replace connection. They enable it. They give me back the headspace to actually show up for people. Before this system, I’d walk into conversations distracted or running on fumes, because my brain was full of operational noise. Now when I sit down in a one-on-one, I’m actually present. When I write recognition for my team, it’s specific and real.
The automations handle the scaffolding. I do the human work. That’s the deal.
Maintenance and logistics
This category covers the boring stuff that quietly eats your week if you let it: Dependabot PR Triage finds and merges safe dependency updates across my repos daily. Handled. Stale Work Finder surfaces pull requests I opened and forgot, issues that went quiet, branches collecting dust. (We all have those. Don’t lie.) Travel Logistics Tracker watches for conference-related threads and consolidates logistics into a single brief. Conference season is chaos. This helps.
What my automations look like
Here’s a real one from my setup, the Stale Work Finder, so you can see what these prompts actually look like in practice:
Find all my stale work across GitHub using the gh CLI. Things that are falling through the cracks.
Check for:
- PRs I opened that haven't received a review in 7+ days
- PRs I'm assigned to review that I haven't reviewed yet (older than 3 days)
- Issues assigned to me that have had no activity in 14+ days
- Draft PRs I own that have been drafts for 2+ weeks
- For each item show: repo, title, link, how long it's been stale, and who's involved.
Format as:
1. 🔴 Embarrassingly stale (3+ weeks)
2. 🟡 Getting dusty (1-3 weeks)
3. 🟢 Just needs a nudge (under a week)
That’s it. That’s the whole automation. You write a prompt, set a schedule, and the agent runs it on your behalf. You can get as detailed or as loose as you want. The app fills in context from your connected tools. It runs every Monday for me and the results are… always a little eye-opening. But it’s better to know.
The AuDHD part
I’ll just say it: for me, automations are an accessibility tool.
AuDHD means my executive function and working memory are wildly inconsistent, and the inconsistency is the hardest part to explain to people. Some days I can hold seventeen threads in my head. Other days I forget I have a meeting in 10 minutes. There is no in between. The gap between those days used to scare me, because my team deserves consistent leadership regardless of what my brain is doing on any given Tuesday.
These automations narrow that gap. They make me consistent. They mean my team gets the same quality of attention whether my executive function showed up today or not. For me, that’s the difference between thriving and slowly burning out. And I’ve done the burning out part. Zero stars, would not recommend.
How to start (for real)
If you’re thinking about building something like this, here’s what I’d say: don’t try to automate everything at once. Start with the one thing that causes you the most friction.
For me, it was meeting prep. I kept walking into meetings cold because prep required visiting four different tools and synthesizing information I didn’t have bandwidth to synthesize. One automation fixed that. And once I felt that relief, I kept going. And going. And going.
Could I consolidate some of these? Probably. I have about 40, and I’m sure some of them could be combined. I prefer specificity, but you could easily roll several into one big automation if that’s more your style.
Here’s the trick that worked for me: open a chat in the GitHub Copilot app and ask it to audit your work surfaces. Where are you dropping balls? Where are the repetitive patterns? What’s the thing you keep meaning to do but never get to? Start there.
The first draft won’t be perfect. That’s fine. You refine it in conversation. You teach it your voice, your priorities, how you think about “good.” Then you let it run.
Start with one. See how it feels.
Then build another. And another. And before you know it, you have 40, and you’re writing a blog post about it. Anyway.
The bigger picture
I think we’re at an interesting moment for how people relate to AI at work. The early conversation about AI at work was mostly about generation. Make me a thing. Write me the code. The reality, at least for me, is more like augmentation of invisible labor. The stuff that burns you out but never shows up in your output. The meta-work nobody acknowledges in performance reviews but everyone is buried in.
Every leader I know is overwhelmed by context. Every neurodivergent professional I know is spending enormous energy on systems that neurotypical people navigate without thinking about. Automations won’t fix organizational dysfunction or bad management or an unreasonable workload. But they can give you back enough headspace to actually do the work you’re here to do.
And honestly? That’s enough. That’s a lot.
And look, this is a GitHub product. It’ll also run your dependency updates, triage your issues, do security sweeps across your repos. The developer workflows are exactly what you’d expect. I just happen to use it for the parts of my job nobody talks about.