Building Your First AI Agent
Putting It All Together
You have learned about agent architecture, tools, function calling, and memory management. Now you will build a complete AI agent from scratch: a research and report writing agent that accepts a topic, researches it using simulated web search tools, and produces a structured report.
This walkthrough covers every component: system prompt, tool definitions, the agent loop, error handling, and output formatting.
The Agent We Are Building
Name: Research Reporter Agent
Goal: Accept a research topic, autonomously gather information using search tools, synthesise findings, and produce a structured report.
Tools:
- web_search: Search for information on a topic
- get_page_content: Read detailed content from a URL
- create_report: Save the final report
Success criteria:
- Covers the topic with at least 3 distinct information sources
- Produces a structured report with introduction, key findings, and conclusion
- Completes in under 15 steps
- Handles tool failures gracefully
Step 1: Define the System Prompt
const SYSTEM_PROMPT = `You are a professional research analyst. Your job is to research topics thoroughly and produce well-structured reports.
PROCESS:
1. Break the topic into 3-4 key questions to investigate
2. Search for each question using the web_search tool
3. For promising results, use get_page_content to read details
4. Synthesise your findings into a coherent report
5. Use create_report to save the final report
REPORT FORMAT:
- Title
- Executive Summary (2-3 sentences)
- Key Findings (3-5 bullet points with evidence)
- Detailed Analysis (2-3 paragraphs)
- Conclusion (1-2 sentences)
- Sources (list all URLs consulted)
RULES:
- Always cite your sources
- Do not speculate beyond what the sources support
- If a search returns no results, try a different query
- After no more than 12 steps, compile whatever you have found and write the report
`;
Step 2: Define the Tools
const tools = [
{
type: 'function',
function: {
name: 'web_search',
description: 'Search the internet for information. Returns a list of relevant URLs and snippets. Use for each key question you identified.',
parameters: {
type: 'object',
properties: {
query: { type: 'string', description: 'The search query' },
num_results: { type: 'integer', description: 'Number of results to return (default 5, max 10)', default: 5 }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'get_page_content',
description: 'Read the full text content of a webpage. Use after web_search to get detailed information from specific URLs.',
parameters: {
type: 'object',
properties: {
url: { type: 'string', description: 'The URL to read' },
max_chars: { type: 'integer', description: 'Maximum characters to return (default 3000)', default: 3000 }
},
required: ['url']
}
}
},
{
type: 'function',
function: {
name: 'create_report',
description: 'Save the final research report. Use this only when your research is complete and you are ready to write the final report.',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
content: { type: 'string', description: 'The full report in markdown format' }
},
required: ['title', 'content']
}
}
}
];
Step 3: Implement Tool Functions
// Run in Node.js -- replace with real implementations
const toolImplementations = {
web_search: async ({ query, num_results = 5 }) => {
// In production: call a real search API (Serper, Tavily, Google Custom Search)
console.log('Searching:', query);
// Simulated results
return {
results: [
{ title: 'Introduction to ' + query, url: 'https://example.com/intro', snippet: 'Comprehensive overview of ' + query + '...' },
{ title: query + ' - Current State', url: 'https://research.example.com/current', snippet: 'Recent developments in ' + query + '...' },
{ title: 'The Future of ' + query, url: 'https://analysis.example.com/future', snippet: 'Expert predictions for ' + query + '...' },
].slice(0, num_results)
};
},
get_page_content: async ({ url, max_chars = 3000 }) => {
// In production: use a headless browser or web scraping service
console.log('Reading:', url);
return {
url,
content: 'Detailed content from ' + url + '. [In production, this would be the actual page text, truncated to ' + max_chars + ' characters.]',
word_count: 450
};
},
create_report: async ({ title, content }) => {
// In production: save to database, Google Docs, Notion, etc.
console.log('Creating report:', title);
console.log(content);
return { success: true, report_id: 'rpt_' + Date.now(), title };
}
};
Step 4: The Agent Loop
import OpenAI from 'openai';
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function runResearchAgent(topic) {
console.log('Starting research on:', topic);
const messages = [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: 'Research this topic and produce a comprehensive report: ' + topic }
];
const MAX_ITERATIONS = 15;
let iteration = 0;
let finalReport = null;
while (iteration < MAX_ITERATIONS) {
iteration++;
console.log('\n--- Iteration ' + iteration + ' ---');
const response = await client.chat.completions.create({
model: 'gpt-4o',
messages,
tools,
tool_choice: 'auto',
temperature: 0,
});
const choice = response.choices[0];
if (choice.finish_reason === 'stop') {
// Agent produced a final text response
console.log('Agent finished:', choice.message.content);
break;
}
if (choice.finish_reason === 'tool_calls') {
messages.push(choice.message);
for (const toolCall of choice.message.tool_calls) {
const fnName = toolCall.function.name;
const fnArgs = JSON.parse(toolCall.function.arguments);
console.log('Tool call:', fnName, JSON.stringify(fnArgs));
try {
const fn = toolImplementations[fnName];
if (!fn) throw new Error('Unknown tool: ' + fnName);
const result = await fn(fnArgs);
console.log('Tool result:', JSON.stringify(result).substring(0, 200));
if (fnName === 'create_report') {
finalReport = { title: fnArgs.title, content: fnArgs.content };
}
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result)
});
} catch (error) {
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify({ error: error.message, suggestion: 'Try a different approach or tool.' })
});
}
}
}
}
if (iteration >= MAX_ITERATIONS) {
console.log('Max iterations reached. Requesting final report...');
}
return finalReport;
}
// Run the agent
const report = await runResearchAgent('The impact of AI on software engineering jobs');
console.log('\nFinal Report:', report?.title);
Step 5: Testing Your Agent
Before deploying, test systematically:
- Happy path: Simple, clear topic with readily available information
- Ambiguous topic: A vague topic that requires the agent to make decisions about interpretation
- Niche topic: A topic with limited search results
- Tool failure simulation: What happens if web_search fails partway through?
- Long-running task: A complex topic that requires more than 10 search calls
For each test, review:
- Did the agent complete the task?
- Was the output quality acceptable?
- Did it handle tool failures gracefully?
- Did it stay within the iteration limit?
- Was the final report well-structured?
Frameworks to Accelerate Agent Development
Building agents from scratch is educational, but in production you will likely use a framework:
LangChain (JavaScript or Python): The most popular agent framework. Provides pre-built agent types, tool integrations, memory management, and evaluation tools. Faster to start but adds abstraction complexity.
LlamaIndex: Particularly strong for data and document-centric agent use cases. Excellent RAG integration.
AutoGen (Microsoft): Framework for multi-agent systems where multiple specialised agents collaborate.
Crew AI: High-level framework for defining agent teams with roles and workflows.
Starting with raw API calls (as in this lesson) builds deep understanding. Once you understand the fundamentals, frameworks help you build faster.
Key Takeaways
- A complete agent implementation requires: system prompt, tool definitions, tool implementations, and an orchestration loop with error handling.
- Always set a maximum iteration limit and implement graceful handling for when the limit is reached.
- Wrap each tool call in try-catch and return structured error messages that help the agent decide what to do next.
- Test systematically: happy path, ambiguous inputs, tool failures, and maximum-length runs.
- Frameworks like LangChain and CrewAI accelerate development once you understand the underlying mechanics.
Try it yourself
Key Takeaways
- A complete agent implementation requires: system prompt, tool definitions, tool implementations, and an orchestration loop with error handling.
- The system prompt must include role, process, output format, rules, and stopping conditions to ensure reliable agent behaviour.
- Always wrap tool calls in try-catch and return structured error messages that help the agent adapt when tools fail.
- Test systematically: happy path, ambiguous inputs, tool failures, and long-running tasks that approach the iteration limit.
- Understand the raw implementation before using frameworks -- this knowledge is essential for debugging and architectural decisions.
Quick Quiz
1.What should your agent system prompt always include?
2.What should happen when a tool call fails in an agent loop?
3.Why should you understand raw API-based agent implementation before using frameworks like LangChain?
4.Which of these is a good test case for an agent that would NOT be covered by a simple happy-path test?
Ready to go further?
CareerEx gives you structured 12-week training, live classes every Saturday and Sunday, real tutor feedback, and a certificate. Join the next cohort.
Join CareerEx