In the previous article, we built the Research Agent which can now gather relevant information, but raw facts still don't make compelling LinkedIn posts. Facts explain an idea. Examples make people remember it.
That's why we need the Examples Agent.
So, the Examples Agent takes the research chunks as input and returns exactly three examples. I chose to limit it to only 3 examples to give downstream agents enough variety without creating noise.
Instead of performing another web search, the Examples Agent works entirely from the research collected by the Research Agent. This keeps the pipeline grounded in the same context instead of introducing new information midway through the workflow.
Topic
│
▼
Research Chunks
│
▼
Read Context
│
▼
Extract Examples
│
▼
Generate Missing Examples
│
▼
Return 3 Examples
Before we write any code, let's write the system prompt.
I wanted the Examples Agent to first look for existing examples within the research chunks before generating new ones.
Also instead of generic examples, it should look forward to using names of real companies and places and people. The example should clearly convey what exactly happened and why it's relevant to the post and make sure the output is a valid JSON that returns 3 examples.
SYSTEM_PROMPT = """
Find or generate 3 concrete examples
that support the topic.
Rules:
- Prefer examples from research.
- Only generate when necessary.
- Explain who, what happened,
and why it's relevant.
- Return valid JSON.
"""
The production prompt is much longer, but I've shortened it here to highlight the core rules.
Let's breakdown function before writing the code. It should take topic and research chunks as input. Then clean the topic using clean_topic() function.
Rather than asking the LLM for examples with only the topic, we also pass the research chunks gathered in the previous step. This gives the model concrete context to work with and reduces unnecessary hallucinations.
user_message = f"TOPIC: {cleaned_topic}\n\nRESEARCH_CHUNKS:\n" + "\n---\n".join(research_chunks)
Let's pass system prompt and user_message to call_llm() function and as we built in Part 2, strip_json_fences() removes markdown code fences so the response can be parsed as valid JSON.
As a standard practice we would parse the JSON in a try and except block to handle exceptions.
try:
parsed = json.loads(refined_response)
except json.JSONDecodeError:
print("LLM did not return valid JSON:")
print(raw_response)
return []
As you noticed, we are returning [], if the model returns malformed JSON, the agent gracefully returns an empty list instead of stopping the entire pipeline.
def get_examples(topic: str, research_chunks: list[str]) -> list[str]:
cleaned_topic = clean_topic(topic)
user_message = f"TOPIC: {cleaned_topic}\n\nRESEARCH_CHUNKS:\n" + "\n---\n".join(research_chunks)
raw_response = call_llm(SYSTEM_PROMPT, user_message)
refined_response = strip_json_fences(raw_response)
print(f"REFINED: {repr(refined_response)}")
try:
parsed = json.loads(refined_response)
except json.JSONDecodeError:
print("LLM did not return valid JSON:")
print(raw_response)
return []
return parsed
We now have the topic, supporting research, and real-world examples. But before we start writing, we should first evaluate whether our ideas are actually worth turning into a post.
In the next article, we'll build the Critic Agent that challenges our assumptions, identifies weak angles, and helps improve the quality of the content before a single draft is generated.
Top comments (0)