Our system can now understand a topic, gather research, find supporting examples, identify weak assumptions, structure everything into a content brief, generate multiple hooks, write two complete LinkedIn drafts and score them.
But there was still one problem.
Everything was running locally through main.py.
That's useful while developing and testing the agents, but if we want an actual application to use this system, we need a way for another application to communicate with our pipeline.
That's where FastAPI comes in.
Instead of running the entire pipeline manually from the terminal, we can expose it through an API endpoint.
The client sends a topic.
The backend runs all the agents.
It then returns the hooks, drafts and scores.
User Topic
│
▼
FastAPI Backend
│
▼
Multi-Agent Pipeline
│
├── Orchestrator
├── Research
├── Examples
├── Critic
├── Brief
├── Hooks
├── Drafts
└── Writing Critic
│
▼
Hooks + Drafts + Scores
Before we start writing code, let's define what our API should accept.
The client shouldn't have to know anything about our internal agents. It only needs to provide the topic. That's why we create a simple Pydantic request model:
class GenerateRequest(BaseModel):
topic: str
The request can now look something like:
{
"topic": "The future of AI agents"
}
The backend takes care of everything else.
What should the API return?
By this point, the user ultimately needs three things:
- The generated hooks
- The two generated drafts
- The scores from the Writing Critic
So we create another Pydantic model:
class GenerateResponse(BaseModel):
hooks: list[str]
drafts: list[str]
scores: dict
This gives us a predictable response structure.
GenerateResponse
│
├── hooks
├── drafts
└── scores
Pydantic acts as a contract for what the API should return.
Creating the FastAPI application
Now we can initialize FastAPI:
app = FastAPI()
And create our /generate endpoint:
@app.post("/generate", response_model=GenerateResponse)
def root(request: GenerateRequest):
The response_model tells FastAPI that this endpoint should return data matching our GenerateResponse model.
Now we wire the agents together
The actual pipeline is almost identical to the main.py we built earlier.
We start with the Orchestrator Agent.
classified_topic = classify_topic(request.topic)
if classified_topic is None:
raise HTTPException(
status_code=500,
detail="Failed to classify topic"
)
Then the Research Agent gathers the relevant information.
research = research_agent(request.topic)
if len(research) == 0:
print("Failed to research")
return
The Examples Agent then works with that research:
examples = get_examples(request.topic, research)
if len(examples) == 0:
raise HTTPException(
status_code=500,
detail="Failed to generate examples"
)
The Critic Agent evaluates the research and examples:
critic = be_critique(
request.topic,
research,
examples
)
Then the Brief Agent structures everything:
brief = create_brief(
request.topic,
research,
examples,
critic
)
And finally we move into the writing pipeline.
Research
│
▼
Examples
│
▼
Critique
│
▼
Brief
│
▼
5 Hooks
│
▼
2 Drafts
│
▼
Writing Critic
│
▼
Scores
The Hook Agent generates the five variations:
hooks = generate_hooks(brief)
The Draft Agent generates two complete posts:
drafts = generate_drafts(hooks, brief)
And the Writing Critic evaluates those drafts:
scoring = rate_drafts(hooks, drafts)
Finally, we return everything through our response model:
return GenerateResponse(
hooks=hooks,
drafts=drafts,
scores=scoring
)
So from the client's perspective, the entire multi-agent architecture is hidden behind one endpoint.
But what happens when something unexpected breaks?
There are a lot of moving parts in this pipeline.
An API request can fail.
An LLM can return an unexpected response.
A research request can fail.
An agent can throw an exception that we didn't anticipate.
We already added error handling inside individual agents, but we also need a final safety net at the API level.
That's why I added a global exception handler:
@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
return JSONResponse(
status_code=500,
content={
"detail": f"Unhandled Exception: {str(exc)}"
}
)
Now unexpected exceptions are returned as a structured API response instead of leaving the request without a useful result.
The final architecture
At this point, the system looks like this:
User Topic
│
▼
FastAPI Backend
│
▼
Orchestrator Agent
│
▼
Research Agent
│
▼
Examples Agent
│
▼
Critic Agent
│
▼
Brief Agent
│
▼
Hook Agent
│
▼
Draft Agent
│
▼
Writing Critic Agent
│
▼
Hooks + Drafts + Scores
The client doesn't need to know which agent does what.
It simply sends a topic to:
POST /generate
And receives the complete result.
This is where I'm ending the series
With the FastAPI backend in place, we now have the complete pipeline wired together.
We started with the Orchestrator Agent, then added the Research Agent, Examples Agent, Critic Agent, Brief Agent, Hook Agent, Draft Agent and finally the Writing Critic Agent.
Each agent has a specific responsibility, and FastAPI brings all of them together behind a single endpoint.
But this wasn't the final version of the system.
While building it, there were quite a few pivots along the way. I initially experimented with Ollama and local models, but eventually switched to Groq models.
I also explored fine-tuning before realising that I simply didn't have enough training data for it to be worth pursuing.
There were many smaller decisions like these throughout the project. Some approaches worked, some didn't, and quite a few things had to be changed as I understood the problem better.
And that was probably the biggest learning for me.
Building an AI system isn't just about getting an LLM to produce an output. It's about figuring out what each component should be responsible for, what information it should receive, what it should return, and how the different components should work together.
This series documents one version of that system, not necessarily the final one.
There are still plenty of things I'd change if I were building it again.
But for now, I'm ending the series here.
Thanks for following along throughout the build.
The complete code is available here:
GitHub Repo: https://github.com/Manav-N4/linkedin-agent
Top comments (0)