I'm Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem (including Node.js, React, Angular and Vue.js) I am expert in AI and in creating AI integrated solutions I am expert in DevOps and Serverless Architecture (AWS, Google Cloud and Azure) I am expert in PHP and its frameworks (such as Codeigniter and Laravel). |
Samuel FajreldinesI am a specialist in the entire JavaScript and TypeScript ecosystem. I am expert in AI and in creating AI integrated solutions. I am expert in DevOps and Serverless Architecture I am expert in PHP and its frameworks.
|
Building intelligent AI agents that can understand natural language and perform complex tasks has become increasingly important in modern app development. Recently, I built a comprehensive fitness AI agent for my app using LangChain.js that can understand user input, make intelligent decisions, and execute appropriate actions. This post explores the real implementation, architecture decisions, and lessons learned.
The fitness domain presents unique challenges for AI agents:
Overcoming these challenges requires more than just a chatbot—it demands a system capable of understanding intent, managing context, and executing specific tasks. That’s where LangChain.js comes in.
LangChain.js is a powerful open-source framework designed to build AI agents that can reason, make decisions, and call external tools based on natural language input. It provides a modular way to orchestrate large language models (LLMs) like GPT with structured workflows, memory, tools, and prompts—all essential for building intelligent, context-aware applications.
LangChain shines when your application needs to:
The implementation uses a clean, modular architecture built around LangChain.js core concepts:
// Core service structure
export default class AiCoreService {
protected llm = new ChatOpenAI({
model: 'gpt-4.1-mini',
temperature: 0
});
protected prompt = ChatPromptTemplate.fromMessages([
['system', fs.readFileSync(path.join(__dirname, 'prompt.md'), 'utf8')],
new MessagesPlaceholder('chat_history'),
['human', '{input}'],
new MessagesPlaceholder('agent_scratchpad')
]);
async run(rawInput: string): Promise<AiCoreServiceResponse> {
const agent = await createOpenAIToolsAgent({
llm: this.llm,
prompt: this.prompt,
tools
});
const agentExecutor = new AgentExecutor({
agent,
verbose: false,
tools
});
const result = await agentExecutor.invoke(
{ input: rawInput, chat_history: this.chatHistory },
{ metadata: { userId: this.userId } }
);
return this.parseStructuredOutput(result.output);
}
}
The real power comes from well-designed tools that handle specific fitness-related tasks:
This tool enables the agent to search through user history using semantic search:
export default new DynamicStructuredTool({
name: 'search_on_context',
description: 'Search user\'s personal context and history to answer questions about past activities, progress, or feelings.',
schema: z.object({
questionOrMessage: z.string().describe('User question or message'),
questionOrMessageLanguage: z.number().describe('Original language code'),
questionOrMessageInEnglish: z.string().describe('Question translated to English')
}),
func: async ({ questionOrMessageInEnglish }, runManager?, config?) => {
const userId = config?.metadata?.userId;
const graphiti = new GraphitiService('user', userId);
const searchResults = await graphiti.search({
query: questionOrMessageInEnglish
});
return searchResults.results
.filter(result => result.invalid_at === null)
.map(result => result.fact)
.join('\n');
}
});
Check more about Graphiti in: https://www.samuelfaj.com/posts/2025-07-14-222718/index.html
The food tool demonstrates sophisticated data processing and validation:
export default new DynamicStructuredTool({
name: 'register_food_eating',
description: 'Register food consumption when user reports eating something.',
schema: z.object({
date: z.string().optional().describe('Date in ISO format'),
foods: z.array(z.object({
name: z.string().describe('Food name in Portuguese'),
englishName: z.string().describe('Food name in English'),
amount: z.number().describe('Amount consumed'),
unit: z.string().describe('Measurement unit (g, ml, etc.)'),
amountInGrams: z.number().describe('Amount in grams'),
calories: z.number().describe('Calories in portion'),
proteinsInGrams: z.number().describe('Protein content'),
carbsInGrams: z.number().describe('Carbohydrate content'),
fatsInGrams: z.number().describe('Fat content'),
confidence: z.number().min(0).max(100).describe('Accuracy confidence 0-100'),
source: z.string().optional().describe('Data source (TACO, USDA, etc.)')
}))
}),
func: async ({ foods, date }, runManager?, config?) => {
// Process and validate food data
const processedFoods = await Promise.all(
foods.map(food => validateAndEnrichFoodData(food))
);
// Save to user's daily log
const dailyMealLog = await DailyMealLogService.findOrCreate(userId, date);
const result = await dailyMealLog.addFoods(processedFoods);
return formatFoodRegistrationResponse(result);
}
});
The system prompt is crucial for agent behavior. Here's the actual prompt structure used:
You are the AI agent for the app www.peso-certo.com, an app focused on health, fitness, weight loss, and overall well-being.
Your mission is to help users achieve their physical goals by providing **clear**, **useful**, and **personalized** answers, without offering medical diagnoses or treatments.
### Available Tools and Functions:
• When the user asks about their history, use `search_on_context`.
• When the user reports having eaten something with quantity, use `register_food_eating`.
• When the user reports exercising, use `register_exercise`.
• When the user expresses emotions or reflections, use `register_diary_entry`.
### Important Rules:
- For exercises, always recommend **demonstration videos and photos**.
- For food substitutions, match them **by calorie content**.
- Respond in the user's language.
- Use headings and emojis moderately.
- **Never provide medical diagnoses.**
## Response Format:
If you're not calling a function, respond in JSON:
{
"answer": "Markdown-formatted answer",
"sources": [{ "title": "Title", "url": "https://example.com" }]
}
The chat service handles conversation flow and context:
export default class ChatService {
constructor(readonly chat: DocumentType<GPTConversation>) {
const userId = this.chat.userId.toString();
this.aiCoreService = new AiCoreService(userId);
this.messageProcessingService = new MessageProcessingService(this);
}
async addUserMessage(params: {
message?: string | null
imageData?: string | null
audioData?: string | null
}) {
const content = await this.messageProcessingService.processMessage(params);
this.chat.messages.push({ role: 'user', content });
this.aiCoreService.setChatHistory(this.chat.messages);
return this;
}
async invokeAiCore() {
const response = await this.aiCoreService.run(
await this.getLastUserMessage()
);
this.addAssistantMessage(response);
return this;
}
}
One key feature is handling both tool calls and structured responses:
parseStructuredOutput(output: string): AiCoreServiceResponse {
let content = output;
let sources: Array<{ title: string, url: string }> | undefined;
try {
// Try to parse JSON response
const match = output.match(/```json\s*([\s\S]*?)```/i);
if (match) {
const json = JSON.parse(match[1]);
content = json.answer;
sources = json.sources;
} else {
const json = JSON.parse(output);
content = json.answer;
sources = json.sources;
}
} catch (e) {
// Fallback to raw output if parsing fails
}
return { content, sources: sources || this.DEFAULT_SOURCES };
}
Using temperature: 0
ensures consistent tool calling and reduces hallucination in structured outputs.
The food tool implements intelligent data validation, falling back to external APIs when confidence is low:
const process = async (food: Food): Promise<Food> => {
if (food.confidence >= 85) {
return food;
}
// Search external food databases
const searchResults = await FoodSearchService.searchFoods(
food.name,
food.englishName,
food.amount,
food.unit
);
const bestMatch = await FoodSearchService.selectBestFoodMatch(
food.name,
searchResults,
{ macros: extractMacros(food) }
);
return enrichFoodWithExternalData(food, bestMatch);
};
The agent handles Portuguese input while using English for API calls and data processing.
Tools receive user context through the metadata
parameter, enabling personalized responses without exposing sensitive data to the model.
Building a production-ready AI agent with LangChain.js requires careful attention to tool design, prompting strategy, and system architecture. The fitness domain provides an excellent case study for complex, multi-modal AI interactions.
The key to success lies in understanding that the agent is not just a chatbot—it's an intelligent coordinator that can understand intent, access relevant data, and execute appropriate actions. By combining LangChain.js's powerful orchestration capabilities with domain-specific tools and careful prompt engineering, we can create AI experiences that truly understand and assist users in their fitness journey.
The full implementation demonstrates that with the right architecture, AI agents can handle real-world complexity while maintaining reliability, performance, and user satisfaction.
About Me
Since I was a child, I've always wanted to be an inventor. As I grew up, I specialized in information systems, an area which I fell in love with and live around it. I am a full-stack developer and work a lot with devops, i.e., I'm a kind of "jack-of-all-trades" in IT. Wherever there is something cool or new, you'll find me exploring and learning... I am passionate about life, family, and sports. I believe that true happiness can only be achieved by balancing these pillars. I am always looking for new challenges and learning opportunities, and would love to connect with other technology professionals to explore possibilities for collaboration. If you are looking for a dedicated and committed full-stack developer with a passion for excellence, please feel free to contact me. It would be a pleasure to talk with you! |
SecurityScoreCard
Nov. 2023 - Present
New York, United States
Senior Software Engineer
I joined SecurityScorecard, a leading organization with over 400 employees, as a Senior Full Stack Software Engineer. My role spans across developing new systems, maintaining and refactoring legacy solutions, and ensuring they meet the company's high standards of performance, scalability, and reliability.
I work across the entire stack, contributing to both frontend and backend development while also collaborating directly on infrastructure-related tasks, leveraging cloud computing technologies to optimize and scale our systems. This broad scope of responsibilities allows me to ensure seamless integration between user-facing applications and underlying systems architecture.
Additionally, I collaborate closely with diverse teams across the organization, aligning technical implementation with strategic business objectives. Through my work, I aim to deliver innovative and robust solutions that enhance SecurityScorecard's offerings and support its mission to provide world-class cybersecurity insights.
Technologies Used:
Node.js Terraform React Typescript AWS Playwright and Cypress