diff --git a/notebooks/.gitignore b/notebooks/.gitignore new file mode 100644 index 0000000..5f8fb13 --- /dev/null +++ b/notebooks/.gitignore @@ -0,0 +1 @@ +langchain_db/ \ No newline at end of file diff --git a/notebooks/remote-agent-testing.ipynb b/notebooks/remote-agent-testing.ipynb new file mode 100644 index 0000000..a328609 --- /dev/null +++ b/notebooks/remote-agent-testing.ipynb @@ -0,0 +1,391 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "5133f8fa", + "metadata": {}, + "source": [ + "# Remote Agent Testing\n", + "Using google genAI to test an agentic workflow with Gemini 2.5" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "62ec2147", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Imports\n", + "import bs4\n", + "from dotenv import load_dotenv\n", + "from langchain.agents import create_agent\n", + "from langchain.agents.middleware import dynamic_prompt, ModelRequest\n", + "from langchain.chat_models import init_chat_model\n", + "from langchain.tools import tool\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import WebBaseLoader\n", + "from langchain_google_genai import GoogleGenerativeAIEmbeddings\n", + "from langchain_text_splitters import RecursiveCharacterTextSplitter\n", + "\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "id": "6dc525a1", + "metadata": {}, + "source": [ + "Using Gemini 2.5 via Langchain's Google Generative AI integration to test an agentic workflow." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "a401cf8a", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "model = init_chat_model(\"google_genai:gemini-2.5-flash-lite\")" + ] + }, + { + "cell_type": "markdown", + "id": "aaa68979", + "metadata": {}, + "source": [ + "Setting up embeddings model" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "45805907", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "embeddings = GoogleGenerativeAIEmbeddings(model=\"models/gemini-embedding-001\")" + ] + }, + { + "cell_type": "markdown", + "id": "b3f90586", + "metadata": {}, + "source": [ + "Vector store setup for data storage and retrieval" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "500f90f4", + "metadata": {}, + "outputs": [], + "source": [ + "vector_store = Chroma(\n", + " collection_name=\"example_collection\",\n", + " embedding_function=embeddings,\n", + " persist_directory=\"./langchain_db\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d4ff7ec0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "6,900 pages later… *“This story is just for that one reader.”* \n", + "*Omniscient Reader’s Viewpoint* is probably one of the most ambitious epics I’ve ever read in this genre. Regression-themed novels are already a flooded trope, but this one blows the rest out of the water purely from how many layers it stacks on top of itself and still manages to come out narratively clean. When I first got into this series (via the webtoon, like most people), the wait between weekly releases drove me up the wall,\n", + "Total characters: 8578\n" + ] + } + ], + "source": [ + "import requests\n", + "from langchain_core.documents import Document\n", + "\n", + "response = requests.get(\"https://viswamedha.com/api/post/a-story-for-one-reader/\")\n", + "data = response.json()\n", + "content = data['content']\n", + "\n", + "docs = [Document(page_content=content, metadata={\"source\": response.url})]\n", + "\n", + "assert len(docs) == 1\n", + "print(docs[0].page_content[:500])\n", + "print(f\"Total characters: {len(docs[0].page_content)}\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "82bcfabc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Split blog post into 13 sub-documents.\n" + ] + } + ], + "source": [ + "\n", + "text_splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=1000, # chunk size (characters)\n", + " chunk_overlap=200, # chunk overlap (characters)\n", + " add_start_index=True, # track index in original document\n", + ")\n", + "all_splits = text_splitter.split_documents(docs)\n", + "\n", + "print(f\"Split blog post into {len(all_splits)} sub-documents.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "id": "2ee1a9ca", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "['19e6412c-7407-4c73-ba24-f47fe1ffe7e2', 'df94988a-8837-464c-8809-ed86343ffd8b', '2456d12c-a077-41d4-85c6-f79b9056109b']\n" + ] + } + ], + "source": [ + "document_ids = vector_store.add_documents(documents=all_splits)\n", + "\n", + "print(document_ids[:3])" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "id": "a9096893", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "@tool(response_format=\"content_and_artifact\")\n", + "def retrieve_context(query: str):\n", + " \"\"\"Retrieve information to help answer a query.\"\"\"\n", + " retrieved_docs = vector_store.similarity_search(query, k=2)\n", + " serialized = \"\\n\\n\".join(\n", + " (f\"Source: {doc.metadata}\\nContent: {doc.page_content}\")\n", + " for doc in retrieved_docs\n", + " )\n", + " return serialized, retrieved_docs" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "dff2345d", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "tools = [retrieve_context]\n", + "prompt = (\n", + " \"You have access to a tool that retrieves context from a blog post. \"\n", + " \"Use the tool to help answer user queries.\"\n", + ")\n", + "agent = create_agent(model, tools, system_prompt=prompt)" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "aaa2fad9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What is the significance of the second loop?\n", + "\n", + "Use the retrieved context to provide a detailed answer.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " retrieve_context (b746e923-2761-4be8-ae23-c0b3698972ac)\n", + " Call ID: b746e923-2761-4be8-ae23-c0b3698972ac\n", + " Args:\n", + " query: Significance of the second loop\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "Tool Calls:\n", + " retrieve_context (b746e923-2761-4be8-ae23-c0b3698972ac)\n", + " Call ID: b746e923-2761-4be8-ae23-c0b3698972ac\n", + " Args:\n", + " query: Significance of the second loop\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: retrieve_context\n", + "\n", + "Source: {'source': 'https://viswamedha.com/api/post/a-story-for-one-reader/', 'start_index': 3377}\n", + "Content: And this is where the paradox really hits. The Great Plotter, while observing regressions and chasing a better ending, ends up **creating the very timeline** he’s been watching. In trying to fix his own story, he triggers a new one. He unknowingly causes the very events that lead to KDJ’s worldline existing in the first place. It's absolutely wild. He becomes the most influential figure in this timeline, yet completely powerless to interact with it directly (due to the constraints of Probability). All he can do is watch as KDJ lives through the story he thought he already knew.\n", + "\n", + "---\n", + "\n", + "## What is the second paradox, and where does the loop begin?\n", + "\n", + "Source: {'start_index': 32858, 'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}\n", + "Content: }\n", + "]\n", + "Then after these clarification, the agent moved into the code writing mode with a different system message.\n", + "System message:\n", + "=================================\u001b[1m Tool Message \u001b[0m=================================\n", + "Name: retrieve_context\n", + "\n", + "Source: {'source': 'https://viswamedha.com/api/post/a-story-for-one-reader/', 'start_index': 3377}\n", + "Content: And this is where the paradox really hits. The Great Plotter, while observing regressions and chasing a better ending, ends up **creating the very timeline** he’s been watching. In trying to fix his own story, he triggers a new one. He unknowingly causes the very events that lead to KDJ’s worldline existing in the first place. It's absolutely wild. He becomes the most influential figure in this timeline, yet completely powerless to interact with it directly (due to the constraints of Probability). All he can do is watch as KDJ lives through the story he thought he already knew.\n", + "\n", + "---\n", + "\n", + "## What is the second paradox, and where does the loop begin?\n", + "\n", + "Source: {'start_index': 32858, 'source': 'https://lilianweng.github.io/posts/2023-06-23-agent/'}\n", + "Content: }\n", + "]\n", + "Then after these clarification, the agent moved into the code writing mode with a different system message.\n", + "System message:\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n" + ] + } + ], + "source": [ + "query = (\n", + " \"What is the significance of the second loop?\\n\\n\"\n", + " \"Use the retrieved context to provide a detailed answer.\"\n", + ")\n", + "\n", + "for event in agent.stream(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": query}]},\n", + " stream_mode=\"values\",\n", + "):\n", + " event[\"messages\"][-1].pretty_print()" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "bda6d7d0", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "\n", + "@dynamic_prompt\n", + "def prompt_with_context(request: ModelRequest) -> str:\n", + " \"\"\"Inject context into state messages.\"\"\"\n", + " last_query = request.state[\"messages\"][-1].text\n", + " retrieved_docs = vector_store.similarity_search(last_query)\n", + "\n", + " docs_content = \"\\n\\n\".join(doc.page_content for doc in retrieved_docs)\n", + "\n", + " system_message = (\n", + " \"You are a helpful assistant. Use the following context in your response:\"\n", + " f\"\\n\\n{docs_content}\"\n", + " )\n", + "\n", + " return system_message\n", + "\n", + "\n", + "agent = create_agent(model, tools=[], middleware=[prompt_with_context])" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "1540855c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "================================\u001b[1m Human Message \u001b[0m=================================\n", + "\n", + "What is the significance of the second loop?\n", + "\n", + "\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The second paradox highlights the self-fulfilling nature of the narrative and Kim Dokja's unique role within it.\n", + "\n", + "Here's the significance:\n", + "\n", + "* **Kim Dokja as the Catalyst:** The \"second loop\" isn't about a repeated cycle of events in the traditional sense. Instead, it's about Kim Dokja's existence and actions *creating* the very timeline he's trying to navigate. He's the \"Great Plotter\" who, in his attempts to alter or understand the story, inadvertently causes the events that lead to the existence of the worldline he's observing.\n", + "* **The Unwritten Becoming the Author:** The paradox lies in how someone who was never part of the original story (*TWSA*) becomes its central figure, then its overseer, and eventually something akin to a god. His obsession with the novel and his subsequent involvement in the scenarios *are* the genesis of that specific reality.\n", + "* **The Power of Observation and Intervention:** The \"Great Plotter\" is trapped in a unique position. He can observe the events he set in motion, even chase a \"better ending,\" but his direct interaction is limited by \"Probability.\" This means he's a profoundly influential figure who is simultaneously powerless to directly change the course of the story he created. He can only watch as Kim Dokja lives through the narrative.\n", + "* **The Genesis of Kim Dokja's Worldline:** The loop begins with Kim Dokja's transition from a reader in our world to the protagonist of the scenarios. His reading of the novel and the subsequent beginning of the scenarios in his reality are the foundational events. The \"Great Plotter's\" actions, in turn, ensure that this specific worldline, with Kim Dokja at its center, comes into being.\n", + "==================================\u001b[1m Ai Message \u001b[0m==================================\n", + "\n", + "The second paradox highlights the self-fulfilling nature of the narrative and Kim Dokja's unique role within it.\n", + "\n", + "Here's the significance:\n", + "\n", + "* **Kim Dokja as the Catalyst:** The \"second loop\" isn't about a repeated cycle of events in the traditional sense. Instead, it's about Kim Dokja's existence and actions *creating* the very timeline he's trying to navigate. He's the \"Great Plotter\" who, in his attempts to alter or understand the story, inadvertently causes the events that lead to the existence of the worldline he's observing.\n", + "* **The Unwritten Becoming the Author:** The paradox lies in how someone who was never part of the original story (*TWSA*) becomes its central figure, then its overseer, and eventually something akin to a god. His obsession with the novel and his subsequent involvement in the scenarios *are* the genesis of that specific reality.\n", + "* **The Power of Observation and Intervention:** The \"Great Plotter\" is trapped in a unique position. He can observe the events he set in motion, even chase a \"better ending,\" but his direct interaction is limited by \"Probability.\" This means he's a profoundly influential figure who is simultaneously powerless to directly change the course of the story he created. He can only watch as Kim Dokja lives through the narrative.\n", + "* **The Genesis of Kim Dokja's Worldline:** The loop begins with Kim Dokja's transition from a reader in our world to the protagonist of the scenarios. His reading of the novel and the subsequent beginning of the scenarios in his reality are the foundational events. The \"Great Plotter's\" actions, in turn, ensure that this specific worldline, with Kim Dokja at its center, comes into being.\n" + ] + } + ], + "source": [ + "query = \"What is the significance of the second loop?\\n\\n\"\n", + "for step in agent.stream(\n", + " {\"messages\": [{\"role\": \"user\", \"content\": query}]},\n", + " stream_mode=\"values\",\n", + "):\n", + " step[\"messages\"][-1].pretty_print()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}