mirror of https://github.com/hwchase17/langchain
Add LangSmith Run Chat Loader (#11458)
parent
484947c492
commit
eb572f41a6
File diff suppressed because one or more lines are too long
@ -0,0 +1,279 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a9ab2a39-7c2d-4119-9dc7-8035fdfba3cb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Fine-Tuning on LangSmith Chat Datasets\n",
|
||||
"\n",
|
||||
"This notebook demonstrates an easy way to load a LangSmith chat dataset fine-tune a model on that data.\n",
|
||||
"The process is simple and comprises 3 steps.\n",
|
||||
"\n",
|
||||
"1. Create the chat dataset.\n",
|
||||
"2. Use the LangSmithDatasetChatLoader to load examples.\n",
|
||||
"3. Fine-tune your model.\n",
|
||||
"\n",
|
||||
"Then you can use the fine-tuned model in your LangChain app.\n",
|
||||
"\n",
|
||||
"Before diving in, let's install our prerequisites.\n",
|
||||
"\n",
|
||||
"## Prerequisites\n",
|
||||
"\n",
|
||||
"Ensure you've installed langchain >= 0.0.311 and have configured your environment with your LangSmith API key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ef488003-514a-48b4-93f1-7de4417abf5d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install -U langchain openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "9fba5c30-9e72-48aa-9535-80f2b3d18ead",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import uuid\n",
|
||||
"uid = uuid.uuid4().hex[:6]\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"YOUR API KEY\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8533ab63-d437-492a-aaec-ccca31167bf2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Select dataset\n",
|
||||
"\n",
|
||||
"This notebook fine-tunes a model directly on a selecting which runs to fine-tune on. You will often curate these from traced runs. You can learn more about LangSmith datasets in the docs [docs](https://docs.smith.langchain.com/evaluation/datasets).\n",
|
||||
"\n",
|
||||
"For the sake of this tutorial, we will upload an existing dataset here that you can use."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "462515e0-872a-446e-abbd-6166d73d7414",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langsmith.client import Client\n",
|
||||
"\n",
|
||||
"client = Client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "d384e4ac-5e8f-42a2-8bb5-7d3c9a8a540d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import requests\n",
|
||||
"url = \"https://raw.githubusercontent.com/langchain-ai/langchain/master/docs/docs_skeleton/docs/integrations/chat_loaders/example_data/langsmith_chat_dataset.json\"\n",
|
||||
"response = requests.get(url)\n",
|
||||
"response.raise_for_status()\n",
|
||||
"data = response.json()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "b0d8ae47-2d3f-4b01-b15f-da58bd750fb4",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"dataset_name = f\"Extraction Fine-tuning Dataset {uid}\"\n",
|
||||
"ds = client.create_dataset(dataset_name=dataset_name, data_type=\"chat\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "87f085b7-71e1-4ff4-a622-e4e1248aa94a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"_ = client.create_examples(\n",
|
||||
" inputs = [e['inputs'] for e in data],\n",
|
||||
" outputs = [e['outputs'] for e in data],\n",
|
||||
" dataset_id=ds.id,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f365a359-52f7-47ff-8c36-aadc1070b409",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Prepare Data\n",
|
||||
"Now we can create an instance of LangSmithRunChatLoader and load the chat sessions using its lazy_load() method."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "817bc077-c18a-473b-94a4-a7d810d583a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chat_loaders.langsmith import LangSmithDatasetChatLoader\n",
|
||||
"\n",
|
||||
"loader = LangSmithDatasetChatLoader(dataset_name=dataset_name)\n",
|
||||
"\n",
|
||||
"chat_sessions = loader.lazy_load()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f21a3bbd-1ed4-481b-9640-206b8bf0d751",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### With the chat sessions loaded, convert them into a format suitable for fine-tuning."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "9e5ac127-b094-4584-9159-5a6d3d7315c7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.adapters.openai import convert_messages_for_finetuning\n",
|
||||
"\n",
|
||||
"training_data = convert_messages_for_finetuning(chat_sessions)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "188c4978-d85e-4984-a008-a50f6cd6bb84",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Fine-tune the Model\n",
|
||||
"Now, initiate the fine-tuning process using the OpenAI library."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "11d19e28-be49-4801-8065-1a58d13cd192",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Status=[running]... 302.42s. 143.85s\r"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import openai\n",
|
||||
"import time\n",
|
||||
"import json\n",
|
||||
"from io import BytesIO\n",
|
||||
"\n",
|
||||
"my_file = BytesIO()\n",
|
||||
"for dialog in training_data:\n",
|
||||
" my_file.write((json.dumps({\"messages\": dialog}) + \"\\n\").encode('utf-8'))\n",
|
||||
"\n",
|
||||
"my_file.seek(0)\n",
|
||||
"training_file = openai.File.create(\n",
|
||||
" file=my_file,\n",
|
||||
" purpose='fine-tune'\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"job = openai.FineTuningJob.create(\n",
|
||||
" training_file=training_file.id,\n",
|
||||
" model=\"gpt-3.5-turbo\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Wait for the fine-tuning to complete (this may take some time)\n",
|
||||
"status = openai.FineTuningJob.retrieve(job.id).status\n",
|
||||
"start_time = time.time()\n",
|
||||
"while status != \"succeeded\":\n",
|
||||
" print(f\"Status=[{status}]... {time.time() - start_time:.2f}s\", end=\"\\r\", flush=True)\n",
|
||||
" time.sleep(5)\n",
|
||||
" status = openai.FineTuningJob.retrieve(job.id).status\n",
|
||||
"\n",
|
||||
"# Now your model is fine-tuned!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "54c4cead-500d-41dd-8333-2defde634396",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Use in LangChain\n",
|
||||
"\n",
|
||||
"After fine-tuning, use the resulting model ID with the ChatOpenAI model class in your LangChain app."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f472ca4-fa9b-485d-bd37-8ce3c59c44db",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Get the fine-tuned model ID\n",
|
||||
"job = openai.FineTuningJob.retrieve(job.id)\n",
|
||||
"model_id = job.fine_tuned_model\n",
|
||||
"\n",
|
||||
"# Use the fine-tuned model in LangChain\n",
|
||||
"model = ChatOpenAI(\n",
|
||||
" model=model_id,\n",
|
||||
" temperature=1,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "7d3b5845-6385-42d1-9f7d-5ea798dc2cd9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model.invoke(\"There were three ravens sat on a tree.\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b8c2c79-ce27-4f37-b1b2-5977db8c4e84",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now you have successfully fine-tuned a model using data from LangSmith LLM runs!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"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.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -0,0 +1,429 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a9ab2a39-7c2d-4119-9dc7-8035fdfba3cb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Fine-Tuning on LangSmith LLM Runs\n",
|
||||
"\n",
|
||||
"This notebook demonstrates how to directly load data from LangSmith's LLM runs and fine-tune a model on that data.\n",
|
||||
"The process is simple and comprises 3 steps.\n",
|
||||
"\n",
|
||||
"1. Select the LLM runs to train on.\n",
|
||||
"2. Use the LangSmithRunChatLoader to load runs as chat sessions.\n",
|
||||
"3. Fine-tune your model.\n",
|
||||
"\n",
|
||||
"Then you can use the fine-tuned model in your LangChain app.\n",
|
||||
"\n",
|
||||
"Before diving in, let's install our prerequisites.\n",
|
||||
"\n",
|
||||
"## Prerequisites\n",
|
||||
"\n",
|
||||
"Ensure you've installed langchain >= 0.0.311 and have configured your environment with your LangSmith API key."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "ef488003-514a-48b4-93f1-7de4417abf5d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install -U langchain openai"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "473adce5-c863-49e6-85c3-049e0ec2222e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import uuid\n",
|
||||
"uid = uuid.uuid4().hex[:6]\n",
|
||||
"project_name = f\"Run Fine-tuning Walkthrough {uid}\"\n",
|
||||
"os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
|
||||
"os.environ[\"LANGCHAIN_API_KEY\"] = \"YOUR API KEY\"\n",
|
||||
"os.environ[\"LANGCHAIN_PROJECT\"] = project_name"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8533ab63-d437-492a-aaec-ccca31167bf2",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 1. Select Runs\n",
|
||||
"The first step is selecting which runs to fine-tune on. A common case would be to select LLM runs within\n",
|
||||
"traces that have received positive user feedback. You can find examples of this in the[LangSmith Cookbook](https://github.com/langchain-ai/langsmith-cookbook/blob/main/exploratory-data-analysis/exporting-llm-runs-and-feedback/llm_run_etl.ipynb) and in the [docs](https://docs.smith.langchain.com/tracing/use-cases/export-runs/local).\n",
|
||||
"\n",
|
||||
"For the sake of this tutorial, we will generate some runs for you to use here. Let's try fine-tuning a\n",
|
||||
"simple function-calling chain."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "9a36d27f-2f3b-4148-b94a-9436fe8b00e0",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.pydantic_v1 import BaseModel, Field\n",
|
||||
"from enum import Enum\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Operation(Enum):\n",
|
||||
" add = \"+\"\n",
|
||||
" subtract = \"-\"\n",
|
||||
" multiply = \"*\"\n",
|
||||
" divide = \"/\"\n",
|
||||
"\n",
|
||||
"class Calculator(BaseModel):\n",
|
||||
" \"\"\"A calculator function\"\"\"\n",
|
||||
" num1: float\n",
|
||||
" num2: float\n",
|
||||
" operation: Operation = Field(..., description=\"+,-,*,/\")\n",
|
||||
"\n",
|
||||
" def calculate(self):\n",
|
||||
" if self.operation == Operation.add:\n",
|
||||
" return self.num1 + self.num2\n",
|
||||
" elif self.operation == Operation.subtract:\n",
|
||||
" return self.num1 - self.num2\n",
|
||||
" elif self.operation == Operation.multiply:\n",
|
||||
" return self.num1 * self.num2\n",
|
||||
" elif self.operation == Operation.divide:\n",
|
||||
" if self.num2 != 0:\n",
|
||||
" return self.num1 / self.num2\n",
|
||||
" else:\n",
|
||||
" return \"Cannot divide by zero\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "89bcc676-27e8-40dc-a4d6-92cf28e0db58",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'description': 'A calculator function',\n",
|
||||
" 'name': 'Calculator',\n",
|
||||
" 'parameters': {'description': 'A calculator function',\n",
|
||||
" 'properties': {'num1': {'title': 'Num1', 'type': 'number'},\n",
|
||||
" 'num2': {'title': 'Num2', 'type': 'number'},\n",
|
||||
" 'operation': {'allOf': [{'description': 'An '\n",
|
||||
" 'enumeration.',\n",
|
||||
" 'enum': ['+',\n",
|
||||
" '-',\n",
|
||||
" '*',\n",
|
||||
" '/'],\n",
|
||||
" 'title': 'Operation'}],\n",
|
||||
" 'description': '+,-,*,/'}},\n",
|
||||
" 'required': ['num1', 'num2', 'operation'],\n",
|
||||
" 'title': 'Calculator',\n",
|
||||
" 'type': 'object'}}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.utils.openai_functions import convert_pydantic_to_openai_function\n",
|
||||
"from langchain.pydantic_v1 import BaseModel\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"openai_function_def = convert_pydantic_to_openai_function(Calculator)\n",
|
||||
"pprint(openai_function_def)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "cd44ff01-22cf-431a-8bf4-29a758d1fcff",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.prompts import ChatPromptTemplate\n",
|
||||
"from langchain.chat_models import ChatOpenAI\n",
|
||||
"from langchain.output_parsers.openai_functions import PydanticOutputFunctionsParser\n",
|
||||
"\n",
|
||||
"prompt = ChatPromptTemplate.from_messages(\n",
|
||||
" [\n",
|
||||
" (\"system\", \"You are an accounting assistant.\"),\n",
|
||||
" (\"user\", \"{input}\"),\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"chain = (\n",
|
||||
" prompt\n",
|
||||
" | ChatOpenAI().bind(functions=[openai_function_def])\n",
|
||||
" | PydanticOutputFunctionsParser(pydantic_schema=Calculator)\n",
|
||||
" | (lambda x: x.calculate())\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "62da7d8f-5cfc-45a6-946e-2bcda2b0ba1f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Retrying langchain.chat_models.openai.ChatOpenAI.completion_with_retry.<locals>._completion_with_retry in 4.0 seconds as it raised ServiceUnavailableError: The server is overloaded or not ready yet..\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"math_questions = [\n",
|
||||
" \"What's 45/9?\",\n",
|
||||
" \"What's 81/9?\",\n",
|
||||
" \"What's 72/8?\",\n",
|
||||
" \"What's 56/7?\",\n",
|
||||
" \"What's 36/6?\",\n",
|
||||
" \"What's 64/8?\",\n",
|
||||
" \"What's 12*6?\",\n",
|
||||
" \"What's 8*8?\",\n",
|
||||
" \"What's 10*10?\",\n",
|
||||
" \"What's 11*11?\",\n",
|
||||
" \"What's 13*13?\",\n",
|
||||
" \"What's 45+30?\",\n",
|
||||
" \"What's 72+28?\",\n",
|
||||
" \"What's 56+44?\",\n",
|
||||
" \"What's 63+37?\",\n",
|
||||
" \"What's 70-35?\",\n",
|
||||
" \"What's 60-30?\",\n",
|
||||
" \"What's 50-25?\",\n",
|
||||
" \"What's 40-20?\",\n",
|
||||
" \"What's 30-15?\"\n",
|
||||
"]\n",
|
||||
"results = chain.batch([{\"input\": q} for q in math_questions], return_exceptions=True)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "cbb1bcae-b922-4d38-b4bd-4b65be400b88",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### Load runs that did not error\n",
|
||||
"\n",
|
||||
"Now we can select the successful runs to fine-tune on."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "d6037992-050d-4ada-a061-860c124f0bf1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langsmith.client import Client\n",
|
||||
"\n",
|
||||
"client = Client()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "0444919a-6f5a-4726-9916-4603b1420d0e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"successful_traces = {\n",
|
||||
" run.trace_id\n",
|
||||
" for run in client.list_runs(\n",
|
||||
" project_name=project_name,\n",
|
||||
" execution_order=1,\n",
|
||||
" error=False,\n",
|
||||
" )\n",
|
||||
"}\n",
|
||||
" \n",
|
||||
"llm_runs = [\n",
|
||||
" run for run in client.list_runs(\n",
|
||||
" project_name=project_name,\n",
|
||||
" run_type=\"llm\",\n",
|
||||
" ) \n",
|
||||
" if run.trace_id in successful_traces\n",
|
||||
"]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f365a359-52f7-47ff-8c36-aadc1070b409",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2. Prepare data\n",
|
||||
"Now we can create an instance of LangSmithRunChatLoader and load the chat sessions using its lazy_load() method."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "817bc077-c18a-473b-94a4-a7d810d583a8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.chat_loaders.langsmith import LangSmithRunChatLoader\n",
|
||||
"\n",
|
||||
"loader = LangSmithRunChatLoader(runs=llm_runs)\n",
|
||||
"\n",
|
||||
"chat_sessions = loader.lazy_load()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "f21a3bbd-1ed4-481b-9640-206b8bf0d751",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"#### With the chat sessions loaded, convert them into a format suitable for fine-tuning."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "9e5ac127-b094-4584-9159-5a6d3d7315c7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain.adapters.openai import convert_messages_for_finetuning\n",
|
||||
"\n",
|
||||
"training_data = convert_messages_for_finetuning(chat_sessions)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "188c4978-d85e-4984-a008-a50f6cd6bb84",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 3. Fine-tune the model\n",
|
||||
"Now, initiate the fine-tuning process using the OpenAI library."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "11d19e28-be49-4801-8065-1a58d13cd192",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Status=[running]... 346.26s. 31.70s\r"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import openai\n",
|
||||
"import time\n",
|
||||
"import json\n",
|
||||
"from io import BytesIO\n",
|
||||
"\n",
|
||||
"my_file = BytesIO()\n",
|
||||
"for dialog in training_data:\n",
|
||||
" my_file.write((json.dumps({\"messages\": dialog}) + \"\\n\").encode('utf-8'))\n",
|
||||
"\n",
|
||||
"my_file.seek(0)\n",
|
||||
"training_file = openai.File.create(\n",
|
||||
" file=my_file,\n",
|
||||
" purpose='fine-tune'\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"job = openai.FineTuningJob.create(\n",
|
||||
" training_file=training_file.id,\n",
|
||||
" model=\"gpt-3.5-turbo\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Wait for the fine-tuning to complete (this may take some time)\n",
|
||||
"status = openai.FineTuningJob.retrieve(job.id).status\n",
|
||||
"start_time = time.time()\n",
|
||||
"while status != \"succeeded\":\n",
|
||||
" print(f\"Status=[{status}]... {time.time() - start_time:.2f}s\", end=\"\\r\", flush=True)\n",
|
||||
" time.sleep(5)\n",
|
||||
" status = openai.FineTuningJob.retrieve(job.id).status\n",
|
||||
"\n",
|
||||
"# Now your model is fine-tuned!"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "54c4cead-500d-41dd-8333-2defde634396",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 4. Use in LangChain\n",
|
||||
"\n",
|
||||
"After fine-tuning, use the resulting model ID with the ChatOpenAI model class in your LangChain app."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "7f45b281-1dfa-43cb-bd28-99fa7e9f45d1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Get the fine-tuned model ID\n",
|
||||
"job = openai.FineTuningJob.retrieve(job.id)\n",
|
||||
"model_id = job.fine_tuned_model\n",
|
||||
"\n",
|
||||
"# Use the fine-tuned model in LangChain\n",
|
||||
"model = ChatOpenAI(\n",
|
||||
" model=model_id,\n",
|
||||
" temperature=1,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"id": "7d3b5845-6385-42d1-9f7d-5ea798dc2cd9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AIMessage(content='{\\n \"num1\": 56,\\n \"num2\": 7,\\n \"operation\": \"/\"\\n}')"
|
||||
]
|
||||
},
|
||||
"execution_count": 18,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(prompt | model).invoke({\"input\": \"What's 56/7?\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5b8c2c79-ce27-4f37-b1b2-5977db8c4e84",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Now you have successfully fine-tuned a model using data from LangSmith LLM runs!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"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.11.2"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Dict, Iterable, Iterator, List, Optional, Union
|
||||
|
||||
from langchain.chat_loaders.base import BaseChatLoader
|
||||
from langchain.load import load
|
||||
from langchain.schema.chat import ChatSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from langsmith.client import Client
|
||||
from langsmith.schemas import Run
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LangSmithRunChatLoader(BaseChatLoader):
|
||||
"""
|
||||
Load chat sessions from a list of LangSmith "llm" runs.
|
||||
|
||||
Attributes:
|
||||
runs (Iterable[Union[str, Run]]): The list of LLM run IDs or run objects.
|
||||
client (Client): Instance of LangSmith client for fetching data.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, runs: Iterable[Union[str, Run]], client: Optional["Client"] = None
|
||||
):
|
||||
"""
|
||||
Initialize a new LangSmithRunChatLoader instance.
|
||||
|
||||
:param runs: List of LLM run IDs or run objects.
|
||||
:param client: An instance of LangSmith client, if not provided,
|
||||
a new client instance will be created.
|
||||
"""
|
||||
from langsmith.client import Client
|
||||
|
||||
self.runs = runs
|
||||
self.client = client or Client()
|
||||
|
||||
def _load_single_chat_session(self, llm_run: "Run") -> ChatSession:
|
||||
"""
|
||||
Convert an individual LangSmith LLM run to a ChatSession.
|
||||
|
||||
:param llm_run: The LLM run object.
|
||||
:return: A chat session representing the run's data.
|
||||
"""
|
||||
chat_session = LangSmithRunChatLoader._get_messages_from_llm_run(llm_run)
|
||||
functions = LangSmithRunChatLoader._get_functions_from_llm_run(llm_run)
|
||||
if functions:
|
||||
chat_session["functions"] = functions
|
||||
return chat_session
|
||||
|
||||
@staticmethod
|
||||
def _get_messages_from_llm_run(llm_run: "Run") -> ChatSession:
|
||||
"""
|
||||
Extract messages from a LangSmith LLM run.
|
||||
|
||||
:param llm_run: The LLM run object.
|
||||
:return: ChatSession with the extracted messages.
|
||||
"""
|
||||
if llm_run.run_type != "llm":
|
||||
raise ValueError(f"Expected run of type llm. Got: {llm_run.run_type}")
|
||||
if "messages" not in llm_run.inputs:
|
||||
raise ValueError(f"Run has no 'messages' inputs. Got {llm_run.inputs}")
|
||||
if not llm_run.outputs:
|
||||
raise ValueError("Cannot convert pending run")
|
||||
messages = load(llm_run.inputs)["messages"]
|
||||
message_chunk = load(llm_run.outputs)["generations"][0]["message"]
|
||||
return ChatSession(messages=messages + [message_chunk])
|
||||
|
||||
@staticmethod
|
||||
def _get_functions_from_llm_run(llm_run: "Run") -> Optional[List[Dict]]:
|
||||
"""
|
||||
Extract functions from a LangSmith LLM run if they exist.
|
||||
|
||||
:param llm_run: The LLM run object.
|
||||
:return: Functions from the run or None.
|
||||
"""
|
||||
if llm_run.run_type != "llm":
|
||||
raise ValueError(f"Expected run of type llm. Got: {llm_run.run_type}")
|
||||
return llm_run.extra.get("invocation_params", {}).get("functions")
|
||||
|
||||
def lazy_load(self) -> Iterator[ChatSession]:
|
||||
"""
|
||||
Lazy load the chat sessions from the iterable of run IDs.
|
||||
|
||||
This method fetches the runs and converts them to chat sessions on-the-fly,
|
||||
yielding one session at a time.
|
||||
|
||||
:return: Iterator of chat sessions containing messages.
|
||||
"""
|
||||
for run_obj in self.runs:
|
||||
try:
|
||||
if hasattr(run_obj, "id"):
|
||||
run = run_obj
|
||||
else:
|
||||
run = self.client.read_run(run_obj)
|
||||
session = self._load_single_chat_session(run)
|
||||
yield session
|
||||
except ValueError as e:
|
||||
logger.warning(f"Could not load run {run_obj}: {repr(e)}")
|
||||
continue
|
||||
|
||||
|
||||
class LangSmithDatasetChatLoader(BaseChatLoader):
|
||||
"""
|
||||
Load chat sessions from a LangSmith dataset with the "chat" data type.
|
||||
|
||||
Attributes:
|
||||
dataset_name (str): The name of the LangSmith dataset.
|
||||
client (Client): Instance of LangSmith client for fetching data.
|
||||
"""
|
||||
|
||||
def __init__(self, *, dataset_name: str, client: Optional["Client"] = None):
|
||||
"""
|
||||
Initialize a new LangSmithChatDatasetLoader instance.
|
||||
|
||||
:param dataset_name: The name of the LangSmith dataset.
|
||||
:param client: An instance of LangSmith client; if not provided,
|
||||
a new client instance will be created.
|
||||
"""
|
||||
try:
|
||||
from langsmith.client import Client
|
||||
except ImportError as e:
|
||||
raise ImportError(
|
||||
"The LangSmith client is required to load LangSmith datasets.\n"
|
||||
"Please install it with `pip install langsmith`"
|
||||
) from e
|
||||
|
||||
self.dataset_name = dataset_name
|
||||
self.client = client or Client()
|
||||
|
||||
def lazy_load(self) -> Iterator[ChatSession]:
|
||||
"""
|
||||
Lazy load the chat sessions from the specified LangSmith dataset.
|
||||
|
||||
This method fetches the chat data from the dataset and
|
||||
converts each data point to chat sessions on-the-fly,
|
||||
yielding one session at a time.
|
||||
|
||||
:return: Iterator of chat sessions containing messages.
|
||||
"""
|
||||
from langchain.adapters import openai as oai_adapter # noqa: E402
|
||||
|
||||
data = self.client.read_dataset_openai_finetuning(
|
||||
dataset_name=self.dataset_name
|
||||
)
|
||||
for data_point in data:
|
||||
yield ChatSession(
|
||||
messages=[
|
||||
oai_adapter.convert_dict_to_message(m)
|
||||
for m in data_point.get("messages", [])
|
||||
],
|
||||
functions=data_point.get("functions"),
|
||||
)
|
Loading…
Reference in New Issue