<?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: Manav</title>
    <description>The latest articles on DEV Community by Manav (@mnv).</description>
    <link>https://dev.to/mnv</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4049182%2F11b056a0-2222-4319-8721-672bf539c255.png</url>
      <title>DEV Community: Manav</title>
      <link>https://dev.to/mnv</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mnv"/>
    <language>en</language>
    <item>
      <title>Building a Multi-Agent AI for Company LinkedIn Pages — Part 2: Building the Orchestrator Agent</title>
      <dc:creator>Manav</dc:creator>
      <pubDate>Tue, 28 Jul 2026 12:16:14 +0000</pubDate>
      <link>https://dev.to/mnv/building-a-multi-agent-ai-for-company-linkedin-pages-part-2-building-the-orchestrator-agent-nb7</link>
      <guid>https://dev.to/mnv/building-a-multi-agent-ai-for-company-linkedin-pages-part-2-building-the-orchestrator-agent-nb7</guid>
      <description>&lt;p&gt;In the previous article, I broke down the problem into independent responsibilities instead of expecting one agent to do everything.&lt;/p&gt;

&lt;p&gt;If every agent is independent of eachother, how do they communicate with eachother? Who decides what runs next?&lt;/p&gt;

&lt;p&gt;That's where the Orchestrator Agent acts like a traffic controller, managing how agents pass context to eachother and maintaining order in which they run.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;linkedin-agent/
├── agents/
│   ├── orchestrator.py     
│   ├── research.py          
│   ├── examples.py          
│   ├── critic.py            
│   ├── brief.py             
│   ├── hook.py              
│   ├── draft.py             
│   └── writing_critic.py    
├── core/
│   ├── llm.py               
│   ├── models.py            
│   └── utils.py            
├── data/
│   └── chroma/
├── main.py                 
├── api.py                   
├── requirements.txt        
└── .env                     
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each agent has it's own file. The core folder has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;llm.py wraps all interactions with Ollama through a single call_llm() function.&lt;/li&gt;
&lt;li&gt;models.py stores shared Pydantic schemas used across agents.&lt;/li&gt;
&lt;li&gt;utils.py has function clean_topic() to remove extra spaces from topic user has entered.&lt;/li&gt;
&lt;li&gt;main.py runs the agent pipeline and api.py has the fastAPI server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Orchestrator Agent has one job: understand the topic, classify it, decide the best content angle, and return structured output for the Research Agent.&lt;/p&gt;

&lt;p&gt;For every llm call we are making we need a SYSYTEM_PROMPT ie the prompt we write to define how the agent has to work and a user_message which has all context from previous agents.&lt;/p&gt;

&lt;p&gt;So, lets start by writing a SYSTEM_PROMPT for the Orchestrator Agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are the orchestrator agent for WanderMesh.

Your job is to analyze a topic and return:

- domain
- content_type
- angle
- reasoning

Rules:
- Return only valid JSON.
- Choose one domain from a predefined list.
- Choose one content type.
- Choose one content angle.
- Explain your reasoning briefly.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I've shortened the prompt for readability. The production version also includes domain-specific categories, output constraints, and a few-shot example to improve classification consistency.&lt;/p&gt;

&lt;p&gt;Rather than asking the model an open-ended question, I constrained its output to a fixed structure. That made the downstream pipeline far more predictable.&lt;/p&gt;

&lt;p&gt;One issue I ran into early was that the model didn't always return valid JSON. Sometimes it wrapped the response in markdown code fences, sometimes it produced trailing commas, and occasionally it returned smart quotes instead of standard JSON quotes.&lt;/p&gt;

&lt;p&gt;Since every downstream agent expected valid JSON, I needed a preprocessing step before parsing the response.&lt;/p&gt;

&lt;p&gt;While testing with smaller local models like gemma2:2b, I found they occasionally ignored formatting instructions and returned malformed JSON despite being explicitly asked for valid JSON.&lt;/p&gt;

&lt;p&gt;To help fix the JSON format I wrote the strip_json_fences() function.&lt;/p&gt;

&lt;p&gt;If you're still getting comfortable with Python, I highly recommend &lt;a href="https://cs50.harvard.edu/python/" rel="noopener noreferrer"&gt;CS50 Python&lt;/a&gt;. I learned a lot by reading the notes first and then implementing everything myself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;strip_json_fences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;```

json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeprefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

```json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;```

&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeprefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;```

&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removesuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;

```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,\s*]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,\s*}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\u201c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\u201d&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\u2018&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\u2019&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even valid JSON isn't enough. The model might forget a field, rename one, or return an unexpected structure. Before handing the output to the next agent, I wanted to validate that it matched exactly what the pipeline expected.&lt;/p&gt;

&lt;p&gt;Further reading: If you're new to Pydantic, the official documentation on &lt;a href="https://pydantic.dev/docs/validation/latest/concepts/models/" rel="noopener noreferrer"&gt;Models&lt;/a&gt; and &lt;a href="https://pydantic.dev/docs/validation/latest/concepts/fields/" rel="noopener noreferrer"&gt;Fields&lt;/a&gt; is an excellent place to understand the validation features used here.&lt;/p&gt;

&lt;p&gt;Now lets create a Pydantic Object for the Orchestrator Agent. For now, I'm only validating the structure, not restricting the possible values. That keeps the orchestrator flexible while the rest of the pipeline is still evolving.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrchestratorOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now lets write the main function for classifying topic. Before writing the code lets fundamentally breakdown what this function should do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Topic
   │
   ▼
clean_topic()
   │
   ▼
call_llm()
   │
   ▼
strip_json_fences()
   │
   ▼
json.loads()
   │
   ▼
Pydantic Validation
   │
   ▼
OrchestratorOutput
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should take topic as input and return Pydantic Object as output for Research Agent to use.&lt;/p&gt;

&lt;p&gt;The topic should first be cleaned using the clean_topic(), removing extra spaces if any and checking if the user hasn't entered an empty string.&lt;/p&gt;

&lt;p&gt;Then we pass the cleaned topic and SYSTEM_PROMPT to the call_llm() function and then remove json fences from it using strip_json_fences() function to return valid JSON.&lt;/p&gt;

&lt;p&gt;Finally, the JSON is parsed and validated. If anything goes wrong, the function returns None instead of passing invalid data further down the pipeline.&lt;/p&gt;

&lt;p&gt;As a standard practice we parse JSON and validate the parsed JSON in try and except blocks to handle Errors if something breaks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;OrchestratorOutput&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cleaned_topic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;clean_topic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;raw_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cleaned_topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;refined_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;strip_json_fences&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refined_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LLM did not return valid JSON:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;OrchestratorOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;model_validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LLM returned JSON but it doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t match expected structure:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the orchestrator can consistently classify a topic and return structured output that every downstream agent can rely on.&lt;/p&gt;

&lt;p&gt;In the next article, I'll build the Research Agent, the component responsible for gathering factual information before any content is generated.&lt;/p&gt;

&lt;p&gt;Github Repo: &lt;a href="https://github.com/Manav-N4/linkedin-agent#linkedin-agent" rel="noopener noreferrer"&gt;https://github.com/Manav-N4/linkedin-agent#linkedin-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>Building a Multi-Agent AI for Company LinkedIn Pages — Part 1: Breaking Down the Problem</title>
      <dc:creator>Manav</dc:creator>
      <pubDate>Mon, 27 Jul 2026 11:34:16 +0000</pubDate>
      <link>https://dev.to/mnv/building-a-multi-agent-ai-for-company-linkedin-pages-part-1-breaking-down-the-problem-20k7</link>
      <guid>https://dev.to/mnv/building-a-multi-agent-ai-for-company-linkedin-pages-part-1-breaking-down-the-problem-20k7</guid>
      <description>&lt;p&gt;As someone who posts on LinkedIn everyday, I've a fair idea on how it's to post on LinkedIn, what works and what doesn't. Recently, I started managing a company's LinkedIn page and quickly realized that it was a completely different problem.&lt;/p&gt;

&lt;p&gt;I used the same writing style as the one on my personal LinkedIn. It didn't work well as people don't relate top company pages the way they do with personal accounts. Company pages have to sound consistent. Every post represents a brand, they need supporting evidence and has to fit a positioning rather than a personal opinion.&lt;/p&gt;

&lt;p&gt;If one were to use chatGPT or Claude for the job, it alone had to do all the work, from research to generating hooks, drafts, maintaining the brand voice and would require constant iteration prompts to get the desired result.&lt;/p&gt;

&lt;p&gt;So, before writing a single line of code or setting up the project, I fundamentally broke down the problem.&lt;/p&gt;

&lt;p&gt;I need to take the topic from the user as input, then the agent needs to do the research, back the research with examples and then create a brief using which it can generate a few hook variations and post drafts, which the user can choose from, where the agent scores the drafts to help user choose the draft from given options.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Felfv3f5css9qe9f3fio6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Felfv3f5css9qe9f3fio6.png" alt="Minimalist horizontal workflow showing: Topic → Research → Examples → Brief → Hooks → Drafts → Review → Publish." width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what if the user didn't like the output? That's when I thought that I need to reduce the probability of the user not liking the post.&lt;/p&gt;

&lt;p&gt;At this point all of this was too much for one agent.&lt;/p&gt;

&lt;p&gt;That's when I thought of building a multi-agent system, where each agent would pick up a task. This makes the pipeline easy to debug and helps me maintain separate code for each agent.&lt;/p&gt;

&lt;p&gt;Another architectural decision was separating validation from generation. Instead of immediately moving from research to writing, I added a validation stage that could identify weak reasoning or inconsistencies before the writing process even began. The generated drafts would then go through another evaluation step, where they were scored before being presented to the user.&lt;/p&gt;

&lt;p&gt;Throughout the building of this multi-agent LinkedIn system we would be using Python and its libraries like PyTorch and Pydantic for writing the agents, wiring them, fine tuning the models and validating output.&lt;/p&gt;

&lt;p&gt;The project also uses RAG with ChromaDB so the system works with relevant retrieved context instead of relying entirely on the model's internal knowledge.&lt;/p&gt;

&lt;p&gt;Currently I've built the agent pipeline, fastAPI backend.&lt;/p&gt;

&lt;p&gt;At the time of writing this, I'm working on the Streamlit interface before moving toward deployment, reliability improvements and eventually making the system work for multiple brands.&lt;/p&gt;

&lt;p&gt;In the next article, I'll break down the orchestrator, the component responsible for deciding how every other agent in the system works together.&lt;/p&gt;

&lt;p&gt;Github Repo: &lt;a href="https://github.com/Manav-N4/linkedin-agent#linkedin-agent" rel="noopener noreferrer"&gt;https://github.com/Manav-N4/linkedin-agent#linkedin-agent&lt;/a&gt;&lt;/p&gt;

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