{"slug": "how-and-why-i-built-an-ai-assistant", "title": "How (and Why) I Built an AI Assistant", "summary": "A developer built a custom AI assistant to gain control over workflow, data privacy, and tool integration, using GPT-4o as the primary model with Claude as a fallback. The project was motivated by the limitations of off-the-shelf assistants and the desire to understand the technology firsthand, as the AI assistant market is projected to grow from $3.35 billion in 2025 to $21.11 billion by 2030.", "body_md": "# How (and Why) I Built an AI Assistant\n\nThis article is an honest account of the process on why I built a custom AI assistant instead of just paying for one, what the architecture looks like, the actual code, what broke, and what it does now that I genuinely rely on.\n\n## # Introduction\n\nIt started with a Tuesday that completely got away from me. I had three client briefs to summarize, a backlog of research tabs I kept promising myself I'd get to, a few emails that needed thoughtful replies, and a half-written technical document sitting open in one tab for the better part of four days. By the time I looked up from context-switching between all of it, it was past 7 PM and I'd shipped almost nothing meaningful.\n\nThat evening, instead of closing my laptop and calling it a loss, I started thinking about the problem differently. I wasn't short on time. I was short on leverage. Every task I did that day had a version of it I could have delegated to something smarter than a browser bookmark. So I started building.\n\nThis article is an honest account of that process: why I built a custom AI assistant instead of just paying for one, what the architecture looks like, the actual code, what broke, and what it does now that I genuinely rely on.\n\n## # The \"Why\" Comes Before the \"How\"\n\nMost people who decide to build an AI assistant start by Googling \"Python LangChain tutorial.\" That's backwards. The first question worth sitting with is: *why build it at all when Siri, ChatGPT, Copilot, and a dozen other tools already exist?*\n\nThe honest answer for me was control. Not in a paranoid, off-grid way, but in the practical sense that every off-the-shelf assistant is designed around someone else's assumptions about what you need. They're general-purpose by design, and general-purpose means compromises. I wanted something that knew my context, used my tone, connected to my specific tools, and stayed within a workflow I already trusted.\n\nThere's also the data question. When you use a third-party assistant, your prompts and context go through their infrastructure. For personal productivity that's arguably fine. For anything client-related or commercially sensitive, it gets murkier. Building your own means you decide where the data lives.\n\nAnd then there's the learning curve argument, which I think gets undersold: you understand a tool far better when you build it yourself. When something breaks, you know where to look. When you want it to do something new, you don't wait for a product update.\n\nThe timing also made the decision easier to justify. According to ** MarketsandMarkets**, the AI assistant market is projected to grow from \\$3.35 billion in 2025 to \\$21.11 billion by 2030 — a 44.5% compound annual growth rate. That kind of trajectory tells you this technology isn't a trend. It's infrastructure. Getting fluent in it now, by building rather than just consuming, puts you ahead of where most people will be in two years.\n\nThat said, building is not always the right call. If you need a quick answer engine or a writing aid that costs \\$20/month, buy it. But if you want something that integrates with your actual workflow, learns from your preferences, and handles tasks in a way that's specific to how you work, that's worth building.\n\n## # Choosing the Stack\n\nOnce I committed to building, the next decision was what to build it with. Here's what I actually considered, not a generic comparison chart.\n\n- The LLM choice came down to two serious options:\nand[OpenAI's GPT-4o](https://openai.com/index/hello-gpt-4o/). I tested both with the same prompts across research, writing, and reasoning tasks. GPT-4o is fast and broadly capable with a mature API. Claude handles long documents and nuanced instruction-following particularly well. I ended up going with GPT-4o as the primary model because of its tool-calling reliability and the maturity of its ecosystem, with Claude available as a fallback for certain document-heavy tasks.[Anthropic's Claude](https://claude.ai/) - For orchestration, I chose\n. There's a fair amount of debate in developer circles about whether LangChain adds too much abstraction, and that criticism isn't without merit. But for a project like this — one that needs memory, tool use, and a reasoning loop — LangChain's abstractions save real time. The alternative is writing that plumbing yourself, which you can do, but it's not where your attention is best spent when you're trying to ship something functional.[LangChain](https://www.langchain.com/) - Memory was a requirement from day one. A stateless chatbot that forgets everything between sessions is useful for one-off questions. It's not useful for a genuine assistant. LangChain's\n`ConversationBufferMemory`\n\nworked fine for in-session context. For persistence across sessions, I used a simple SQLite-backed approach, which I'll show in the code section. - For tools, I gave the assistant the ability to search the web (via DuckDuckGo's API — no key required), read and summarize files I pass it, and call custom Python functions I've written for specific recurring tasks. This is where the real value lives: turning it from a chatbot into something that can actually do things.\n\nA clean horizontal architecture flow diagram of the stack\n\n## # Setting Up the Environment\n\nBefore any code runs, you need three things in order: Python 3.10 or higher, a virtual environment, and your API keys stored safely.\n\n**Step 1: Creating and Activating a Virtual Environment**\n\n```\n# Create a virtual environment named 'assistant_env'\npython -m venv assistant_env\n\n# Activate it on macOS/Linux\nsource assistant_env/bin/activate\n\n# Activate it on Windows\nassistant_env\\Scripts\\activate\n```\n\nA virtual environment keeps your project's dependencies isolated from everything else on your machine. This matters more than it sounds — dependency conflicts between projects are a common, silent source of bugs.\n\n**Step 2: Installing the Required Packages**\n\n```\npip install langchain==0.3.0 \\\n            langchain-openai \\\n            langchain-community \\\n            langgraph \\\n            duckduckgo-search \\\n            python-dotenv \\\n            pydantic \\\n            requests\n```\n\nHere's what each package is doing:\n\n`langchain`\n\nis the core framework that connects your LLM, memory, and tools.`langchain-openai`\n\nis the specific connector for OpenAI's models.`langchain-community`\n\ngives you access to community-built tools and integrations, including DuckDuckGo search.`langgraph`\n\nhandles more complex, stateful agent workflows.`duckduckgo-search`\n\nlets the assistant search the web without needing an API key.`python-dotenv`\n\nloads your API keys from a`.env`\n\nfile instead of hardcoding them.`pydantic`\n\nhandles data validation for structured inputs and outputs.\n\n**Step 3: Storing Your API Keys Securely**\n\nNever hardcode an API key directly into your script. Create a `.env`\n\nfile in your project root:\n\n```\n# .env file -- never commit this to version control\nOPENAI_API_KEY=your_openai_key_here\n```\n\nThen add `.env`\n\nto your `.gitignore`\n\nfile immediately:\n\n```\n# .gitignore\n.env\nassistant_env/\n__pycache__/\n```\n\n## # Building the Core Assistant\n\nThis is where it comes together. I'll walk through each component in the order it needs to be built.\n\n**Connecting to the LLM**\n\n``` python\n# assistant.py\n\nimport os\nfrom dotenv import load_dotenv\nfrom langchain_openai import ChatOpenAI\n\n# Load environment variables from the .env file\nload_dotenv()\n\n# Initialize the language model\n# temperature controls randomness: 0 = focused and deterministic, 1 = more creative\n# For an assistant that needs to be accurate and consistent, keep this low (0.1 to 0.3)\nllm = ChatOpenAI(\n    model=\"gpt-4o\",\n    temperature=0.2,\n    api_key=os.getenv(\"OPENAI_API_KEY\")\n)\n```\n\n**What this does:**`ChatOpenAI`\n\ncreates a connection to GPT-4o through the API. The`temperature`\n\nparameter is worth understanding: at 0, the model always picks the most probable next token, which produces very consistent but sometimes rigid output. At 1, it's much more varied and creative. For a task-focused assistant, staying between 0.1 and 0.3 gives you reliability without losing all the natural language quality.**Designing the System Prompt** The system prompt is the most underrated part of the whole build. It defines your assistant's personality, its constraints, and how it handles ambiguous situations. Spend more time here than you think you need to.\n\n```\n# The system prompt acts as your assistant's standing instructions.\n# It's sent at the start of every conversation to anchor its behavior.\n\nSYSTEM_PROMPT = \"\"\"\nYou are a focused, reliable personal assistant.\n\nYour job is to help the user research topics, summarize documents, \ndraft written content, and handle structured tasks. You always:\n\n- Give direct answers before elaborating\n- Say when you're unsure rather than guessing\n- Ask for clarification if a task is genuinely ambiguous\n- Keep responses concise unless detail is explicitly requested\n\nYou have access to web search and can read files the user provides.\nWhen using these tools, always cite where you got your information.\n\nDo not make up facts, invent citations, or fill gaps with plausible-sounding fiction.\n\"\"\"\n```\n\n**What this does:** This prompt is sent ahead of every conversation. Think of it as the job description you'd give a human assistant on their first day. The more specific it is, the less you'll have to correct the model mid-conversation. Vague instructions produce vague behavior, every time.**Adding Memory** Without memory, your assistant forgets everything the moment you start a new message. This is how you fix that.\n\n``` python\nfrom langchain.memory import ConversationBufferMemory\nfrom langchain_community.chat_message_histories import SQLChatMessageHistory\n\n# SQLChatMessageHistory stores conversation history in a local SQLite database.\n# The session_id lets you maintain separate memory threads (e.g. one per project).\nmessage_history = SQLChatMessageHistory(\n    session_id=\"main_session\",\n    connection_string=\"sqlite:///assistant_memory.db\"\n)\n\n# ConversationBufferMemory wraps the message history and feeds it to the LLM\n# on each turn so the model knows what was said before.\nmemory = ConversationBufferMemory(\n    memory_key=\"chat_history\",\n    chat_memory=message_history,\n    return_messages=True\n)\n```\n\n**What this does:**`SQLChatMessageHistory`\n\nsaves every exchange to a local SQLite file called`assistant_memory.db`\n\n. This means your assistant remembers context between sessions. The`session_id`\n\nis just a label — you can create multiple sessions for different projects or topics, and they stay completely separate from each other.**One caveat:** buffer memory stores the full history and will eventually hit the model's context limit on long conversations. For production use,`ConversationSummaryMemory`\n\nis a better choice — it compresses older history into a summary so you stay within token limits.**Giving It Tools** This is what separates a chatbot from an assistant. Tools let the model take real actions.\n\n``` python\nfrom langchain.agents import AgentExecutor, create_openai_tools_agent\nfrom langchain_community.tools import DuckDuckGoSearchRun\nfrom langchain.tools import tool\nfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n\n# Tool 1: Web search via DuckDuckGo -- no API key required\nsearch_tool = DuckDuckGoSearchRun()\n\n# Tool 2: A custom file reader you define yourself\n# The @tool decorator registers this function as something the agent can call\n@tool\ndef read_file(file_path: str) -> str:\n    \"\"\"\n    Reads a text file from the given path and returns its contents.\n    Use this when the user asks you to read, analyze, or summarize a file.\n    \"\"\"\n    try:\n        with open(file_path, \"r\", encoding=\"utf-8\") as f:\n            return f.read()\n    except FileNotFoundError:\n        return f\"File not found: {file_path}\"\n    except Exception as e:\n        return f\"Error reading file: {str(e)}\"\n\n# Register the tools the agent can use\ntools = [search_tool, read_file]\n\n# Build the prompt template\n# MessagesPlaceholder slots in the memory (chat history) and the agent's scratchpad\nprompt = ChatPromptTemplate.from_messages([\n    (\"system\", SYSTEM_PROMPT),\n    MessagesPlaceholder(variable_name=\"chat_history\"),\n    (\"human\", \"{input}\"),\n    MessagesPlaceholder(variable_name=\"agent_scratchpad\")\n])\n\n# Create the agent -- this combines the LLM, the tools, and the prompt\nagent = create_openai_tools_agent(llm, tools, prompt)\n\n# AgentExecutor is the runtime loop: it calls the agent, runs any tools it selects,\n# feeds the results back, and repeats until it has a final answer\nagent_executor = AgentExecutor(\n    agent=agent,\n    tools=tools,\n    memory=memory,\n    verbose=True,      # Set to False in production; True lets you see the reasoning steps\n    max_iterations=5   # Prevents the agent from running in circles on hard problems\n)\n```\n\n**What this does:** The`AgentExecutor`\n\nis the engine. When you send it a message, it doesn't just pass it to the LLM and return whatever comes back. It runs a loop: the model decides whether it needs to use a tool, calls the tool if so, gets the result, thinks about what to do next, and only returns a final answer when it's satisfied. This is the ReAct (Reasoning + Acting) pattern in practice.\n\nA circular loop diagram showing four labeled stages connected by arrows\n\nPutting it all together, the main run loop:\n\n``` php\ndef chat(user_input: str) -> str:\n    \"\"\"\n    Send a message to the assistant and get a response.\n    Memory is handled automatically by the agent_executor.\n    \"\"\"\n    try:\n        response = agent_executor.invoke({\"input\": user_input})\n        return response[\"output\"]\n    except Exception as e:\n        # Graceful error handling -- tells you what broke without crashing the session\n        return f\"Something went wrong: {str(e)}. Please try again or rephrase your request.\"\n\n# Simple command-line interface to run the assistant\nif __name__ == \"__main__\":\n    print(\"Assistant ready. Type 'quit' to exit.\\n\")\n    while True:\n        user_input = input(\"You: \").strip()\n        if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n            print(\"Goodbye.\")\n            break\n        if not user_input:\n            continue\n        response = chat(user_input)\n        print(f\"\\nAssistant: {response}\\n\")\n```\n\n**What this does:** The `chat()`\n\nfunction is the single entry point for the whole system. You pass it a string, it handles everything — memory retrieval, tool calls, LLM reasoning, error handling — and returns a string. The `if __name__ == \"__main__\"`\n\nblock turns the whole script into a working command-line assistant you can run immediately with `python assistant.py`\n\n.\n\n## # Testing and Fixing What Breaks\n\nThe first time I ran this, the assistant gave a confident answer that was factually wrong, ignored a tool it should have used, and formatted a response in a way I hated. None of that is unusual. It's not a sign the build is broken — it's the start of the real work.\n\nThe most important thing to test early is whether the agent actually uses its tools when it should. A common failure mode is the model trying to answer from memory when it should be searching, because the system prompt didn't make the expectation explicit enough. I fixed this by adding to the system prompt:\n\n```\nWhen answering questions about recent events, current data, or anything \ntime-sensitive, always use the web search tool. Do not rely on your \ntraining knowledge for facts that may have changed.\n```\n\n**On error handling:** this matters more than most tutorials let on. According to research from ** Mordor Intelligence**, nearly half of AI-generated code fails its first security review. The same principle applies to AI-generated responses — output should be treated as a draft, not a final answer, until you've established trust in a particular type of task. The\n\n`try/except`\n\nblock in the `chat()`\n\nfunction above is a start, but you'll want to expand it as you discover the specific ways your assistant fails.For more structured testing, write test cases like this:\n\n```\n# test_assistant.py\n# Run these to verify the assistant behaves as expected before deploying\n\ntest_cases = [\n    {\n        \"input\": \"What is the current price of Bitcoin?\",\n        \"expected_behavior\": \"Should use web search, not answer from memory\"\n    },\n    {\n        \"input\": \"Summarize the file at /tmp/test_document.txt\",\n        \"expected_behavior\": \"Should call the read_file tool\"\n    },\n    {\n        \"input\": \"What did I ask you five messages ago?\",\n        \"expected_behavior\": \"Should reference conversation memory correctly\"\n    }\n]\n\nfor case in test_cases:\n    print(f\"Testing: {case['input']}\")\n    print(f\"Expected: {case['expected_behavior']}\")\n    result = chat(case[\"input\"])\n    print(f\"Got: {result[:200]}...\")  # Print first 200 characters of response\n    print(\"---\")\n```\n\nRun these after any change to the system prompt or tool configuration. Small prompt changes often have surprising downstream effects.\n\n## # The Full Code\n\nEverything above has been explained in pieces. Here it is as one complete, copy-paste-ready file. Save it as `assistant.py`\n\n, make sure your `.env`\n\nfile is in the same directory, and run it with `python assistant.py`\n\n.\n\n```\n# assistant.py\n# Full AI Assistant -- built with LangChain, GPT-4o, DuckDuckGo Search, and SQLite memory\n# Requirements: Python 3.10+  |  Run: pip install langchain==0.3.0 langchain-openai\n#               langchain-community langgraph duckduckgo-search python-dotenv pydantic requests\n\nimport os\nfrom dotenv import load_dotenv\nfrom langchain_openai import ChatOpenAI\nfrom langchain.memory import ConversationBufferMemory\nfrom langchain_community.chat_message_histories import SQLChatMessageHistory\nfrom langchain.agents import AgentExecutor, create_openai_tools_agent\nfrom langchain_community.tools import DuckDuckGoSearchRun\nfrom langchain.tools import tool\nfrom langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n\n# ──────────────────────────────────────────────\n# 1. LOAD ENVIRONMENT VARIABLES\n# ──────────────────────────────────────────────\n# Reads OPENAI_API_KEY from your .env file.\n# Never hardcode API keys directly in your source code.\nload_dotenv()\n\n# ──────────────────────────────────────────────\n# 2. INITIALIZE THE LANGUAGE MODEL\n# ──────────────────────────────────────────────\n# temperature=0.2 keeps responses focused and consistent.\n# Raise it toward 1.0 if you want more creative, varied output.\nllm = ChatOpenAI(\n    model=\"gpt-4o\",\n    temperature=0.2,\n    api_key=os.getenv(\"OPENAI_API_KEY\")\n)\n\n# ──────────────────────────────────────────────\n# 3. DEFINE THE SYSTEM PROMPT\n# ──────────────────────────────────────────────\n# This is the assistant's standing instruction set.\n# It shapes behavior on every single turn -- treat it carefully.\nSYSTEM_PROMPT = \"\"\"\nYou are a focused, reliable personal assistant.\n\nYour job is to help the user research topics, summarize documents,\ndraft written content, and handle structured tasks. You always:\n\n- Give direct answers before elaborating\n- Say when you're unsure rather than guessing\n- Ask for clarification if a task is genuinely ambiguous\n- Keep responses concise unless detail is explicitly requested\n\nYou have access to web search and can read files the user provides.\nWhen using these tools, always cite where you got your information.\n\nWhen answering questions about recent events, current data, or anything\ntime-sensitive, always use the web search tool. Do not rely on your\ntraining knowledge for facts that may have changed.\n\nDo not make up facts, invent citations, or fill gaps with plausible-sounding fiction.\n\"\"\"\n\n# ──────────────────────────────────────────────\n# 4. SET UP PERSISTENT MEMORY\n# ──────────────────────────────────────────────\n# SQLChatMessageHistory saves conversation history to a local SQLite database.\n# This means the assistant remembers context across sessions, not just within one.\n# Change session_id to keep separate memory threads (e.g. one per project).\nmessage_history = SQLChatMessageHistory(\n    session_id=\"main_session\",\n    connection_string=\"sqlite:///assistant_memory.db\"\n)\n\n# ConversationBufferMemory wraps the message history and injects it\n# into each prompt so the model always has the full conversation context.\n# Note: for very long conversations, swap this for ConversationSummaryMemory\n# to avoid hitting the model's token limit.\nmemory = ConversationBufferMemory(\n    memory_key=\"chat_history\",\n    chat_memory=message_history,\n    return_messages=True\n)\n\n# ──────────────────────────────────────────────\n# 5. DEFINE TOOLS\n# ──────────────────────────────────────────────\n\n# Tool 1: Web search via DuckDuckGo -- no API key required\nsearch_tool = DuckDuckGoSearchRun()\n\n# Tool 2: Custom file reader\n# The @tool decorator registers this Python function as a callable tool\n# that the agent can invoke when the user asks it to read a file.\n@tool\ndef read_file(file_path: str) -> str:\n    \"\"\"\n    Reads a text file from the given path and returns its contents.\n    Use this when the user asks you to read, analyze, or summarize a file.\n    \"\"\"\n    try:\n        with open(file_path, \"r\", encoding=\"utf-8\") as f:\n            return f.read()\n    except FileNotFoundError:\n        return f\"File not found: {file_path}\"\n    except Exception as e:\n        return f\"Error reading file: {str(e)}\"\n\n# Collect all tools into a list -- add more custom tools here as you build them\ntools = [search_tool, read_file]\n\n# ──────────────────────────────────────────────\n# 6. BUILD THE AGENT\n# ──────────────────────────────────────────────\n\n# The prompt template structures every message sent to the model.\n# MessagesPlaceholder slots in the memory (chat_history) and the agent's\n# internal reasoning scratchpad on each turn.\nprompt = ChatPromptTemplate.from_messages([\n    (\"system\", SYSTEM_PROMPT),\n    MessagesPlaceholder(variable_name=\"chat_history\"),\n    (\"human\", \"{input}\"),\n    MessagesPlaceholder(variable_name=\"agent_scratchpad\")\n])\n\n# create_openai_tools_agent wires together the LLM, the tools, and the prompt\n# into a single agent that knows how to decide when and which tools to call.\nagent = create_openai_tools_agent(llm, tools, prompt)\n\n# AgentExecutor is the runtime loop.\n# It calls the agent, runs any tools it selects, feeds those results back,\n# and repeats until it reaches a final answer (or hits max_iterations).\nagent_executor = AgentExecutor(\n    agent=agent,\n    tools=tools,\n    memory=memory,\n    verbose=True,       # Set to False in production; True shows the reasoning steps\n    max_iterations=5    # Prevents the agent from looping indefinitely on hard problems\n)\n\n# ──────────────────────────────────────────────\n# 7. THE CHAT FUNCTION\n# ──────────────────────────────────────────────\n\ndef chat(user_input: str) -> str:\n    \"\"\"\n    Send a message to the assistant and get a response.\n    Memory, tool use, and error handling are all managed here.\n    \"\"\"\n    try:\n        response = agent_executor.invoke({\"input\": user_input})\n        return response[\"output\"]\n    except Exception as e:\n        # Returns a readable error message instead of crashing the session.\n        # In production, log the full exception here for debugging.\n        return f\"Something went wrong: {str(e)}. Please try again or rephrase your request.\"\n\n# ──────────────────────────────────────────────\n# 8. COMMAND-LINE INTERFACE\n# ──────────────────────────────────────────────\n\n# Run this file directly to start a command-line session: python assistant.py\n# To wrap it in a browser UI instead, see Gradio or Streamlit in Part 7.\nif __name__ == \"__main__\":\n    print(\"Assistant ready. Type 'quit' to exit.\\n\")\n    while True:\n        user_input = input(\"You: \").strip()\n        if user_input.lower() in [\"quit\", \"exit\", \"q\"]:\n            print(\"Goodbye.\")\n            break\n        if not user_input:\n            continue\n        response = chat(user_input)\n        print(f\"\\nAssistant: {response}\\n\")\n```\n\n#### // Results\n\nAfter about two weeks of regular use, here's what the assistant actually handles for me now:\n\n**Research synthesis:** I give it a topic and three to five URLs, and it pulls the key points into a structured summary. What used to take 45 minutes takes about 4 minutes, with the remaining time spent on my own verification and judgment.**Draft generation:** First drafts of emails, summaries, and structured documents. The output isn't final and I don't expect it to be, but having something to edit is faster than starting from a blank page every time.**File digestion:** I drop meeting notes, PDFs, and log files into a folder and ask it to pull out specific information. It handles this reliably as long as the files are text-based and under about 50,000 words.\n\nThe time savings are real. ** Data from DX's analysis of 135,000+ developers** found an average of 3.6 hours saved per week when using AI tools, and daily users showed even larger gains. My experience is in that range for task-heavy days, though it varies a lot by the type of work.\n\n## # Wrapping Up\n\nThat Tuesday I described at the start — the one where I worked all day and shipped almost nothing — still happens. But it happens less, and when it does, it's rarely because I was stuck in the wrong kind of work. The assistant handles the parts of the job that don't require me specifically, which frees me to spend more time on the parts that do.\n\nWhat I didn't expect is that building it changed how I think about the work itself. When you're responsible for a tool, you start noticing friction differently. You start asking \"could this be delegated?\" more consistently, which is a useful mental habit regardless of whether you have AI involved.\n\nThe barrier to building something like this is lower than it appears. The full working assistant above is under 150 lines of Python, uses freely available frameworks, and runs on any machine with Python installed. The hardest part is deciding what you actually want it to do — and that question is worth answering carefully, because a focused assistant beats a general one every time.\n\nStart small. Give it one job. Add complexity only when you run out of value at the simpler level. That approach works for tools, and it works for building habits around them.\n\nis a software engineer and technical writer passionate about leveraging cutting-edge technologies to craft compelling narratives, with a keen eye for detail and a knack for simplifying complex concepts. You can also find Shittu on\n\n[Shittu Olumide](https://www.linkedin.com/in/olumide-shittu/)", "url": "https://wpnews.pro/news/how-and-why-i-built-an-ai-assistant", "canonical_source": "https://www.kdnuggets.com/how-and-why-i-built-an-ai-assistant", "published_at": "2026-06-17 14:00:39+00:00", "updated_at": "2026-06-17 14:54:30.919453+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "large-language-models", "ai-infrastructure"], "entities": ["OpenAI", "Anthropic", "GPT-4o", "Claude", "MarketsandMarkets", "LangChain"], "alternates": {"html": "https://wpnews.pro/news/how-and-why-i-built-an-ai-assistant", "markdown": "https://wpnews.pro/news/how-and-why-i-built-an-ai-assistant.md", "text": "https://wpnews.pro/news/how-and-why-i-built-an-ai-assistant.txt", "jsonld": "https://wpnews.pro/news/how-and-why-i-built-an-ai-assistant.jsonld"}}