[{"data":1,"prerenderedAt":396},["ShallowReactive",2],{"navigation":3,"/blog/integrating-llms-into-laravel-python-microservices":26,"/blog/integrating-llms-into-laravel-python-microservices-surround":391},[4],{"title":5,"path":6,"stem":7,"children":8,"page":25},"Blog","/blog","blog",[9,13,17,21],{"title":10,"path":11,"stem":12},"GitHub Actions CI/CD for Laravel: A Zero-Downtime Deployment Setup","/blog/github-actions-laravel-zero-downtime-deployment","blog/github-actions-laravel-zero-downtime-deployment",{"title":14,"path":15,"stem":16},"Integrating LLMs into Laravel Using Python Microservices","/blog/integrating-llms-into-laravel-python-microservices","blog/integrating-llms-into-laravel-python-microservices",{"title":18,"path":19,"stem":20},"How I Cut Report Generation from 30s to 2s in Laravel","/blog/laravel-report-generation-30s-to-2s","blog/laravel-report-generation-30s-to-2s",{"title":22,"path":23,"stem":24},"Why I Chose PostgreSQL Over MongoDB for a CRM Reporting Engine","/blog/postgresql-vs-mongodb-crm-reporting","blog/postgresql-vs-mongodb-crm-reporting",false,{"id":27,"title":14,"author":28,"body":32,"date":383,"description":384,"extension":385,"image":386,"meta":387,"minRead":127,"navigation":123,"path":15,"seo":388,"sitemap":389,"stem":16,"__hash__":390},"blog/blog/integrating-llms-into-laravel-python-microservices.md",{"name":29,"avatar":30},"Vincent Ndegwa",{"src":31,"alt":29},"/personal/profile.jpeg",{"type":33,"value":34,"toc":375},"minimark",[35,39,42,47,60,63,67,78,81,85,253,257,323,326,330,333,350,354,357,368,371],[36,37,38],"p",{},"In one CRM project, product wanted AI generated follow-up emails and sentiment assisted lead scoring. The data, users, and workflow logic already lived in Laravel. Rewriting everything in Python would have delayed delivery and increased risk.",[36,40,41],{},"The solution was simple: Laravel remained the system of record, and a small FastAPI service handled LLM tasks.",[43,44,46],"h2",{"id":45},"why-this-split-works","Why This Split Works",[48,49,50,54,57],"ul",{},[51,52,53],"li",{},"Laravel keeps auth, permissions, and business rules in one place",[51,55,56],{},"FastAPI hosts AI specific dependencies like LangChain and vector tooling",[51,58,59],{},"Both services evolve independently with clear contracts",[36,61,62],{},"This model is easier to maintain than mixing Python runtime concerns directly into a PHP monolith.",[43,64,66],{"id":65},"architecture","Architecture",[68,69,75],"pre",{"className":70,"code":72,"language":73,"meta":74},[71],"language-text","Nuxt/React UI -> Laravel API -> FastAPI AI service -> LLM provider\n                         \u003C- JSON response \u003C-\n","text","",[76,77,72],"code",{"__ignoreMap":74},[36,79,80],{},"Laravel never exposes provider secrets to the frontend. It validates inputs, enriches context from the database, then calls FastAPI over an internal network.",[43,82,84],{"id":83},"fastapi-endpoint","FastAPI Endpoint",[68,86,90],{"className":87,"code":88,"language":89,"meta":74,"style":74},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","from fastapi import FastAPI\nfrom pydantic import BaseModel\nfrom langchain_openai import ChatOpenAI\nfrom langchain.prompts import ChatPromptTemplate\n\napp = FastAPI()\n\nclass FollowUpInput(BaseModel):\n    client_name: str\n    deal_summary: str\n    sender_name: str\n\n@app.post(\"/generate/follow-up\")\ndef generate_follow_up(payload: FollowUpInput):\n    llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.4)\n\n    prompt = ChatPromptTemplate.from_messages([\n        (\"system\", \"You write concise B2B follow-up emails.\"),\n        (\n            \"human\",\n            \"Client: {client_name}\\nDeal: {deal_summary}\\nSender: {sender_name}\\nWrite an email with a clear next step.\"\n        ),\n    ])\n\n    chain = prompt | llm\n    result = chain.invoke(payload.model_dump())\n    return {\"email\": result.content}\n","python",[76,91,92,100,106,112,118,125,131,136,142,148,154,160,165,171,177,183,188,194,200,206,212,218,224,230,235,241,247],{"__ignoreMap":74},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,99],{},"from fastapi import FastAPI\n",[93,101,103],{"class":95,"line":102},2,[93,104,105],{},"from pydantic import BaseModel\n",[93,107,109],{"class":95,"line":108},3,[93,110,111],{},"from langchain_openai import ChatOpenAI\n",[93,113,115],{"class":95,"line":114},4,[93,116,117],{},"from langchain.prompts import ChatPromptTemplate\n",[93,119,121],{"class":95,"line":120},5,[93,122,124],{"emptyLinePlaceholder":123},true,"\n",[93,126,128],{"class":95,"line":127},6,[93,129,130],{},"app = FastAPI()\n",[93,132,134],{"class":95,"line":133},7,[93,135,124],{"emptyLinePlaceholder":123},[93,137,139],{"class":95,"line":138},8,[93,140,141],{},"class FollowUpInput(BaseModel):\n",[93,143,145],{"class":95,"line":144},9,[93,146,147],{},"    client_name: str\n",[93,149,151],{"class":95,"line":150},10,[93,152,153],{},"    deal_summary: str\n",[93,155,157],{"class":95,"line":156},11,[93,158,159],{},"    sender_name: str\n",[93,161,163],{"class":95,"line":162},12,[93,164,124],{"emptyLinePlaceholder":123},[93,166,168],{"class":95,"line":167},13,[93,169,170],{},"@app.post(\"/generate/follow-up\")\n",[93,172,174],{"class":95,"line":173},14,[93,175,176],{},"def generate_follow_up(payload: FollowUpInput):\n",[93,178,180],{"class":95,"line":179},15,[93,181,182],{},"    llm = ChatOpenAI(model=\"gpt-4o-mini\", temperature=0.4)\n",[93,184,186],{"class":95,"line":185},16,[93,187,124],{"emptyLinePlaceholder":123},[93,189,191],{"class":95,"line":190},17,[93,192,193],{},"    prompt = ChatPromptTemplate.from_messages([\n",[93,195,197],{"class":95,"line":196},18,[93,198,199],{},"        (\"system\", \"You write concise B2B follow-up emails.\"),\n",[93,201,203],{"class":95,"line":202},19,[93,204,205],{},"        (\n",[93,207,209],{"class":95,"line":208},20,[93,210,211],{},"            \"human\",\n",[93,213,215],{"class":95,"line":214},21,[93,216,217],{},"            \"Client: {client_name}\\nDeal: {deal_summary}\\nSender: {sender_name}\\nWrite an email with a clear next step.\"\n",[93,219,221],{"class":95,"line":220},22,[93,222,223],{},"        ),\n",[93,225,227],{"class":95,"line":226},23,[93,228,229],{},"    ])\n",[93,231,233],{"class":95,"line":232},24,[93,234,124],{"emptyLinePlaceholder":123},[93,236,238],{"class":95,"line":237},25,[93,239,240],{},"    chain = prompt | llm\n",[93,242,244],{"class":95,"line":243},26,[93,245,246],{},"    result = chain.invoke(payload.model_dump())\n",[93,248,250],{"class":95,"line":249},27,[93,251,252],{},"    return {\"email\": result.content}\n",[43,254,256],{"id":255},"laravel-service-layer","Laravel Service Layer",[68,258,262],{"className":259,"code":260,"language":261,"meta":74,"style":74},"language-php shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","public function draftFollowUp(Contact $contact, Deal $deal, User $sender): string\n{\n    $response = Http::timeout(10)\n        ->retry(2, 300)\n        ->post(config('services.ai.base_url') . '/generate/follow-up', [\n            'client_name' => $contact->full_name,\n            'deal_summary' => $deal->summary,\n            'sender_name' => $sender->name,\n        ]);\n\n    return $response->throw()->json('email');\n}\n","php",[76,263,264,269,274,279,284,289,294,299,304,309,313,318],{"__ignoreMap":74},[93,265,266],{"class":95,"line":96},[93,267,268],{},"public function draftFollowUp(Contact $contact, Deal $deal, User $sender): string\n",[93,270,271],{"class":95,"line":102},[93,272,273],{},"{\n",[93,275,276],{"class":95,"line":108},[93,277,278],{},"    $response = Http::timeout(10)\n",[93,280,281],{"class":95,"line":114},[93,282,283],{},"        ->retry(2, 300)\n",[93,285,286],{"class":95,"line":120},[93,287,288],{},"        ->post(config('services.ai.base_url') . '/generate/follow-up', [\n",[93,290,291],{"class":95,"line":127},[93,292,293],{},"            'client_name' => $contact->full_name,\n",[93,295,296],{"class":95,"line":133},[93,297,298],{},"            'deal_summary' => $deal->summary,\n",[93,300,301],{"class":95,"line":138},[93,302,303],{},"            'sender_name' => $sender->name,\n",[93,305,306],{"class":95,"line":144},[93,307,308],{},"        ]);\n",[93,310,311],{"class":95,"line":150},[93,312,124],{"emptyLinePlaceholder":123},[93,314,315],{"class":95,"line":156},[93,316,317],{},"    return $response->throw()->json('email');\n",[93,319,320],{"class":95,"line":162},[93,321,322],{},"}\n",[36,324,325],{},"This gives you predictable error handling in one place and keeps controllers clean.",[43,327,329],{"id":328},"production-hardening","Production Hardening",[36,331,332],{},"I learned these checks matter more than the prompt itself:",[48,334,335,338,341,344,347],{},[51,336,337],{},"strict request schemas with Pydantic",[51,339,340],{},"request timeouts and retries at Laravel boundary",[51,342,343],{},"Redis caching for repeated prompt payloads",[51,345,346],{},"circuit breaker fallback when AI service is degraded",[51,348,349],{},"internal only networking for FastAPI",[43,351,353],{"id":352},"what-hiring-teams-usually-care-about","What Hiring Teams Usually Care About",[36,355,356],{},"When I discuss this architecture in interviews, teams respond well to three things:",[48,358,359,362,365],{},[51,360,361],{},"clear separation of concerns",[51,363,364],{},"secure handling of secrets and internal traffic",[51,366,367],{},"realistic failure strategy for AI dependent features",[36,369,370],{},"That is what turns an AI demo into production software.",[372,373,374],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":74,"searchDepth":102,"depth":102,"links":376},[377,378,379,380,381,382],{"id":45,"depth":102,"text":46},{"id":65,"depth":102,"text":66},{"id":83,"depth":102,"text":84},{"id":255,"depth":102,"text":256},{"id":328,"depth":102,"text":329},{"id":352,"depth":102,"text":353},"2025-03-05","A practical guide to wiring a LangChain Python service to a Laravel backend so you can add AI-powered features without abandoning your existing stack.","md","https://images.unsplash.com/photo-1677442135703-1787eea5ce01?w=1260&auto=format&fit=crop&q=80",{},{"title":14,"description":384},{"loc":15},"DMBYPz6zbR8L-jeTcXu_LwL6TQWPkVPlQRbj6y7V_Sk",[392,394],{"title":10,"path":11,"stem":12,"description":393,"children":-1},"A practical walkthrough of atomic symlink deployments, parallel test pipelines, and one-command rollbacks using GitHub Actions and DigitalOcean.",{"title":18,"path":19,"stem":20,"description":395,"children":-1},"A practical walkthrough of how Redis caching, Laravel Queues, and careful query optimisation transformed a sluggish ERP reporting module into a near-instant one.",1791651066261]