diff --git a/libs/ai-endpoints/Makefile b/libs/ai-endpoints/Makefile
index 52863145..d79ec9d7 100644
--- a/libs/ai-endpoints/Makefile
+++ b/libs/ai-endpoints/Makefile
@@ -33,7 +33,6 @@ lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
- ./scripts/check_pydantic.sh .
./scripts/lint_imports.sh
poetry run ruff .
[ "$(PYTHON_FILES)" = "" ] || poetry run ruff format $(PYTHON_FILES) --diff
diff --git a/libs/ai-endpoints/docs/chat/nvidia_ai_endpoints.ipynb b/libs/ai-endpoints/docs/chat/nvidia_ai_endpoints.ipynb
index 4795e3c9..eb4cfdbc 100644
--- a/libs/ai-endpoints/docs/chat/nvidia_ai_endpoints.ipynb
+++ b/libs/ai-endpoints/docs/chat/nvidia_ai_endpoints.ipynb
@@ -41,9 +41,7 @@
"id": "e13eb331",
"metadata": {},
"outputs": [],
- "source": [
- "%pip install --upgrade --quiet langchain-nvidia-ai-endpoints"
- ]
+ "source": ["%pip install --upgrade --quiet langchain-nvidia-ai-endpoints"]
},
{
"cell_type": "markdown",
@@ -71,18 +69,7 @@
"id": "686c4d2f",
"metadata": {},
"outputs": [],
- "source": [
- "import getpass\n",
- "import os\n",
- "\n",
- "# del os.environ['NVIDIA_API_KEY'] ## delete key and reset\n",
- "if os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n",
- " print(\"Valid NVIDIA_API_KEY already in environment. Delete to reset\")\n",
- "else:\n",
- " nvapi_key = getpass.getpass(\"NVAPI Key (starts with nvapi-): \")\n",
- " assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n",
- " os.environ[\"NVIDIA_API_KEY\"] = nvapi_key"
- ]
+ "source": ["import getpass\nimport os\n\n# del os.environ['NVIDIA_API_KEY'] ## delete key and reset\nif os.environ.get(\"NVIDIA_API_KEY\", \"\").startswith(\"nvapi-\"):\n print(\"Valid NVIDIA_API_KEY already in environment. Delete to reset\")\nelse:\n nvapi_key = getpass.getpass(\"NVAPI Key (starts with nvapi-): \")\n assert nvapi_key.startswith(\"nvapi-\"), f\"{nvapi_key[:5]}... is not a valid key\"\n os.environ[\"NVIDIA_API_KEY\"] = nvapi_key"]
},
{
"cell_type": "markdown",
@@ -104,14 +91,7 @@
"outputId": "e9c4cc72-8db6-414b-d8e9-95de93fc5db4"
},
"outputs": [],
- "source": [
- "## Core LC Chat Interface\n",
- "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n",
- "\n",
- "llm = ChatNVIDIA(model=\"mistralai/mixtral-8x7b-instruct-v0.1\")\n",
- "result = llm.invoke(\"Write a ballad about LangChain.\")\n",
- "print(result.content)"
- ]
+ "source": ["## Core LC Chat Interface\nfrom langchain_nvidia_ai_endpoints import ChatNVIDIA\n\nllm = ChatNVIDIA(model=\"mistralai/mixtral-8x7b-instruct-v0.1\")\nresult = llm.invoke(\"Write a ballad about LangChain.\")\nprint(result.content)"]
},
{
"cell_type": "markdown",
@@ -130,12 +110,7 @@
"id": "49838930",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n",
- "\n",
- "# connect to an embedding NIM running at localhost:8000, specifying a specific model\n",
- "llm = ChatNVIDIA(base_url=\"http://localhost:8000/v1\", model=\"meta/llama3-8b-instruct\")"
- ]
+ "source": ["from langchain_nvidia_ai_endpoints import ChatNVIDIA\n\n# connect to an embedding NIM running at localhost:8000, specifying a specific model\nllm = ChatNVIDIA(base_url=\"http://localhost:8000/v1\", model=\"meta/llama3-8b-instruct\")"]
},
{
"cell_type": "markdown",
@@ -153,11 +128,7 @@
"id": "01fa5095-be72-47b0-8247-e9fac799435d",
"metadata": {},
"outputs": [],
- "source": [
- "print(llm.batch([\"What's 2*3?\", \"What's 2*6?\"]))\n",
- "# Or via the async API\n",
- "# await llm.abatch([\"What's 2*3?\", \"What's 2*6?\"])"
- ]
+ "source": ["print(llm.batch([\"What's 2*3?\", \"What's 2*6?\"]))\n# Or via the async API\n# await llm.abatch([\"What's 2*3?\", \"What's 2*6?\"])"]
},
{
"cell_type": "code",
@@ -165,11 +136,7 @@
"id": "75189ac6-e13f-414f-9064-075c77d6e754",
"metadata": {},
"outputs": [],
- "source": [
- "for chunk in llm.stream(\"How far can a seagull fly in one day?\"):\n",
- " # Show the token separations\n",
- " print(chunk.content, end=\"|\")"
- ]
+ "source": ["for chunk in llm.stream(\"How far can a seagull fly in one day?\"):\n # Show the token separations\n print(chunk.content, end=\"|\")"]
},
{
"cell_type": "code",
@@ -177,12 +144,7 @@
"id": "8a9a4122-7a10-40c0-a979-82a769ce7f6a",
"metadata": {},
"outputs": [],
- "source": [
- "async for chunk in llm.astream(\n",
- " \"How long does it take for monarch butterflies to migrate?\"\n",
- "):\n",
- " print(chunk.content, end=\"|\")"
- ]
+ "source": ["async for chunk in llm.astream(\n \"How long does it take for monarch butterflies to migrate?\"\n):\n print(chunk.content, end=\"|\")"]
},
{
"cell_type": "markdown",
@@ -204,10 +166,7 @@
"id": "5b8a312d-38e9-4528-843e-59451bdadbac",
"metadata": {},
"outputs": [],
- "source": [
- "ChatNVIDIA.get_available_models()\n",
- "# llm.get_available_models()"
- ]
+ "source": ["ChatNVIDIA.get_available_models()\n# llm.get_available_models()"]
},
{
"cell_type": "markdown",
@@ -247,19 +206,7 @@
"id": "f5f7aee8-e90c-4d5a-ac97-0dd3d45c3f4c",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_core.output_parsers import StrOutputParser\n",
- "from langchain_core.prompts import ChatPromptTemplate\n",
- "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n",
- "\n",
- "prompt = ChatPromptTemplate.from_messages(\n",
- " [(\"system\", \"You are a helpful AI assistant named Fred.\"), (\"user\", \"{input}\")]\n",
- ")\n",
- "chain = prompt | ChatNVIDIA(model=\"meta/llama3-8b-instruct\") | StrOutputParser()\n",
- "\n",
- "for txt in chain.stream({\"input\": \"What's your name?\"}):\n",
- " print(txt, end=\"\")"
- ]
+ "source": ["from langchain_core.output_parsers import StrOutputParser\nfrom langchain_core.prompts import ChatPromptTemplate\nfrom langchain_nvidia_ai_endpoints import ChatNVIDIA\n\nprompt = ChatPromptTemplate.from_messages(\n [(\"system\", \"You are a helpful AI assistant named Fred.\"), (\"user\", \"{input}\")]\n)\nchain = prompt | ChatNVIDIA(model=\"meta/llama3-8b-instruct\") | StrOutputParser()\n\nfor txt in chain.stream({\"input\": \"What's your name?\"}):\n print(txt, end=\"\")"]
},
{
"cell_type": "markdown",
@@ -277,21 +224,7 @@
"id": "49aa569b-5f33-47b3-9edc-df58313eb038",
"metadata": {},
"outputs": [],
- "source": [
- "prompt = ChatPromptTemplate.from_messages(\n",
- " [\n",
- " (\n",
- " \"system\",\n",
- " \"You are an expert coding AI. Respond only in valid python; no narration whatsoever.\",\n",
- " ),\n",
- " (\"user\", \"{input}\"),\n",
- " ]\n",
- ")\n",
- "chain = prompt | ChatNVIDIA(model=\"meta/codellama-70b\") | StrOutputParser()\n",
- "\n",
- "for txt in chain.stream({\"input\": \"How do I solve this fizz buzz problem?\"}):\n",
- " print(txt, end=\"\")"
- ]
+ "source": ["prompt = ChatPromptTemplate.from_messages(\n [\n (\n \"system\",\n \"You are an expert coding AI. Respond only in valid python; no narration whatsoever.\",\n ),\n (\"user\", \"{input}\"),\n ]\n)\nchain = prompt | ChatNVIDIA(model=\"meta/codellama-70b\") | StrOutputParser()\n\nfor txt in chain.stream({\"input\": \"How do I solve this fizz buzz problem?\"}):\n print(txt, end=\"\")"]
},
{
"cell_type": "markdown",
@@ -311,15 +244,7 @@
"id": "26625437-1695-440f-b792-b85e6add9a90",
"metadata": {},
"outputs": [],
- "source": [
- "import IPython\n",
- "import requests\n",
- "\n",
- "image_url = \"https://www.nvidia.com/content/dam/en-zz/Solutions/research/ai-playground/nvidia-picasso-3c33-p@2x.jpg\" ## Large Image\n",
- "image_content = requests.get(image_url).content\n",
- "\n",
- "IPython.display.Image(image_content)"
- ]
+ "source": ["import IPython\nimport requests\n\nimage_url = \"https://www.nvidia.com/content/dam/en-zz/Solutions/research/ai-playground/nvidia-picasso-3c33-p@2x.jpg\" ## Large Image\nimage_content = requests.get(image_url).content\n\nIPython.display.Image(image_content)"]
},
{
"cell_type": "code",
@@ -327,11 +252,7 @@
"id": "dfbbe57c-27a5-4cbb-b967-19c4e7d29fd0",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n",
- "\n",
- "llm = ChatNVIDIA(model=\"nvidia/neva-22b\")"
- ]
+ "source": ["from langchain_nvidia_ai_endpoints import ChatNVIDIA\n\nllm = ChatNVIDIA(model=\"nvidia/neva-22b\")"]
},
{
"cell_type": "markdown",
@@ -347,15 +268,76 @@
"id": "432ea2a2-4d39-43f8-a236-041294171f14",
"metadata": {},
"outputs": [],
+ "source": ["from langchain_core.messages import HumanMessage\n\nllm.invoke(\n [\n HumanMessage(\n content=[\n {\"type\": \"text\", \"text\": \"Describe this image:\"},\n {\"type\": \"image_url\", \"image_url\": {\"url\": image_url}},\n ]\n )\n ]\n)"]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "25e8db7c",
+ "metadata": {},
"source": [
- "from langchain_core.messages import HumanMessage\n",
+ "#### Passing an image as an NVCF asset\n",
+ "\n",
+ "If your image is sufficiently large or you will pass it multiple times in a chat conversation, you may upload it once and reference it in your chat conversation.\n",
"\n",
+ "See https://docs.nvidia.com/cloud-functions/user-guide/latest/cloud-function/assets.html for details about how upload the image."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "091f7fce",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import requests\n",
+ "\n",
+ "content_type = \"image/jpg\"\n",
+ "description = \"example-image-from-lc-nv-ai-e-notebook\"\n",
+ "\n",
+ "create_response = requests.post(\n",
+ " \"https://api.nvcf.nvidia.com/v2/nvcf/assets\",\n",
+ " headers={\n",
+ " \"Authorization\": f\"Bearer {os.environ['NVIDIA_API_KEY']}\",\n",
+ " \"accept\": \"application/json\",\n",
+ " \"Content-Type\": \"application/json\",\n",
+ " },\n",
+ " json={\n",
+ " \"contentType\": content_type,\n",
+ " \"description\": description\n",
+ " }\n",
+ ")\n",
+ "create_response.raise_for_status()\n",
+ "\n",
+ "upload_response = requests.put(\n",
+ " create_response.json()[\"uploadUrl\"],\n",
+ " headers={\n",
+ " \"Content-Type\": content_type,\n",
+ " \"x-amz-meta-nvcf-asset-description\": description,\n",
+ " },\n",
+ " data=image_content,\n",
+ ")\n",
+ "upload_response.raise_for_status()\n",
+ "\n",
+ "asset_id = create_response.json()[\"assetId\"]\n",
+ "asset_id"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5c24be59",
+ "metadata": {},
+ "outputs": [],
+ "source": [
"llm.invoke(\n",
" [\n",
" HumanMessage(\n",
" content=[\n",
- " {\"type\": \"text\", \"text\": \"Describe this image:\"},\n",
- " {\"type\": \"image_url\", \"image_url\": {\"url\": image_url}},\n",
+ " {\"type\": \"text\", \"text\": \"Describe this image\"},\n",
+ " {\n",
+ " \"type\": \"image_url\",\n",
+ " \"image_url\": {\"url\": f\"data:{content_type};asset_id,{asset_id}\"},\n",
+ " },\n",
" ]\n",
" )\n",
" ]\n",
@@ -384,15 +366,7 @@
"id": "c58f1dd0",
"metadata": {},
"outputs": [],
- "source": [
- "import IPython\n",
- "import requests\n",
- "\n",
- "image_url = \"https://picsum.photos/seed/kitten/300/200\"\n",
- "image_content = requests.get(image_url).content\n",
- "\n",
- "IPython.display.Image(image_content)"
- ]
+ "source": ["import IPython\nimport requests\n\nimage_url = \"https://picsum.photos/seed/kitten/300/200\"\nimage_content = requests.get(image_url).content\n\nIPython.display.Image(image_content)"]
},
{
"cell_type": "code",
@@ -400,28 +374,7 @@
"id": "8c721629-42eb-4006-bf68-0296f7925ebc",
"metadata": {},
"outputs": [],
- "source": [
- "import base64\n",
- "\n",
- "from langchain_core.messages import HumanMessage\n",
- "\n",
- "## Works for simpler images. For larger images, see actual implementation\n",
- "b64_string = base64.b64encode(image_content).decode(\"utf-8\")\n",
- "\n",
- "llm.invoke(\n",
- " [\n",
- " HumanMessage(\n",
- " content=[\n",
- " {\"type\": \"text\", \"text\": \"Describe this image:\"},\n",
- " {\n",
- " \"type\": \"image_url\",\n",
- " \"image_url\": {\"url\": f\"data:image/png;base64,{b64_string}\"},\n",
- " },\n",
- " ]\n",
- " )\n",
- " ]\n",
- ")"
- ]
+ "source": ["import base64\n\nfrom langchain_core.messages import HumanMessage\n\n## Works for simpler images. For larger images, see actual implementation\nb64_string = base64.b64encode(image_content).decode(\"utf-8\")\n\nllm.invoke(\n [\n HumanMessage(\n content=[\n {\"type\": \"text\", \"text\": \"Describe this image:\"},\n {\n \"type\": \"image_url\",\n \"image_url\": {\"url\": f\"data:image/png;base64,{b64_string}\"},\n },\n ]\n )\n ]\n)"]
},
{
"cell_type": "markdown",
@@ -439,10 +392,7 @@
"id": "00c06a9a-497b-4192-a842-b075e27401aa",
"metadata": {},
"outputs": [],
- "source": [
- "base64_with_mime_type = f\"data:image/png;base64,{b64_string}\"\n",
- "llm.invoke(f'What\\'s in this image?\\n')"
- ]
+ "source": ["base64_with_mime_type = f\"data:image/png;base64,{b64_string}\"\nllm.invoke(f'What\\'s in this image?\\n')"]
},
{
"cell_type": "markdown",
@@ -470,9 +420,7 @@
"id": "082ccb21-91e1-4e71-a9ba-4bff1e89f105",
"metadata": {},
"outputs": [],
- "source": [
- "%pip install --upgrade --quiet langchain"
- ]
+ "source": ["%pip install --upgrade --quiet langchain"]
},
{
"cell_type": "code",
@@ -482,41 +430,7 @@
"id": "fd2c6bc1"
},
"outputs": [],
- "source": [
- "from langchain_core.chat_history import InMemoryChatMessageHistory\n",
- "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
- "\n",
- "# store is a dictionary that maps session IDs to their corresponding chat histories.\n",
- "store = {} # memory is maintained outside the chain\n",
- "\n",
- "\n",
- "# A function that returns the chat history for a given session ID.\n",
- "def get_session_history(session_id: str) -> InMemoryChatMessageHistory:\n",
- " if session_id not in store:\n",
- " store[session_id] = InMemoryChatMessageHistory()\n",
- " return store[session_id]\n",
- "\n",
- "\n",
- "chat = ChatNVIDIA(\n",
- " model=\"mistralai/mixtral-8x22b-instruct-v0.1\",\n",
- " temperature=0.1,\n",
- " max_tokens=100,\n",
- " top_p=1.0,\n",
- ")\n",
- "\n",
- "# Define a RunnableConfig object, with a `configurable` key. session_id determines thread\n",
- "config = {\"configurable\": {\"session_id\": \"1\"}}\n",
- "\n",
- "conversation = RunnableWithMessageHistory(\n",
- " chat,\n",
- " get_session_history,\n",
- ")\n",
- "\n",
- "conversation.invoke(\n",
- " \"Hi I'm Srijan Dubey.\", # input or query\n",
- " config=config,\n",
- ")"
- ]
+ "source": ["from langchain_core.chat_history import InMemoryChatMessageHistory\nfrom langchain_core.runnables.history import RunnableWithMessageHistory\n\n# store is a dictionary that maps session IDs to their corresponding chat histories.\nstore = {} # memory is maintained outside the chain\n\n\n# A function that returns the chat history for a given session ID.\ndef get_session_history(session_id: str) -> InMemoryChatMessageHistory:\n if session_id not in store:\n store[session_id] = InMemoryChatMessageHistory()\n return store[session_id]\n\n\nchat = ChatNVIDIA(\n model=\"mistralai/mixtral-8x22b-instruct-v0.1\",\n temperature=0.1,\n max_tokens=100,\n top_p=1.0,\n)\n\n# Define a RunnableConfig object, with a `configurable` key. session_id determines thread\nconfig = {\"configurable\": {\"session_id\": \"1\"}}\n\nconversation = RunnableWithMessageHistory(\n chat,\n get_session_history,\n)\n\nconversation.invoke(\n \"Hi I'm Srijan Dubey.\", # input or query\n config=config,\n)"]
},
{
"cell_type": "code",
@@ -531,12 +445,7 @@
"outputId": "79acc89d-a820-4f2c-bac2-afe99da95580"
},
"outputs": [],
- "source": [
- "conversation.invoke(\n",
- " \"I'm doing well! Just having a conversation with an AI.\",\n",
- " config=config,\n",
- ")"
- ]
+ "source": ["conversation.invoke(\n \"I'm doing well! Just having a conversation with an AI.\",\n config=config,\n)"]
},
{
"cell_type": "code",
@@ -551,12 +460,7 @@
"outputId": "a1714513-a8fd-4d14-f974-233e39d5c4f5"
},
"outputs": [],
- "source": [
- "conversation.invoke(\n",
- " \"Tell me about yourself.\",\n",
- " config=config,\n",
- ")"
- ]
+ "source": ["conversation.invoke(\n \"Tell me about yourself.\",\n config=config,\n)"]
},
{
"cell_type": "markdown",
@@ -584,10 +488,7 @@
"id": "e36c8911",
"metadata": {},
"outputs": [],
- "source": [
- "tool_models = [model for model in ChatNVIDIA.get_available_models() if model.supports_tools]\n",
- "tool_models"
- ]
+ "source": ["tool_models = [model for model in ChatNVIDIA.get_available_models() if model.supports_tools]\ntool_models"]
},
{
"cell_type": "markdown",
@@ -603,21 +504,7 @@
"id": "bd54f174",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_core.pydantic_v1 import Field\n",
- "from langchain_core.tools import tool\n",
- "\n",
- "@tool\n",
- "def get_current_weather(\n",
- " location: str = Field(..., description=\"The location to get the weather for.\")\n",
- "):\n",
- " \"\"\"Get the current weather for a location.\"\"\"\n",
- " ...\n",
- "\n",
- "llm = ChatNVIDIA(model=tool_models[0].id).bind_tools(tools=[get_current_weather])\n",
- "response = llm.invoke(\"What is the weather in Boston?\")\n",
- "response.tool_calls"
- ]
+ "source": ["from pydantic import Field\nfrom langchain_core.tools import tool\n\n@tool\ndef get_current_weather(\n location: str = Field(..., description=\"The location to get the weather for.\")\n):\n \"\"\"Get the current weather for a location.\"\"\"\n ...\n\nllm = ChatNVIDIA(model=tool_models[0].id).bind_tools(tools=[get_current_weather])\nresponse = llm.invoke(\"What is the weather in Boston?\")\nresponse.tool_calls"]
},
{
"cell_type": "markdown",
@@ -655,11 +542,7 @@
"id": "0515f558",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_nvidia_ai_endpoints import ChatNVIDIA\n",
- "structured_models = [model for model in ChatNVIDIA.get_available_models() if model.supports_structured_output]\n",
- "structured_models"
- ]
+ "source": ["from langchain_nvidia_ai_endpoints import ChatNVIDIA\nstructured_models = [model for model in ChatNVIDIA.get_available_models() if model.supports_structured_output]\nstructured_models"]
},
{
"cell_type": "markdown",
@@ -675,17 +558,7 @@
"id": "482c37e8",
"metadata": {},
"outputs": [],
- "source": [
- "from langchain_core.pydantic_v1 import BaseModel, Field\n",
- "\n",
- "class Person(BaseModel):\n",
- " first_name: str = Field(..., description=\"The person's first name.\")\n",
- " last_name: str = Field(..., description=\"The person's last name.\")\n",
- "\n",
- "llm = ChatNVIDIA(model=structured_models[0].id).with_structured_output(Person)\n",
- "response = llm.invoke(\"Who is Michael Jeffrey Jordon?\")\n",
- "response"
- ]
+ "source": ["from pydantic import BaseModel, Field\n\nclass Person(BaseModel):\n first_name: str = Field(..., description=\"The person's first name.\")\n last_name: str = Field(..., description=\"The person's last name.\")\n\nllm = ChatNVIDIA(model=structured_models[0].id).with_structured_output(Person)\nresponse = llm.invoke(\"Who is Michael Jeffrey Jordon?\")\nresponse"]
},
{
"cell_type": "markdown",
@@ -701,24 +574,7 @@
"id": "7f802912",
"metadata": {},
"outputs": [],
- "source": [
- "from enum import Enum\n",
- "\n",
- "class Choices(Enum):\n",
- " A = \"A\"\n",
- " B = \"B\"\n",
- " C = \"C\"\n",
- "\n",
- "llm = ChatNVIDIA(model=structured_models[2].id).with_structured_output(Choices)\n",
- "response = llm.invoke(\"\"\"\n",
- " What does 1+1 equal?\n",
- " A. -100\n",
- " B. 2\n",
- " C. doorstop\n",
- " \"\"\"\n",
- ")\n",
- "response"
- ]
+ "source": ["from enum import Enum\n\nclass Choices(Enum):\n A = \"A\"\n B = \"B\"\n C = \"C\"\n\nllm = ChatNVIDIA(model=structured_models[2].id).with_structured_output(Choices)\nresponse = llm.invoke(\"\"\"\n What does 1+1 equal?\n A. -100\n B. 2\n C. doorstop\n \"\"\"\n)\nresponse"]
},
{
"cell_type": "code",
@@ -726,19 +582,7 @@
"id": "02b7ef29",
"metadata": {},
"outputs": [],
- "source": [
- "model = structured_models[3].id\n",
- "llm = ChatNVIDIA(model=model).with_structured_output(Choices)\n",
- "print(model)\n",
- "response = llm.invoke(\"\"\"\n",
- " What does 1+1 equal?\n",
- " A. -100\n",
- " B. 2\n",
- " C. doorstop\n",
- " \"\"\"\n",
- ")\n",
- "response"
- ]
+ "source": ["model = structured_models[3].id\nllm = ChatNVIDIA(model=model).with_structured_output(Choices)\nprint(model)\nresponse = llm.invoke(\"\"\"\n What does 1+1 equal?\n A. -100\n B. 2\n C. doorstop\n \"\"\"\n)\nresponse"]
}
],
"metadata": {
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_common.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_common.py
index 019cdb01..218a0ab4 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_common.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_common.py
@@ -20,13 +20,13 @@
from urllib.parse import urlparse, urlunparse
import requests
-from langchain_core.pydantic_v1 import (
+from pydantic import (
BaseModel,
+ ConfigDict,
Field,
PrivateAttr,
SecretStr,
- root_validator,
- validator,
+ field_validator,
)
from requests.models import Response
@@ -34,6 +34,9 @@
logger = logging.getLogger(__name__)
+_API_KEY_VAR = "NVIDIA_API_KEY"
+_BASE_URL_VAR = "NVIDIA_BASE_URL"
+
class _NVIDIAClient(BaseModel):
"""
@@ -41,20 +44,23 @@ class _NVIDIAClient(BaseModel):
"""
default_hosted_model_name: str = Field(..., description="Default model name to use")
- model_name: Optional[str] = Field(..., description="Name of the model to invoke")
+ # "mdl_name" because "model_" is a protected namespace in pydantic
+ mdl_name: Optional[str] = Field(..., description="Name of the model to invoke")
model: Optional[Model] = Field(None, description="The model to invoke")
is_hosted: bool = Field(True)
cls: str = Field(..., description="Class Name")
# todo: add a validator for requests.Response (last_response attribute) and
# remove arbitrary_types_allowed=True
- class Config:
- arbitrary_types_allowed = True
+ model_config = ConfigDict(
+ arbitrary_types_allowed=True,
+ )
## Core defaults. These probably should not be changed
- _api_key_var = "NVIDIA_API_KEY"
base_url: str = Field(
- ...,
+ default_factory=lambda: os.getenv(
+ _BASE_URL_VAR, "https://integrate.api.nvidia.com/v1"
+ ),
description="Base URL for standard inference",
)
infer_path: str = Field(
@@ -71,13 +77,28 @@ class Config:
)
get_session_fn: Callable = Field(requests.Session)
- api_key: Optional[SecretStr] = Field(description="API Key for service of choice")
+ api_key: Optional[SecretStr] = Field(
+ default_factory=lambda: SecretStr(
+ os.getenv(_API_KEY_VAR, "INTERNAL_LCNVAIE_ERROR")
+ )
+ if _API_KEY_VAR in os.environ
+ else None,
+ description="API Key for service of choice",
+ )
## Generation arguments
- timeout: float = Field(60, ge=0, description="Timeout for waiting on response (s)")
- interval: float = Field(0.02, ge=0, description="Interval for pulling response")
+ timeout: float = Field(
+ 60,
+ ge=0,
+ description="The minimum amount of time (in sec) to poll after a 202 response",
+ )
+ interval: float = Field(
+ 0.02,
+ ge=0,
+ description="Interval (in sec) between polling attempts after a 202 response",
+ )
last_inputs: Optional[dict] = Field(
- description="Last inputs sent over to the server"
+ default={}, description="Last inputs sent over to the server"
)
last_response: Response = Field(
None, description="Last response sent from the server"
@@ -103,47 +124,25 @@ class Config:
###################################################################################
################### Validation and Initialization #################################
- @validator("base_url")
+ @field_validator("base_url")
def _validate_base_url(cls, v: str) -> str:
+ ## Making sure /v1 in added to the url
if v is not None:
- result = urlparse(v)
- expected_format = "Expected format is 'http://host:port'."
- # Ensure scheme and netloc (domain name) are present
- if not (result.scheme and result.netloc):
- raise ValueError(f"Invalid base_url format. {expected_format} Got: {v}")
- return v
-
- @root_validator(pre=True)
- def _preprocess_args(cls, values: Dict[str, Any]) -> Dict[str, Any]:
- # if api_key is not provided or None,
- # try to get it from the environment
- # we can't use Field(default_factory=...)
- # because construction may happen with api_key=None
- if values.get("api_key") is None:
- values["api_key"] = os.getenv(cls._api_key_var)
-
- ## Making sure /v1 in added to the url, followed by infer_path
- if "base_url" in values:
- base_url = values["base_url"].strip("/")
- parsed = urlparse(base_url)
- expected_format = "Expected format is: http://host:port"
+ parsed = urlparse(v)
+ # Ensure scheme and netloc (domain name) are present
if not (parsed.scheme and parsed.netloc):
- raise ValueError(
- f"Invalid base_url format. {expected_format} Got: {base_url}"
- )
+ expected_format = "Expected format is: http://host:port"
+ raise ValueError(f"Invalid base_url format. {expected_format} Got: {v}")
- if base_url.endswith(
+ if v.strip("/").endswith(
("/embeddings", "/completions", "/rankings", "/reranking")
):
- warnings.warn(f"Using {base_url}, ignoring the rest")
+ warnings.warn(f"Using {v}, ignoring the rest")
- values["base_url"] = base_url = urlunparse(
- (parsed.scheme, parsed.netloc, "v1", None, None, None)
- )
- values["infer_path"] = values["infer_path"].format(base_url=base_url)
+ v = urlunparse((parsed.scheme, parsed.netloc, "v1", None, None, None))
- return values
+ return v
# final validation after model is constructed
# todo: when pydantic v2 is available,
@@ -165,10 +164,10 @@ def __init__(self, **kwargs: Any):
)
# set default model for hosted endpoint
- if not self.model_name:
- self.model_name = self.default_hosted_model_name
+ if not self.mdl_name:
+ self.mdl_name = self.default_hosted_model_name
- if model := determine_model(self.model_name):
+ if model := determine_model(self.mdl_name):
if not model.client:
warnings.warn(f"Unable to determine validity of {model.id}")
elif model.client != self.cls:
@@ -186,27 +185,27 @@ def __init__(self, **kwargs: Any):
candidates = [
model
for model in self.available_models
- if model.id == self.model_name
+ if model.id == self.mdl_name
]
assert len(candidates) <= 1, (
- f"Multiple candidates for {self.model_name} "
+ f"Multiple candidates for {self.mdl_name} "
f"in `available_models`: {candidates}"
)
if candidates:
model = candidates[0]
warnings.warn(
- f"Found {self.model_name} in available_models, but type is "
+ f"Found {self.mdl_name} in available_models, but type is "
"unknown and inference may fail."
)
else:
raise ValueError(
- f"Model {self.model_name} is unknown, check `available_models`"
+ f"Model {self.mdl_name} is unknown, check `available_models`"
)
self.model = model
- self.model_name = self.model.id # name may change because of aliasing
+ self.mdl_name = self.model.id # name may change because of aliasing
else:
# set default model
- if not self.model_name:
+ if not self.mdl_name:
valid_models = [
model
for model in self.available_models
@@ -214,9 +213,9 @@ def __init__(self, **kwargs: Any):
]
self.model = next(iter(valid_models), None)
if self.model:
- self.model_name = self.model.id
+ self.mdl_name = self.model.id
warnings.warn(
- f"Default model is set as: {self.model_name}. \n"
+ f"Default model is set as: {self.mdl_name}. \n"
"Set model using model parameter. \n"
"To get available models use available_models property.",
UserWarning,
@@ -233,15 +232,15 @@ def is_lc_serializable(cls) -> bool:
@property
def lc_secrets(self) -> Dict[str, str]:
- return {"api_key": self._api_key_var}
+ return {"api_key": _API_KEY_VAR}
@property
def lc_attributes(self) -> Dict[str, Any]:
attributes: Dict[str, Any] = {}
attributes["base_url"] = self.base_url
- if self.model_name:
- attributes["model"] = self.model_name
+ if self.mdl_name:
+ attributes["model"] = self.mdl_name
return attributes
@@ -332,11 +331,15 @@ def _post(
self,
invoke_url: str,
payload: Optional[dict] = {},
+ extra_headers: dict = {},
) -> Tuple[Response, requests.Session]:
"""Method for posting to the AI Foundation Model Function API."""
self.last_inputs = {
"url": invoke_url,
- "headers": self.headers_tmpl["call"],
+ "headers": {
+ **self.headers_tmpl["call"],
+ **extra_headers,
+ },
"json": payload,
}
session = self.get_session_fn()
@@ -372,9 +375,7 @@ def _wait(self, response: Response, session: requests.Session) -> Response:
start_time = time.time()
# note: the local NIM does not return a 202 status code
# (per RL 22may2024 circa 24.05)
- while (
- response.status_code == 202
- ): # todo: there are no tests that reach this point
+ while response.status_code == 202:
time.sleep(self.interval)
if (time.time() - start_time) > self.timeout:
raise TimeoutError(
@@ -385,10 +386,12 @@ def _wait(self, response: Response, session: requests.Session) -> Response:
"NVCF-REQID" in response.headers
), "Received 202 response with no request id to follow"
request_id = response.headers.get("NVCF-REQID")
- # todo: this needs testing, missing auth header update
+ payload = {
+ "url": self.polling_url_tmpl.format(request_id=request_id),
+ "headers": self.headers_tmpl["call"],
+ }
self.last_response = response = session.get(
- self.polling_url_tmpl.format(request_id=request_id),
- headers=self.headers_tmpl["call"],
+ **self.__add_authorization(payload)
)
self._try_raise(response)
return response
@@ -444,9 +447,12 @@ def _try_raise(self, response: Response) -> None:
def get_req(
self,
payload: dict = {},
+ extra_headers: dict = {},
) -> Response:
"""Post to the API."""
- response, session = self._post(self.infer_url, payload)
+ response, session = self._post(
+ self.infer_url, payload, extra_headers=extra_headers
+ )
return self._wait(response, session)
def postprocess(
@@ -485,7 +491,10 @@ def _aggregate_msgs(self, msg_list: Sequence[dict]) -> Tuple[dict, bool]:
usage_holder = msg.get("usage", {}) ####
if "choices" in msg:
## Tease out ['choices'][0]...['delta'/'message']
- msg = msg.get("choices", [{}])[0]
+ # when streaming w/ usage info, we may get a response
+ # w/ choices: [] that includes final usage info
+ choices = msg.get("choices", [{}])
+ msg = choices[0] if choices else {}
# todo: this meeds to be fixed, the fact we only
# use the first choice breaks the interface
finish_reason_holder = msg.get("finish_reason", None)
@@ -517,10 +526,14 @@ def _aggregate_msgs(self, msg_list: Sequence[dict]) -> Tuple[dict, bool]:
def get_req_stream(
self,
payload: dict,
+ extra_headers: dict = {},
) -> Iterator[Dict]:
self.last_inputs = {
"url": self.infer_url,
- "headers": self.headers_tmpl["stream"],
+ "headers": {
+ **self.headers_tmpl["stream"],
+ **extra_headers,
+ },
"json": payload,
}
@@ -528,7 +541,7 @@ def get_req_stream(
stream=True, **self.__add_authorization(self.last_inputs)
)
self._try_raise(response)
- call = self.copy()
+ call: _NVIDIAClient = self.model_copy()
def out_gen() -> Generator[dict, Any, Any]:
## Good for client, since it allows self.last_inputs
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_statics.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_statics.py
index 2f00b4ce..edd2014a 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_statics.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/_statics.py
@@ -2,7 +2,7 @@
import warnings
from typing import Literal, Optional
-from langchain_core.pydantic_v1 import BaseModel, validator
+from pydantic import BaseModel, model_validator
class Model(BaseModel):
@@ -23,7 +23,7 @@ class Model(BaseModel):
id: str
# why do we have a model_type? because ChatNVIDIA can speak both chat and vlm.
model_type: Optional[
- Literal["chat", "vlm", "embedding", "ranking", "completions", "qa"]
+ Literal["chat", "vlm", "nv-vlm", "embedding", "ranking", "completions", "qa"]
] = None
client: Optional[
Literal["ChatNVIDIA", "NVIDIAEmbeddings", "NVIDIARerank", "NVIDIA"]
@@ -37,21 +37,21 @@ class Model(BaseModel):
def __hash__(self) -> int:
return hash(self.id)
- @validator("client", always=True)
- def validate_client(cls, client: str, values: dict) -> str:
- if client:
+ @model_validator(mode="after")
+ def validate_client(self) -> "Model":
+ if self.client:
supported = {
- "ChatNVIDIA": ("chat", "vlm", "qa"),
+ "ChatNVIDIA": ("chat", "vlm", "nv-vlm", "qa"),
"NVIDIAEmbeddings": ("embedding",),
"NVIDIARerank": ("ranking",),
"NVIDIA": ("completions",),
}
- model_type = values.get("model_type")
- if model_type not in supported[client]:
+ if self.model_type not in supported.get(self.client, ()):
raise ValueError(
- f"Model type '{model_type}' not supported by client '{client}'"
+ f"Model type '{self.model_type}' not supported "
+ f"by client '{self.client}'"
)
- return client
+ return self
CHAT_MODEL_TABLE = {
@@ -427,63 +427,56 @@ def validate_client(cls, client: str, values: dict) -> str:
VLM_MODEL_TABLE = {
"adept/fuyu-8b": Model(
id="adept/fuyu-8b",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/adept/fuyu-8b",
aliases=["ai-fuyu-8b", "playground_fuyu_8b", "fuyu_8b"],
),
"google/deplot": Model(
id="google/deplot",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/google/deplot",
aliases=["ai-google-deplot", "playground_deplot", "deplot"],
),
"microsoft/kosmos-2": Model(
id="microsoft/kosmos-2",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/microsoft/kosmos-2",
aliases=["ai-microsoft-kosmos-2", "playground_kosmos_2", "kosmos_2"],
),
"nvidia/neva-22b": Model(
id="nvidia/neva-22b",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/nvidia/neva-22b",
aliases=["ai-neva-22b", "playground_neva_22b", "neva_22b"],
),
"google/paligemma": Model(
id="google/paligemma",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/google/paligemma",
aliases=["ai-google-paligemma"],
),
"microsoft/phi-3-vision-128k-instruct": Model(
id="microsoft/phi-3-vision-128k-instruct",
- model_type="vlm",
+ model_type="nv-vlm",
client="ChatNVIDIA",
endpoint="https://ai.api.nvidia.com/v1/vlm/microsoft/phi-3-vision-128k-instruct",
aliases=["ai-phi-3-vision-128k-instruct"],
),
- "liuhaotian/llava-v1.6-mistral-7b": Model(
- id="liuhaotian/llava-v1.6-mistral-7b",
+ "microsoft/phi-3.5-vision-instruct": Model(
+ id="microsoft/phi-3.5-vision-instruct",
model_type="vlm",
client="ChatNVIDIA",
- endpoint="https://ai.api.nvidia.com/v1/stg/vlm/community/llava16-mistral-7b",
- aliases=[
- "ai-llava16-mistral-7b",
- "community/llava16-mistral-7b",
- "liuhaotian/llava16-mistral-7b",
- ],
),
- "liuhaotian/llava-v1.6-34b": Model(
- id="liuhaotian/llava-v1.6-34b",
+ "nvidia/vila": Model(
+ id="nvidia/vila",
model_type="vlm",
client="ChatNVIDIA",
- endpoint="https://ai.api.nvidia.com/v1/stg/vlm/community/llava16-34b",
- aliases=["ai-llava16-34b", "community/llava16-34b", "liuhaotian/llava16-34b"],
+ endpoint="https://ai.api.nvidia.com/v1/vlm/nvidia/vila",
),
}
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/chat_models.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/chat_models.py
index 42d825eb..28018dcc 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/chat_models.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/chat_models.py
@@ -4,10 +4,9 @@
import base64
import enum
-import io
import logging
import os
-import sys
+import re
import urllib.parse
import warnings
from typing import (
@@ -19,17 +18,18 @@
Literal,
Optional,
Sequence,
+ Tuple,
Type,
Union,
)
-import requests
from langchain_core.callbacks.manager import (
AsyncCallbackManagerForLLMRun,
CallbackManagerForLLMRun,
)
from langchain_core.exceptions import OutputParserException
from langchain_core.language_models import BaseChatModel, LanguageModelInput
+from langchain_core.language_models.chat_models import LangSmithParams
from langchain_core.messages import (
AIMessage,
AIMessageChunk,
@@ -46,26 +46,17 @@
ChatResult,
Generation,
)
-from langchain_core.pydantic_v1 import BaseModel, Field, PrivateAttr, root_validator
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain_core.utils.pydantic import is_basemodel_subclass
+from pydantic import BaseModel, Field, PrivateAttr
from langchain_nvidia_ai_endpoints._common import _NVIDIAClient
from langchain_nvidia_ai_endpoints._statics import Model
from langchain_nvidia_ai_endpoints._utils import convert_message_to_dict
_CallbackManager = Union[AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun]
-_DictOrPydanticOrEnumClass = Union[Dict[str, Any], Type[BaseModel], Type[enum.Enum]]
-_DictOrPydanticOrEnum = Union[Dict, BaseModel, enum.Enum]
-
-try:
- import PIL.Image
-
- has_pillow = True
-except ImportError:
- has_pillow = False
logger = logging.getLogger(__name__)
@@ -79,46 +70,56 @@ def _is_url(s: str) -> bool:
return False
-def _resize_image(img_data: bytes, max_dim: int = 1024) -> str:
- if not has_pillow:
- print( # noqa: T201
- "Pillow is required to resize images down to reasonable scale."
- " Please install it using `pip install pillow`."
- " For now, not resizing; may cause NVIDIA API to fail."
- )
- return base64.b64encode(img_data).decode("utf-8")
- image = PIL.Image.open(io.BytesIO(img_data))
- max_dim_size = max(image.size)
- aspect_ratio = max_dim / max_dim_size
- new_h = int(image.size[1] * aspect_ratio)
- new_w = int(image.size[0] * aspect_ratio)
- resized_image = image.resize((new_w, new_h), PIL.Image.Resampling.LANCZOS)
- output_buffer = io.BytesIO()
- resized_image.save(output_buffer, format="JPEG")
- output_buffer.seek(0)
- resized_b64_string = base64.b64encode(output_buffer.read()).decode("utf-8")
- return resized_b64_string
-
-
def _url_to_b64_string(image_source: str) -> str:
- b64_template = "data:image/png;base64,{b64_string}"
try:
if _is_url(image_source):
- response = requests.get(
- image_source, headers={"User-Agent": "langchain-nvidia-ai-endpoints"}
- )
- response.raise_for_status()
- encoded = base64.b64encode(response.content).decode("utf-8")
- if sys.getsizeof(encoded) > 200000:
- ## (VK) Temporary fix. NVIDIA API has a limit of 250KB for the input.
- encoded = _resize_image(response.content)
- return b64_template.format(b64_string=encoded)
+ return image_source
+ # import sys
+ # import io
+ # try:
+ # import PIL.Image
+ # has_pillow = True
+ # except ImportError:
+ # has_pillow = False
+ # def _resize_image(img_data: bytes, max_dim: int = 1024) -> str:
+ # if not has_pillow:
+ # print( # noqa: T201
+ # "Pillow is required to resize images down to reasonable scale." # noqa: E501
+ # " Please install it using `pip install pillow`."
+ # " For now, not resizing; may cause NVIDIA API to fail."
+ # )
+ # return base64.b64encode(img_data).decode("utf-8")
+ # image = PIL.Image.open(io.BytesIO(img_data))
+ # max_dim_size = max(image.size)
+ # aspect_ratio = max_dim / max_dim_size
+ # new_h = int(image.size[1] * aspect_ratio)
+ # new_w = int(image.size[0] * aspect_ratio)
+ # resized_image = image.resize((new_w, new_h), PIL.Image.Resampling.LANCZOS) # noqa: E501
+ # output_buffer = io.BytesIO()
+ # resized_image.save(output_buffer, format="JPEG")
+ # output_buffer.seek(0)
+ # resized_b64_string = base64.b64encode(output_buffer.read()).decode("utf-8") # noqa: E501
+ # return resized_b64_string
+ # b64_template = "data:image/png;base64,{b64_string}"
+ # response = requests.get(
+ # image_source, headers={"User-Agent": "langchain-nvidia-ai-endpoints"}
+ # )
+ # response.raise_for_status()
+ # encoded = base64.b64encode(response.content).decode("utf-8")
+ # if sys.getsizeof(encoded) > 200000:
+ # ## (VK) Temporary fix. NVIDIA API has a limit of 250KB for the input.
+ # encoded = _resize_image(response.content)
+ # return b64_template.format(b64_string=encoded)
elif image_source.startswith("data:image"):
return image_source
elif os.path.exists(image_source):
with open(image_source, "rb") as f:
- encoded = base64.b64encode(f.read()).decode("utf-8")
- return b64_template.format(b64_string=encoded)
+ image_data = f.read()
+ import imghdr
+
+ image_type = imghdr.what(None, image_data)
+ encoded = base64.b64encode(image_data).decode("utf-8")
+ return f"data:image/{image_type};base64,{encoded}"
else:
raise ValueError(
"The provided string is not a valid URL, base64, or file path."
@@ -127,7 +128,9 @@ def _url_to_b64_string(image_source: str) -> str:
raise ValueError(f"Unable to process the provided image source: {e}")
-def _nv_vlm_adjust_input(message_dict: Dict[str, Any]) -> Dict[str, Any]:
+def _nv_vlm_adjust_input(
+ message_dict: Dict[str, Any], model_type: str
+) -> Dict[str, Any]:
"""
The NVIDIA VLM API input message.content:
{
@@ -170,10 +173,73 @@ def _nv_vlm_adjust_input(message_dict: Dict[str, Any]) -> Dict[str, Any]:
isinstance(part["image_url"], dict)
and "url" in part["image_url"]
):
- part["image_url"] = _url_to_b64_string(part["image_url"]["url"])
+ url = _url_to_b64_string(part["image_url"]["url"])
+ if model_type == "nv-vlm":
+ part["image_url"] = url
+ else:
+ part["image_url"]["url"] = url
return message_dict
+def _nv_vlm_get_asset_ids(
+ content: Union[str, List[Union[str, Dict[str, Any]]]],
+) -> List[str]:
+ """
+ VLM APIs accept asset IDs as input in two forms:
+ - content = [{"image_url": {"url": "data:image/{type};asset_id,{asset_id}"}}*]
+ - content = .*.*
+
+ This function extracts asset IDs from the message content.
+ """
+
+ def extract_asset_id(data: str) -> List[str]:
+ pattern = re.compile(r'data:image/[^;]+;asset_id,([^"\'\s]+)')
+ return pattern.findall(data)
+
+ asset_ids = []
+ if isinstance(content, str):
+ asset_ids.extend(extract_asset_id(content))
+ elif isinstance(content, list):
+ for part in content:
+ if isinstance(part, str):
+ asset_ids.extend(extract_asset_id(part))
+ elif isinstance(part, dict) and "image_url" in part:
+ image_url = part["image_url"]
+ if isinstance(image_url, dict) and "url" in image_url:
+ asset_ids.extend(extract_asset_id(image_url["url"]))
+
+ return asset_ids
+
+
+def _process_for_vlm(
+ inputs: List[Dict[str, Any]],
+ model: Optional[Model], # not optional, Optional for type alignment
+) -> Tuple[List[Dict[str, Any]], Dict[str, str]]:
+ """
+ Process inputs for NVIDIA VLM models.
+
+ This function processes the input messages for NVIDIA VLM models.
+ It extracts asset IDs from the input messages and adds them to the
+ headers for the NVIDIA VLM API.
+ """
+ if not model or not model.model_type:
+ return inputs, {}
+
+ extra_headers = {}
+ if "vlm" in model.model_type:
+ asset_ids = []
+ for input in inputs:
+ if "content" in input:
+ asset_ids.extend(_nv_vlm_get_asset_ids(input["content"]))
+ if asset_ids:
+ extra_headers["NVCF-INPUT-ASSET-REFERENCES"] = ",".join(asset_ids)
+ inputs = [_nv_vlm_adjust_input(message, model.model_type) for message in inputs]
+ return inputs, extra_headers
+
+
+_DEFAULT_MODEL_NAME: str = "meta/llama3-8b-instruct"
+
+
class ChatNVIDIA(BaseChatModel):
"""NVIDIA chat model.
@@ -188,31 +254,20 @@ class ChatNVIDIA(BaseChatModel):
"""
_client: _NVIDIAClient = PrivateAttr(_NVIDIAClient)
- _default_model_name: str = "meta/llama3-8b-instruct"
- _default_base_url: str = "https://integrate.api.nvidia.com/v1"
- base_url: str = Field(
+ base_url: Optional[str] = Field(
+ default=None,
description="Base url for model listing an invocation",
)
- model: Optional[str] = Field(description="Name of the model to invoke")
- temperature: Optional[float] = Field(description="Sampling temperature in [0, 1]")
+ model: Optional[str] = Field(None, description="Name of the model to invoke")
+ temperature: Optional[float] = Field(
+ None, description="Sampling temperature in [0, 1]"
+ )
max_tokens: Optional[int] = Field(
1024, description="Maximum # of tokens to generate"
)
- top_p: Optional[float] = Field(description="Top-p for distribution sampling")
- seed: Optional[int] = Field(description="The seed for deterministic results")
- stop: Optional[Sequence[str]] = Field(description="Stop words (cased)")
-
- _base_url_var = "NVIDIA_BASE_URL"
-
- @root_validator(pre=True)
- def _validate_base_url(cls, values: Dict[str, Any]) -> Dict[str, Any]:
- values["base_url"] = (
- values.get(cls._base_url_var.lower())
- or values.get("base_url")
- or os.getenv(cls._base_url_var)
- or cls._default_base_url
- )
- return values
+ top_p: Optional[float] = Field(None, description="Top-p for distribution sampling")
+ seed: Optional[int] = Field(None, description="The seed for deterministic results")
+ stop: Optional[Sequence[str]] = Field(None, description="Stop words (cased)")
def __init__(self, **kwargs: Any):
"""
@@ -248,17 +303,23 @@ def __init__(self, **kwargs: Any):
)
"""
super().__init__(**kwargs)
+ # allow nvidia_base_url as an alternative for base_url
+ base_url = kwargs.pop("nvidia_base_url", self.base_url)
+ # allow nvidia_api_key as an alternative for api_key
+ api_key = kwargs.pop("nvidia_api_key", kwargs.pop("api_key", None))
self._client = _NVIDIAClient(
- base_url=self.base_url,
- model_name=self.model,
- default_hosted_model_name=self._default_model_name,
- api_key=kwargs.get("nvidia_api_key", kwargs.get("api_key", None)),
+ **({"base_url": base_url} if base_url else {}), # only pass if set
+ mdl_name=self.model,
+ default_hosted_model_name=_DEFAULT_MODEL_NAME,
+ **({"api_key": api_key} if api_key else {}), # only pass if set
infer_path="{base_url}/chat/completions",
cls=self.__class__.__name__,
)
# todo: only store the model in one place
# the model may be updated to a newer name during initialization
- self.model = self._client.model_name
+ self.model = self._client.mdl_name
+ # same for base_url
+ self.base_url = self._client.base_url
@property
def available_models(self) -> List[Model]:
@@ -282,6 +343,28 @@ def _llm_type(self) -> str:
"""Return type of NVIDIA AI Foundation Model Interface."""
return "chat-nvidia-ai-playground"
+ def _get_ls_params(
+ self,
+ stop: Optional[List[str]] = None,
+ **kwargs: Any,
+ ) -> LangSmithParams:
+ """Get standard LangSmith parameters for tracing."""
+ params = self._get_invocation_params(stop=stop, **kwargs)
+ return LangSmithParams(
+ ls_provider="NVIDIA",
+ # error: Incompatible types (expression has type "Optional[str]",
+ # TypedDict item "ls_model_name" has type "str") [typeddict-item]
+ ls_model_name=self.model or "UNKNOWN",
+ ls_model_type="chat",
+ ls_temperature=params.get("temperature", self.temperature),
+ ls_max_tokens=params.get("max_tokens", self.max_tokens),
+ # mypy error: Extra keys ("ls_top_p", "ls_seed")
+ # for TypedDict "LangSmithParams" [typeddict-item]
+ # ls_top_p=params.get("top_p", self.top_p),
+ # ls_seed=params.get("seed", self.seed),
+ ls_stop=params.get("stop", self.stop),
+ )
+
def _generate(
self,
messages: List[BaseMessage],
@@ -290,11 +373,12 @@ def _generate(
**kwargs: Any,
) -> ChatResult:
inputs = [
- _nv_vlm_adjust_input(message)
+ message
for message in [convert_message_to_dict(message) for message in messages]
]
+ inputs, extra_headers = _process_for_vlm(inputs, self._client.model)
payload = self._get_payload(inputs=inputs, stop=stop, stream=False, **kwargs)
- response = self._client.get_req(payload=payload)
+ response = self._client.get_req(payload=payload, extra_headers=extra_headers)
responses, _ = self._client.postprocess(response)
self._set_callback_out(responses, run_manager)
parsed_response = self._custom_postprocess(responses, streaming=False)
@@ -313,11 +397,28 @@ def _stream(
) -> Iterator[ChatGenerationChunk]:
"""Allows streaming to model!"""
inputs = [
- _nv_vlm_adjust_input(message)
+ message
for message in [convert_message_to_dict(message) for message in messages]
]
- payload = self._get_payload(inputs=inputs, stop=stop, stream=True, **kwargs)
- for response in self._client.get_req_stream(payload=payload):
+ inputs, extra_headers = _process_for_vlm(inputs, self._client.model)
+ payload = self._get_payload(
+ inputs=inputs,
+ stop=stop,
+ stream=True,
+ stream_options={"include_usage": True},
+ **kwargs,
+ )
+ # todo: get vlm endpoints fixed and remove this
+ # vlm endpoints do not accept standard stream_options parameter
+ if (
+ self._client.model
+ and self._client.model.model_type
+ and self._client.model.model_type == "nv-vlm"
+ ):
+ payload.pop("stream_options")
+ for response in self._client.get_req_stream(
+ payload=payload, extra_headers=extra_headers
+ ):
self._set_callback_out(response, run_manager)
parsed_response = self._custom_postprocess(response, streaming=True)
# for pre 0.2 compatibility w/ ChatMessageChunk
@@ -354,6 +455,12 @@ def _custom_postprocess(
"additional_kwargs": {},
"response_metadata": {},
}
+ if token_usage := kw_left.pop("token_usage", None):
+ out_dict["usage_metadata"] = {
+ "input_tokens": token_usage.get("prompt_tokens", 0),
+ "output_tokens": token_usage.get("completion_tokens", 0),
+ "total_tokens": token_usage.get("total_tokens", 0),
+ }
# "tool_calls" is set for invoke and stream responses
if tool_calls := kw_left.pop("tool_calls", None):
assert isinstance(
@@ -447,7 +554,7 @@ def _get_payload(
def bind_tools(
self,
- tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]],
+ tools: Sequence[Union[Dict[str, Any], Type, Callable, BaseTool]],
*,
tool_choice: Optional[
Union[dict, str, Literal["auto", "none", "any", "required"], bool]
@@ -533,11 +640,11 @@ def bind_functions(
# as a result need to type ignore for the schema parameter and return type.
def with_structured_output( # type: ignore
self,
- schema: _DictOrPydanticOrEnumClass,
+ schema: Union[Dict, Type],
*,
include_raw: bool = False,
**kwargs: Any,
- ) -> Runnable[LanguageModelInput, _DictOrPydanticOrEnum]:
+ ) -> Runnable[LanguageModelInput, Union[Dict, BaseModel]]:
"""
Bind a structured output schema to the model.
@@ -574,7 +681,7 @@ def with_structured_output( # type: ignore
1. If a Pydantic schema is provided, the model will return a Pydantic object.
Example:
```
- from langchain_core.pydantic_v1 import BaseModel, Field
+ from pydantic import BaseModel, Field
class Joke(BaseModel):
setup: str = Field(description="The setup of the joke")
punchline: str = Field(description="The punchline to the joke")
@@ -732,7 +839,11 @@ def parse_result(
return None
output_parser = ForgivingPydanticOutputParser(pydantic_object=schema)
- nvext_param = {"guided_json": schema.schema()}
+ if hasattr(schema, "model_json_schema"):
+ json_schema = schema.model_json_schema()
+ else:
+ json_schema = schema.schema()
+ nvext_param = {"guided_json": json_schema}
else:
raise ValueError(
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/embeddings.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/embeddings.py
index 02f3715a..dc29d39c 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/embeddings.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/embeddings.py
@@ -1,22 +1,24 @@
"""Embeddings Components Derived from NVEModel/Embeddings"""
-import os
import warnings
-from typing import Any, Dict, List, Literal, Optional
+from typing import Any, List, Literal, Optional
from langchain_core.embeddings import Embeddings
from langchain_core.outputs.llm_result import LLMResult
-from langchain_core.pydantic_v1 import (
+from pydantic import (
BaseModel,
+ ConfigDict,
Field,
PrivateAttr,
- root_validator,
)
from langchain_nvidia_ai_endpoints._common import _NVIDIAClient
from langchain_nvidia_ai_endpoints._statics import Model
from langchain_nvidia_ai_endpoints.callbacks import usage_callback_var
+_DEFAULT_MODEL_NAME: str = "nvidia/nv-embedqa-e5-v5"
+_DEFAULT_BATCH_SIZE: int = 50
+
class NVIDIAEmbeddings(BaseModel, Embeddings):
"""
@@ -29,17 +31,16 @@ class NVIDIAEmbeddings(BaseModel, Embeddings):
too long.
"""
- class Config:
- validate_assignment = True
+ model_config = ConfigDict(
+ validate_assignment=True,
+ )
_client: _NVIDIAClient = PrivateAttr(_NVIDIAClient)
- _default_model_name: str = "nvidia/nv-embedqa-e5-v5"
- _default_max_batch_size: int = 50
- _default_base_url: str = "https://integrate.api.nvidia.com/v1"
- base_url: str = Field(
+ base_url: Optional[str] = Field(
+ default=None,
description="Base url for model listing an invocation",
)
- model: Optional[str] = Field(description="Name of the model to invoke")
+ model: Optional[str] = Field(None, description="Name of the model to invoke")
truncate: Literal["NONE", "START", "END"] = Field(
default="NONE",
description=(
@@ -47,19 +48,7 @@ class Config:
"Default is 'NONE', which raises an error if an input is too long."
),
)
- max_batch_size: int = Field(default=_default_max_batch_size)
-
- _base_url_var = "NVIDIA_BASE_URL"
-
- @root_validator(pre=True)
- def _validate_base_url(cls, values: Dict[str, Any]) -> Dict[str, Any]:
- values["base_url"] = (
- values.get(cls._base_url_var.lower())
- or values.get("base_url")
- or os.getenv(cls._base_url_var)
- or cls._default_base_url
- )
- return values
+ max_batch_size: int = Field(default=_DEFAULT_BATCH_SIZE)
def __init__(self, **kwargs: Any):
"""
@@ -90,17 +79,23 @@ def __init__(self, **kwargs: Any):
embedder = NVIDIAEmbeddings(base_url="http://localhost:8080/v1")
"""
super().__init__(**kwargs)
+ # allow nvidia_base_url as an alternative for base_url
+ base_url = kwargs.pop("nvidia_base_url", self.base_url)
+ # allow nvidia_api_key as an alternative for api_key
+ api_key = kwargs.pop("nvidia_api_key", kwargs.pop("api_key", None))
self._client = _NVIDIAClient(
- base_url=self.base_url,
- model_name=self.model,
- default_hosted_model_name=self._default_model_name,
- api_key=kwargs.get("nvidia_api_key", kwargs.get("api_key", None)),
+ **({"base_url": base_url} if base_url else {}), # only pass if set
+ mdl_name=self.model,
+ default_hosted_model_name=_DEFAULT_MODEL_NAME,
+ **({"api_key": api_key} if api_key else {}), # only pass if set
infer_path="{base_url}/embeddings",
cls=self.__class__.__name__,
)
# todo: only store the model in one place
# the model may be updated to a newer name during initialization
- self.model = self._client.model_name
+ self.model = self._client.mdl_name
+ # same for base_url
+ self.base_url = self._client.base_url
# todo: remove when nvolveqa_40k is removed from MODEL_TABLE
if "model" in kwargs and kwargs["model"] in [
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/llm.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/llm.py
index 12f364a5..942e610d 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/llm.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/llm.py
@@ -1,50 +1,40 @@
from __future__ import annotations
-import os
import warnings
from typing import Any, Dict, Iterator, List, Optional
from langchain_core.callbacks.manager import CallbackManagerForLLMRun
from langchain_core.language_models.llms import LLM
from langchain_core.outputs import GenerationChunk
-from langchain_core.pydantic_v1 import Field, PrivateAttr, root_validator
+from pydantic import ConfigDict, Field, PrivateAttr
from langchain_nvidia_ai_endpoints._common import _NVIDIAClient
from langchain_nvidia_ai_endpoints._statics import Model
+_DEFAULT_MODEL_NAME: str = "nvidia/mistral-nemo-minitron-8b-base"
+
class NVIDIA(LLM):
"""
LangChain LLM that uses the Completions API with NVIDIA NIMs.
"""
- class Config:
- validate_assignment = True
+ model_config = ConfigDict(
+ validate_assignment=True,
+ )
_client: _NVIDIAClient = PrivateAttr(_NVIDIAClient)
_default_model_name: str = "nvidia/mistral-nemo-minitron-8b-base"
- _default_base_url: str = "https://integrate.api.nvidia.com/v1"
- base_url: str = Field(
+ base_url: Optional[str] = Field(
+ default=None,
description="Base url for model listing and invocation",
)
- model: Optional[str] = Field(description="The model to use for completions.")
-
- _base_url_var = "NVIDIA_BASE_URL"
+ model: Optional[str] = Field(None, description="The model to use for completions.")
_init_args: Dict[str, Any] = PrivateAttr()
"""Stashed arguments given to the constructor that can be passed to
the Completions API endpoint."""
- @root_validator(pre=True)
- def _validate_base_url(cls, values: Dict[str, Any]) -> Dict[str, Any]:
- values["base_url"] = (
- values.get(cls._base_url_var.lower())
- or values.get("base_url")
- or os.getenv(cls._base_url_var)
- or cls._default_base_url
- )
- return values
-
def __check_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
"""
Check kwargs, warn for unknown keys, and return a copy recognized keys.
@@ -109,17 +99,23 @@ def __init__(self, **kwargs: Any):
e.g. `NVIDIA().invoke("prompt", max_tokens=512)`.
"""
super().__init__(**kwargs)
+ # allow nvidia_base_url as an alternative for base_url
+ base_url = kwargs.pop("nvidia_base_url", self.base_url)
+ # allow nvidia_api_key as an alternative for api_key
+ api_key = kwargs.pop("nvidia_api_key", kwargs.pop("api_key", None))
self._client = _NVIDIAClient(
- base_url=self.base_url,
- model_name=self.model,
- default_hosted_model_name=self._default_model_name,
- api_key=kwargs.pop("nvidia_api_key", kwargs.pop("api_key", None)),
+ **({"base_url": base_url} if base_url else {}), # only pass if set
+ mdl_name=self.model,
+ default_hosted_model_name=_DEFAULT_MODEL_NAME,
+ **({"api_key": api_key} if api_key else {}), # only pass if set
infer_path="{base_url}/completions",
cls=self.__class__.__name__,
)
# todo: only store the model in one place
# the model may be updated to a newer name during initialization
- self.model = self._client.model_name
+ self.model = self._client.mdl_name
+ # same for base_url
+ self.base_url = self._client.base_url
# stash all additional args that can be passed to the Completions API,
# but first make sure we pull out any args that are processed elsewhere.
diff --git a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/reranking.py b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/reranking.py
index d1caa69f..d64f8d84 100644
--- a/libs/ai-endpoints/langchain_nvidia_ai_endpoints/reranking.py
+++ b/libs/ai-endpoints/langchain_nvidia_ai_endpoints/reranking.py
@@ -1,12 +1,16 @@
from __future__ import annotations
-import os
-from typing import Any, Dict, Generator, List, Literal, Optional, Sequence
+from typing import Any, Generator, List, Literal, Optional, Sequence
from langchain_core.callbacks.manager import Callbacks
from langchain_core.documents import Document
from langchain_core.documents.compressor import BaseDocumentCompressor
-from langchain_core.pydantic_v1 import BaseModel, Field, PrivateAttr, root_validator
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+ PrivateAttr,
+)
from langchain_nvidia_ai_endpoints._common import _NVIDIAClient
from langchain_nvidia_ai_endpoints._statics import Model
@@ -17,25 +21,29 @@ class Ranking(BaseModel):
logit: float
+_DEFAULT_MODEL_NAME: str = "nvidia/nv-rerankqa-mistral-4b-v3"
+_DEFAULT_BATCH_SIZE: int = 32
+
+
class NVIDIARerank(BaseDocumentCompressor):
"""
LangChain Document Compressor that uses the NVIDIA NeMo Retriever Reranking API.
"""
- class Config:
- validate_assignment = True
+ model_config = ConfigDict(
+ validate_assignment=True,
+ )
_client: _NVIDIAClient = PrivateAttr(_NVIDIAClient)
- _default_batch_size: int = 32
- _default_model_name: str = "nvidia/nv-rerankqa-mistral-4b-v3"
- _default_base_url: str = "https://integrate.api.nvidia.com/v1"
- base_url: str = Field(
+ base_url: Optional[str] = Field(
+ default=None,
description="Base url for model listing an invocation",
)
top_n: int = Field(5, ge=0, description="The number of documents to return.")
- model: Optional[str] = Field(description="The model to use for reranking.")
+ model: Optional[str] = Field(None, description="The model to use for reranking.")
truncate: Optional[Literal["NONE", "END"]] = Field(
+ default=None,
description=(
"Truncate input text if it exceeds the model's maximum token length. "
"Default is model dependent and is likely to raise error if an "
@@ -43,21 +51,9 @@ class Config:
),
)
max_batch_size: int = Field(
- _default_batch_size, ge=1, description="The maximum batch size."
+ _DEFAULT_BATCH_SIZE, ge=1, description="The maximum batch size."
)
- _base_url_var = "NVIDIA_BASE_URL"
-
- @root_validator(pre=True)
- def _validate_base_url(cls, values: Dict[str, Any]) -> Dict[str, Any]:
- values["base_url"] = (
- values.get(cls._base_url_var.lower())
- or values.get("base_url")
- or os.getenv(cls._base_url_var)
- or cls._default_base_url
- )
- return values
-
def __init__(self, **kwargs: Any):
"""
Create a new NVIDIARerank document compressor.
@@ -134,17 +130,23 @@ def __init__(self, **kwargs: Any):
"""
super().__init__(**kwargs)
+ # allow nvidia_base_url as an alternative for base_url
+ base_url = kwargs.pop("nvidia_base_url", self.base_url)
+ # allow nvidia_api_key as an alternative for api_key
+ api_key = kwargs.pop("nvidia_api_key", kwargs.pop("api_key", None))
self._client = _NVIDIAClient(
- base_url=self.base_url,
- model_name=self.model,
- default_hosted_model_name=self._default_model_name,
- api_key=kwargs.get("nvidia_api_key", kwargs.get("api_key", None)),
+ **({"base_url": base_url} if base_url else {}), # only pass if set
+ mdl_name=self.model,
+ default_hosted_model_name=_DEFAULT_MODEL_NAME,
+ **({"api_key": api_key} if api_key else {}), # only pass if set
infer_path="{base_url}/ranking",
cls=self.__class__.__name__,
)
# todo: only store the model in one place
# the model may be updated to a newer name during initialization
- self.model = self._client.model_name
+ self.model = self._client.mdl_name
+ # same for base_url
+ self.base_url = self._client.base_url
@property
def available_models(self) -> List[Model]:
diff --git a/libs/ai-endpoints/poetry.lock b/libs/ai-endpoints/poetry.lock
index 483fb260..a01a1932 100644
--- a/libs/ai-endpoints/poetry.lock
+++ b/libs/ai-endpoints/poetry.lock
@@ -148,18 +148,15 @@ files = [
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
]
-[package.dependencies]
-typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.9\""}
-
[[package]]
name = "anyio"
-version = "4.4.0"
+version = "4.5.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
- {file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
- {file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
+ {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"},
+ {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"},
]
[package.dependencies]
@@ -169,9 +166,9 @@ sniffio = ">=1.1"
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
-doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
-test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
-trio = ["trio (>=0.23)"]
+doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"]
+trio = ["trio (>=0.26.1)"]
[[package]]
name = "async-timeout"
@@ -368,7 +365,6 @@ files = [
[package.dependencies]
python-dateutil = ">=2.4"
-typing-extensions = {version = ">=3.10.0.1", markers = "python_version <= \"3.8\""}
[[package]]
name = "freezegun"
@@ -579,19 +575,19 @@ files = [
[[package]]
name = "langchain-core"
-version = "0.2.40"
+version = "0.3.1"
description = "Building applications with LLMs through composability"
optional = false
-python-versions = ">=3.8.1,<4.0"
+python-versions = ">=3.9,<4.0"
files = []
develop = false
[package.dependencies]
jsonpatch = "^1.33"
-langsmith = "^0.1.112"
+langsmith = "^0.1.117"
packaging = ">=23.2,<25"
pydantic = [
- {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""},
+ {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""},
{version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""},
]
PyYAML = ">=5.3"
@@ -601,19 +597,41 @@ typing-extensions = ">=4.7"
[package.source]
type = "git"
url = "https://github.com/langchain-ai/langchain.git"
-reference = "langchain-core==0.2.40"
-resolved_reference = "0f2b32ffa96358192e011ee2f8db579a323ed0ce"
+reference = "HEAD"
+resolved_reference = "eef18dec442eabb2c2532bd67cc2efa12a43d406"
subdirectory = "libs/core"
+[[package]]
+name = "langchain-standard-tests"
+version = "0.1.1"
+description = "Standard tests for LangChain implementations"
+optional = false
+python-versions = ">=3.9,<4.0"
+files = []
+develop = false
+
+[package.dependencies]
+httpx = "^0.27.0"
+langchain-core = "^0.3.0"
+pytest = ">=7,<9"
+syrupy = "^4"
+
+[package.source]
+type = "git"
+url = "https://github.com/langchain-ai/langchain.git"
+reference = "HEAD"
+resolved_reference = "eef18dec442eabb2c2532bd67cc2efa12a43d406"
+subdirectory = "libs/standard-tests"
+
[[package]]
name = "langsmith"
-version = "0.1.121"
+version = "0.1.123"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = "<4.0,>=3.8.1"
files = [
- {file = "langsmith-0.1.121-py3-none-any.whl", hash = "sha256:fdb1ac8a671d3904201bfeea197d87bded46a10d08f1034af464211872e29893"},
- {file = "langsmith-0.1.121.tar.gz", hash = "sha256:e9381b82a5bd484af9a51c3e96faea572746b8d617b070c1cda40cbbe48e33df"},
+ {file = "langsmith-0.1.123-py3-none-any.whl", hash = "sha256:ee30c96e69038af92487c6229870b9ccc1fba43eb1b84fb4132a013af7212c6e"},
+ {file = "langsmith-0.1.123.tar.gz", hash = "sha256:5d4ad7bb57351f0fc492debf2d7d0b96f2eed41b5545cd36f3043c5f4d42aa6b"},
]
[package.dependencies]
@@ -981,18 +999,18 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pydantic"
-version = "2.9.1"
+version = "2.9.2"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612"},
- {file = "pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2"},
+ {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
+ {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.23.3"
+pydantic-core = "2.23.4"
typing-extensions = [
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
@@ -1004,100 +1022,100 @@ timezone = ["tzdata"]
[[package]]
name = "pydantic-core"
-version = "2.23.3"
+version = "2.23.4"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.8"
files = [
- {file = "pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6"},
- {file = "pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec"},
- {file = "pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba"},
- {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee"},
- {file = "pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe"},
- {file = "pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b"},
- {file = "pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83"},
- {file = "pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27"},
- {file = "pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8"},
- {file = "pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8"},
- {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48"},
- {file = "pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5"},
- {file = "pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1"},
- {file = "pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa"},
- {file = "pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305"},
- {file = "pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326"},
- {file = "pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c"},
- {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c"},
- {file = "pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab"},
- {file = "pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c"},
- {file = "pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b"},
- {file = "pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f"},
- {file = "pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5"},
- {file = "pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855"},
- {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4"},
- {file = "pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d"},
- {file = "pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8"},
- {file = "pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1"},
- {file = "pydantic_core-2.23.3-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c"},
- {file = "pydantic_core-2.23.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e"},
- {file = "pydantic_core-2.23.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295"},
- {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba"},
- {file = "pydantic_core-2.23.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e"},
- {file = "pydantic_core-2.23.3-cp38-none-win32.whl", hash = "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710"},
- {file = "pydantic_core-2.23.3-cp38-none-win_amd64.whl", hash = "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea"},
- {file = "pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8"},
- {file = "pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a"},
- {file = "pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd"},
- {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835"},
- {file = "pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70"},
- {file = "pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7"},
- {file = "pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4"},
- {file = "pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25"},
- {file = "pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab"},
- {file = "pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
+ {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
+ {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
+ {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
+ {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
+ {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
+ {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
+ {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
+ {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
+ {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
+ {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
+ {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
+ {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
+ {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
+ {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
+ {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
+ {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
+ {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
+ {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
+ {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
+ {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
+ {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
]
[package.dependencies]
@@ -1432,46 +1450,41 @@ zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "watchdog"
-version = "4.0.2"
+version = "5.0.2"
description = "Filesystem events monitoring"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ede7f010f2239b97cc79e6cb3c249e72962404ae3865860855d5cbe708b0fd22"},
- {file = "watchdog-4.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2cffa171445b0efa0726c561eca9a27d00a1f2b83846dbd5a4f639c4f8ca8e1"},
- {file = "watchdog-4.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c50f148b31b03fbadd6d0b5980e38b558046b127dc483e5e4505fcef250f9503"},
- {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c7d4bf585ad501c5f6c980e7be9c4f15604c7cc150e942d82083b31a7548930"},
- {file = "watchdog-4.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:914285126ad0b6eb2258bbbcb7b288d9dfd655ae88fa28945be05a7b475a800b"},
- {file = "watchdog-4.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:984306dc4720da5498b16fc037b36ac443816125a3705dfde4fd90652d8028ef"},
- {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1cdcfd8142f604630deef34722d695fb455d04ab7cfe9963055df1fc69e6727a"},
- {file = "watchdog-4.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7ab624ff2f663f98cd03c8b7eedc09375a911794dfea6bf2a359fcc266bff29"},
- {file = "watchdog-4.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132937547a716027bd5714383dfc40dc66c26769f1ce8a72a859d6a48f371f3a"},
- {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:cd67c7df93eb58f360c43802acc945fa8da70c675b6fa37a241e17ca698ca49b"},
- {file = "watchdog-4.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcfd02377be80ef3b6bc4ce481ef3959640458d6feaae0bd43dd90a43da90a7d"},
- {file = "watchdog-4.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:980b71510f59c884d684b3663d46e7a14b457c9611c481e5cef08f4dd022eed7"},
- {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:aa160781cafff2719b663c8a506156e9289d111d80f3387cf3af49cedee1f040"},
- {file = "watchdog-4.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f6ee8dedd255087bc7fe82adf046f0b75479b989185fb0bdf9a98b612170eac7"},
- {file = "watchdog-4.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0b4359067d30d5b864e09c8597b112fe0a0a59321a0f331498b013fb097406b4"},
- {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:770eef5372f146997638d737c9a3c597a3b41037cfbc5c41538fc27c09c3a3f9"},
- {file = "watchdog-4.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:eeea812f38536a0aa859972d50c76e37f4456474b02bd93674d1947cf1e39578"},
- {file = "watchdog-4.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b2c45f6e1e57ebb4687690c05bc3a2c1fb6ab260550c4290b8abb1335e0fd08b"},
- {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:10b6683df70d340ac3279eff0b2766813f00f35a1d37515d2c99959ada8f05fa"},
- {file = "watchdog-4.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:f7c739888c20f99824f7aa9d31ac8a97353e22d0c0e54703a547a218f6637eb3"},
- {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:c100d09ac72a8a08ddbf0629ddfa0b8ee41740f9051429baa8e31bb903ad7508"},
- {file = "watchdog-4.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:f5315a8c8dd6dd9425b974515081fc0aadca1d1d61e078d2246509fd756141ee"},
- {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d468028a77b42cc685ed694a7a550a8d1771bb05193ba7b24006b8241a571a1"},
- {file = "watchdog-4.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f15edcae3830ff20e55d1f4e743e92970c847bcddc8b7509bcd172aa04de506e"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:936acba76d636f70db8f3c66e76aa6cb5136a936fc2a5088b9ce1c7a3508fc83"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:e252f8ca942a870f38cf785aef420285431311652d871409a64e2a0a52a2174c"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:0e83619a2d5d436a7e58a1aea957a3c1ccbf9782c43c0b4fed80580e5e4acd1a"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:88456d65f207b39f1981bf772e473799fcdc10801062c36fd5ad9f9d1d463a73"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:32be97f3b75693a93c683787a87a0dc8db98bb84701539954eef991fb35f5fbc"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:c82253cfc9be68e3e49282831afad2c1f6593af80c0daf1287f6a92657986757"},
- {file = "watchdog-4.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c0b14488bd336c5b1845cee83d3e631a1f8b4e9c5091ec539406e4a324f882d8"},
- {file = "watchdog-4.0.2-py3-none-win32.whl", hash = "sha256:0d8a7e523ef03757a5aa29f591437d64d0d894635f8a50f370fe37f913ce4e19"},
- {file = "watchdog-4.0.2-py3-none-win_amd64.whl", hash = "sha256:c344453ef3bf875a535b0488e3ad28e341adbd5a9ffb0f7d62cefacc8824ef2b"},
- {file = "watchdog-4.0.2-py3-none-win_ia64.whl", hash = "sha256:baececaa8edff42cd16558a639a9b0ddf425f93d892e8392a56bf904f5eff22c"},
- {file = "watchdog-4.0.2.tar.gz", hash = "sha256:b4dfbb6c49221be4535623ea4474a4d6ee0a9cef4a80b20c28db4d858b64e270"},
+ {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d961f4123bb3c447d9fcdcb67e1530c366f10ab3a0c7d1c0c9943050936d4877"},
+ {file = "watchdog-5.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72990192cb63872c47d5e5fefe230a401b87fd59d257ee577d61c9e5564c62e5"},
+ {file = "watchdog-5.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6bec703ad90b35a848e05e1b40bf0050da7ca28ead7ac4be724ae5ac2653a1a0"},
+ {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dae7a1879918f6544201d33666909b040a46421054a50e0f773e0d870ed7438d"},
+ {file = "watchdog-5.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c4a440f725f3b99133de610bfec93d570b13826f89616377715b9cd60424db6e"},
+ {file = "watchdog-5.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8b2918c19e0d48f5f20df458c84692e2a054f02d9df25e6c3c930063eca64c1"},
+ {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:aa9cd6e24126d4afb3752a3e70fce39f92d0e1a58a236ddf6ee823ff7dba28ee"},
+ {file = "watchdog-5.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f627c5bf5759fdd90195b0c0431f99cff4867d212a67b384442c51136a098ed7"},
+ {file = "watchdog-5.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d7594a6d32cda2b49df3fd9abf9b37c8d2f3eab5df45c24056b4a671ac661619"},
+ {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba32efcccfe2c58f4d01115440d1672b4eb26cdd6fc5b5818f1fb41f7c3e1889"},
+ {file = "watchdog-5.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:963f7c4c91e3f51c998eeff1b3fb24a52a8a34da4f956e470f4b068bb47b78ee"},
+ {file = "watchdog-5.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8c47150aa12f775e22efff1eee9f0f6beee542a7aa1a985c271b1997d340184f"},
+ {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14dd4ed023d79d1f670aa659f449bcd2733c33a35c8ffd88689d9d243885198b"},
+ {file = "watchdog-5.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b84bff0391ad4abe25c2740c7aec0e3de316fdf7764007f41e248422a7760a7f"},
+ {file = "watchdog-5.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e8d5ff39f0a9968952cce548e8e08f849141a4fcc1290b1c17c032ba697b9d7"},
+ {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:fb223456db6e5f7bd9bbd5cd969f05aae82ae21acc00643b60d81c770abd402b"},
+ {file = "watchdog-5.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9814adb768c23727a27792c77812cf4e2fd9853cd280eafa2bcfa62a99e8bd6e"},
+ {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:901ee48c23f70193d1a7bc2d9ee297df66081dd5f46f0ca011be4f70dec80dab"},
+ {file = "watchdog-5.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:638bcca3d5b1885c6ec47be67bf712b00a9ab3d4b22ec0881f4889ad870bc7e8"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5597c051587f8757798216f2485e85eac583c3b343e9aa09127a3a6f82c65ee8"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_armv7l.whl", hash = "sha256:53ed1bf71fcb8475dd0ef4912ab139c294c87b903724b6f4a8bd98e026862e6d"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_i686.whl", hash = "sha256:29e4a2607bd407d9552c502d38b45a05ec26a8e40cc7e94db9bb48f861fa5abc"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64.whl", hash = "sha256:b6dc8f1d770a8280997e4beae7b9a75a33b268c59e033e72c8a10990097e5fde"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:d2ab34adc9bf1489452965cdb16a924e97d4452fcf88a50b21859068b50b5c3b"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_s390x.whl", hash = "sha256:7d1aa7e4bb0f0c65a1a91ba37c10e19dabf7eaaa282c5787e51371f090748f4b"},
+ {file = "watchdog-5.0.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:726eef8f8c634ac6584f86c9c53353a010d9f311f6c15a034f3800a7a891d941"},
+ {file = "watchdog-5.0.2-py3-none-win32.whl", hash = "sha256:bda40c57115684d0216556671875e008279dea2dc00fcd3dde126ac8e0d7a2fb"},
+ {file = "watchdog-5.0.2-py3-none-win_amd64.whl", hash = "sha256:d010be060c996db725fbce7e3ef14687cdcc76f4ca0e4339a68cc4532c382a73"},
+ {file = "watchdog-5.0.2-py3-none-win_ia64.whl", hash = "sha256:3960136b2b619510569b90f0cd96408591d6c251a75c97690f4553ca88889769"},
+ {file = "watchdog-5.0.2.tar.gz", hash = "sha256:dcebf7e475001d2cdeb020be630dc5b687e9acdd60d16fea6bb4508e7b94cf76"},
]
[package.extras]
@@ -1584,5 +1597,5 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
-python-versions = ">=3.8.1,<4.0"
-content-hash = "b6b18aa680d8841dd68ccbdf6b821fda271ac0e9160fbccf22c4cebc69fbb668"
+python-versions = ">=3.9,<4.0"
+content-hash = "7a08e2786a8b70e328e92fbf014c8910fb64c0c55476c0dd050d61ffa636a4e5"
diff --git a/libs/ai-endpoints/pyproject.toml b/libs/ai-endpoints/pyproject.toml
index 23f72644..94e0befe 100644
--- a/libs/ai-endpoints/pyproject.toml
+++ b/libs/ai-endpoints/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langchain-nvidia-ai-endpoints"
-version = "0.2.2"
+version = "0.3.0"
description = "An integration package connecting NVIDIA AI Endpoints and LangChain"
authors = []
readme = "README.md"
@@ -11,8 +11,8 @@ license = "MIT"
"Source Code" = "https://github.com/langchain-ai/langchain-nvidia/tree/main/libs/ai-endpoints"
[tool.poetry.dependencies]
-python = ">=3.8.1,<4.0"
-langchain-core = ">=0.2.22,<0.3"
+python = ">=3.9,<4.0"
+langchain-core = ">=0.3.0,<0.4"
aiohttp = "^3.9.1"
pillow = ">=10.0.0,<11.0.0"
@@ -26,8 +26,9 @@ pytest-mock = "^3.10.0"
syrupy = "^4.0.2"
pytest-watcher = "^0.3.4"
pytest-asyncio = "^0.21.1"
-langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core", tag = "langchain-core==0.2.40" }
+langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" }
requests-mock = "^1.11.0"
+langchain-standard-tests = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/standard-tests" }
faker = "^24.4.0"
[tool.poetry.group.codespell]
@@ -52,13 +53,13 @@ ruff = "^0.1.5"
mypy = "^0.991"
types-requests = "^2.31.0.10"
types-pillow = "^10.2.0.20240125"
-langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core", tag = "langchain-core==0.2.40" }
+langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" }
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
-langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core", tag = "langchain-core==0.2.40" }
+langchain-core = { git = "https://github.com/langchain-ai/langchain.git", subdirectory = "libs/core" }
[tool.ruff.lint]
select = [
diff --git a/libs/ai-endpoints/scripts/check_pydantic.sh b/libs/ai-endpoints/scripts/check_pydantic.sh
deleted file mode 100755
index d0fa31d6..00000000
--- a/libs/ai-endpoints/scripts/check_pydantic.sh
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/bin/bash
-#
-# This script searches for lines starting with "import pydantic" or "from pydantic"
-# in tracked files within a Git repository.
-#
-# Usage: ./scripts/check_pydantic.sh /path/to/repository
-
-# Check if a path argument is provided
-if [ $# -ne 1 ]; then
- echo "Usage: $0 /path/to/repository"
- exit 1
-fi
-
-repository_path="$1"
-
-# Search for lines matching the pattern within the specified repository
-result=$(git -C "$repository_path" grep -E '^import pydantic|^from pydantic' | grep -v "# ignore: check_pydantic")
-
-# Check if any matching lines were found
-if [ -n "$result" ]; then
- echo "ERROR: The following lines need to be updated:"
- echo "$result"
- echo "Please replace the code with an import from langchain_core.pydantic_v1."
- echo "For example, replace 'from pydantic import BaseModel'"
- echo "with 'from langchain_core.pydantic_v1 import BaseModel'"
- exit 1
-fi
diff --git a/libs/ai-endpoints/tests/data/nvidia-picasso-large.png b/libs/ai-endpoints/tests/data/nvidia-picasso-large.png
new file mode 100644
index 00000000..5ccac1a1
Binary files /dev/null and b/libs/ai-endpoints/tests/data/nvidia-picasso-large.png differ
diff --git a/libs/ai-endpoints/tests/data/nvidia-picasso.gif b/libs/ai-endpoints/tests/data/nvidia-picasso.gif
new file mode 100644
index 00000000..bed37022
Binary files /dev/null and b/libs/ai-endpoints/tests/data/nvidia-picasso.gif differ
diff --git a/libs/ai-endpoints/tests/data/nvidia-picasso.png b/libs/ai-endpoints/tests/data/nvidia-picasso.png
new file mode 100644
index 00000000..45beb713
Binary files /dev/null and b/libs/ai-endpoints/tests/data/nvidia-picasso.png differ
diff --git a/libs/ai-endpoints/tests/data/nvidia-picasso.webp b/libs/ai-endpoints/tests/data/nvidia-picasso.webp
new file mode 100644
index 00000000..db596dc1
Binary files /dev/null and b/libs/ai-endpoints/tests/data/nvidia-picasso.webp differ
diff --git a/libs/ai-endpoints/tests/integration_tests/conftest.py b/libs/ai-endpoints/tests/integration_tests/conftest.py
index 15671c7f..6cebcf67 100644
--- a/libs/ai-endpoints/tests/integration_tests/conftest.py
+++ b/libs/ai-endpoints/tests/integration_tests/conftest.py
@@ -10,6 +10,18 @@
NVIDIARerank,
)
from langchain_nvidia_ai_endpoints._statics import MODEL_TABLE, Model
+from langchain_nvidia_ai_endpoints.chat_models import (
+ _DEFAULT_MODEL_NAME as DEFAULT_CHAT_MODEL,
+)
+from langchain_nvidia_ai_endpoints.embeddings import (
+ _DEFAULT_MODEL_NAME as DEFAULT_EMBEDDINGS_MODEL,
+)
+from langchain_nvidia_ai_endpoints.llm import (
+ _DEFAULT_MODEL_NAME as DEFAULT_COMPLETIONS_MODEL,
+)
+from langchain_nvidia_ai_endpoints.reranking import (
+ _DEFAULT_MODEL_NAME as DEFAULT_RERANKING_MODEL,
+)
def get_mode(config: pytest.Config) -> dict:
@@ -87,7 +99,7 @@ def get_all_known_models() -> List[Model]:
return list(MODEL_TABLE.values())
if "chat_model" in metafunc.fixturenames:
- models = [ChatNVIDIA._default_model_name]
+ models = [DEFAULT_CHAT_MODEL]
if model_list := metafunc.config.getoption("chat_model_id"):
models = model_list
if metafunc.config.getoption("all_models"):
@@ -111,7 +123,7 @@ def get_all_known_models() -> List[Model]:
metafunc.parametrize("tool_model", models, ids=models)
if "completions_model" in metafunc.fixturenames:
- models = [NVIDIA._default_model_name]
+ models = [DEFAULT_COMPLETIONS_MODEL]
if model_list := metafunc.config.getoption("completions_model_id"):
models = model_list
if metafunc.config.getoption("all_models"):
@@ -135,7 +147,7 @@ def get_all_known_models() -> List[Model]:
metafunc.parametrize("structured_model", models, ids=models)
if "rerank_model" in metafunc.fixturenames:
- models = [NVIDIARerank._default_model_name]
+ models = [DEFAULT_RERANKING_MODEL]
if model_list := metafunc.config.getoption("rerank_model_id"):
models = model_list
if metafunc.config.getoption("all_models"):
@@ -150,7 +162,7 @@ def get_all_known_models() -> List[Model]:
models = [
model.id
for model in get_all_known_models()
- if model.model_type == "vlm"
+ if model.model_type in {"vlm", "nv-vlm"}
]
metafunc.parametrize("vlm_model", models, ids=models)
@@ -167,7 +179,7 @@ def get_all_known_models() -> List[Model]:
metafunc.parametrize("qa_model", models, ids=models)
if "embedding_model" in metafunc.fixturenames:
- models = [NVIDIAEmbeddings._default_model_name]
+ models = [DEFAULT_EMBEDDINGS_MODEL]
if metafunc.config.getoption("all_models"):
models = [model.id for model in NVIDIAEmbeddings(**mode).available_models]
if model_list := metafunc.config.getoption("embedding_model_id"):
diff --git a/libs/ai-endpoints/tests/integration_tests/test_bind_tools.py b/libs/ai-endpoints/tests/integration_tests/test_bind_tools.py
index c9a84b69..aaee110a 100644
--- a/libs/ai-endpoints/tests/integration_tests/test_bind_tools.py
+++ b/libs/ai-endpoints/tests/integration_tests/test_bind_tools.py
@@ -1,5 +1,7 @@
import json
import warnings
+from functools import reduce
+from operator import add
from typing import Any, Callable, List, Literal, Optional, Union
import pytest
@@ -9,8 +11,8 @@
BaseMessage,
BaseMessageChunk,
)
-from langchain_core.pydantic_v1 import Field
from langchain_core.tools import tool
+from pydantic import Field
from langchain_nvidia_ai_endpoints import ChatNVIDIA
@@ -736,3 +738,52 @@ def test_accuracy_parallel_tool_calls_easy(
tool_call1 = response.tool_calls[1]
assert tool_call1["name"] == "get_current_weather"
assert tool_call1["args"]["location"] in valid_args
+
+
+@pytest.mark.xfail(reason="Server producing invalid response")
+def test_stream_usage_metadata(
+ tool_model: str,
+ mode: dict,
+) -> None:
+ """
+ This is a regression test for the server. The server was returning
+ usage metadata multiple times resulting in incorrect aggregate
+ usage data.
+
+ We use invoke to get the baseline usage metadata and then compare
+ the usage metadata from the stream to the baseline.
+ """
+
+ @tool
+ def magic(
+ num: int = Field(..., description="Number to magic"),
+ ) -> int:
+ """Magic a number"""
+ return (num**num) % num
+
+ prompt = "What is magic(42)?"
+ llm = ChatNVIDIA(model=tool_model, **mode).bind_tools(
+ [magic], tool_choice="required"
+ )
+ baseline = llm.invoke(prompt)
+ assert isinstance(baseline, AIMessage)
+ assert baseline.usage_metadata is not None
+ baseline_in, baseline_out, baseline_total = (
+ baseline.usage_metadata["input_tokens"],
+ baseline.usage_metadata["output_tokens"],
+ baseline.usage_metadata["total_tokens"],
+ )
+ assert baseline_in + baseline_out == baseline_total
+ response = reduce(add, llm.stream(prompt))
+ assert isinstance(response, AIMessage)
+ assert response.usage_metadata is not None
+ tolerance = 1.25 # allow for streaming to be 25% higher than invoke
+ response_in, response_out, response_total = (
+ response.usage_metadata["input_tokens"],
+ response.usage_metadata["output_tokens"],
+ response.usage_metadata["total_tokens"],
+ )
+ assert response_in + response_out == response_total
+ assert response_in < baseline_in * tolerance
+ assert response_out < baseline_out * tolerance
+ assert response_total < baseline_total * tolerance
diff --git a/libs/ai-endpoints/tests/integration_tests/test_embeddings.py b/libs/ai-endpoints/tests/integration_tests/test_embeddings.py
index 23733e25..998d5ade 100644
--- a/libs/ai-endpoints/tests/integration_tests/test_embeddings.py
+++ b/libs/ai-endpoints/tests/integration_tests/test_embeddings.py
@@ -6,6 +6,7 @@
import pytest
from langchain_nvidia_ai_endpoints import NVIDIAEmbeddings
+from langchain_nvidia_ai_endpoints.embeddings import _DEFAULT_BATCH_SIZE
def test_embed_query(embedding_model: str, mode: dict) -> None:
@@ -62,7 +63,7 @@ def test_embed_query_long_text(embedding_model: str, mode: dict) -> None:
def test_embed_documents_batched_texts(embedding_model: str, mode: dict) -> None:
embedding = NVIDIAEmbeddings(model=embedding_model, **mode)
- count = NVIDIAEmbeddings._default_max_batch_size * 2 + 1
+ count = _DEFAULT_BATCH_SIZE * 2 + 1
texts = ["nvidia " * 32] * count
output = embedding.embed_documents(texts)
assert len(output) == count
@@ -73,7 +74,7 @@ def test_embed_documents_mixed_long_texts(embedding_model: str, mode: dict) -> N
if embedding_model in ["playground_nvolveqa_40k", "nvolveqa_40k"]:
pytest.skip("Skip test for nvolveqa-40k due to compat override of truncate")
embedding = NVIDIAEmbeddings(model=embedding_model, **mode)
- count = NVIDIAEmbeddings._default_max_batch_size * 2 - 1
+ count = _DEFAULT_BATCH_SIZE * 2 - 1
texts = ["nvidia " * 32] * count
texts[len(texts) // 2] = "nvidia " * 10240
with pytest.raises(Exception):
diff --git a/libs/ai-endpoints/tests/integration_tests/test_ranking.py b/libs/ai-endpoints/tests/integration_tests/test_ranking.py
index 06f9444b..47fc8438 100644
--- a/libs/ai-endpoints/tests/integration_tests/test_ranking.py
+++ b/libs/ai-endpoints/tests/integration_tests/test_ranking.py
@@ -66,11 +66,11 @@ def test_langchain_reranker_direct_empty_docs(
def test_langchain_reranker_direct_top_n_negative(
query: str, documents: List[Document], rerank_model: str, mode: dict
) -> None:
- orig = NVIDIARerank.Config.validate_assignment
- NVIDIARerank.Config.validate_assignment = False
+ orig = NVIDIARerank.model_config["validate_assignment"]
+ NVIDIARerank.model_config["validate_assignment"] = False
ranker = NVIDIARerank(model=rerank_model, **mode)
ranker.top_n = -100
- NVIDIARerank.Config.validate_assignment = orig
+ NVIDIARerank.model_config["validate_assignment"] = orig
result_docs = ranker.compress_documents(documents=documents, query=query)
assert len(result_docs) == 0
diff --git a/libs/ai-endpoints/tests/integration_tests/test_standard.py b/libs/ai-endpoints/tests/integration_tests/test_standard.py
new file mode 100644
index 00000000..983124c0
--- /dev/null
+++ b/libs/ai-endpoints/tests/integration_tests/test_standard.py
@@ -0,0 +1,23 @@
+"""Standard LangChain interface tests"""
+
+from typing import Type
+
+import pytest
+from langchain_core.language_models import BaseChatModel
+from langchain_standard_tests.integration_tests import ChatModelIntegrationTests
+
+from langchain_nvidia_ai_endpoints import ChatNVIDIA
+
+
+class TestNVIDIAStandard(ChatModelIntegrationTests):
+ @property
+ def chat_model_class(self) -> Type[BaseChatModel]:
+ return ChatNVIDIA
+
+ @property
+ def chat_model_params(self) -> dict:
+ return {"model": "meta/llama-3.1-8b-instruct"}
+
+ @pytest.mark.xfail(reason="anthropic-style list content not supported")
+ def test_tool_message_histories_list_content(self, model: BaseChatModel) -> None:
+ return super().test_tool_message_histories_list_content(model)
diff --git a/libs/ai-endpoints/tests/integration_tests/test_structured_output.py b/libs/ai-endpoints/tests/integration_tests/test_structured_output.py
index 3f4f5aa8..1f059e75 100644
--- a/libs/ai-endpoints/tests/integration_tests/test_structured_output.py
+++ b/libs/ai-endpoints/tests/integration_tests/test_structured_output.py
@@ -3,7 +3,7 @@
import pytest
from langchain_core.messages import HumanMessage
-from langchain_core.pydantic_v1 import BaseModel, Field
+from pydantic import BaseModel, Field
from langchain_nvidia_ai_endpoints import ChatNVIDIA
diff --git a/libs/ai-endpoints/tests/integration_tests/test_vlm_models.py b/libs/ai-endpoints/tests/integration_tests/test_vlm_models.py
index 260073c6..b261b4ee 100644
--- a/libs/ai-endpoints/tests/integration_tests/test_vlm_models.py
+++ b/libs/ai-endpoints/tests/integration_tests/test_vlm_models.py
@@ -1,26 +1,34 @@
import base64
+import os
from typing import Any, Dict, List, Union
import pytest
+import requests
from langchain_core.messages import BaseMessage, HumanMessage
from langchain_nvidia_ai_endpoints.chat_models import ChatNVIDIA
-# todo: test S3 bucket asset id
-# todo: sizes
-# todo: formats
-# todo: multiple images
# todo: multiple texts
-# todo: detail (fidelity)
+# todo: accuracy tests
+#
+# API Specification -
+#
+# - User message may contain 1 or more image_url
+# - image_url contains a url and optional detail
+# - detail is one of "low", "high" or "auto" (default)
+# - url is either a url to an image or base64 encoded image
+# - format for base64 is "data:image/png;{type}},..."
+# - supported image types are png, jpeg (or jpg), webp, gif (non-animated)
+#
#
# note: differences between api catalog and openai api
-# - openai api supports server-side image download, api catalog does not
+# - openai api supports server-side image download, api catalog does not consistently
# - ChatNVIDIA does client side download to simulate the same behavior
# - ChatNVIDIA will automatically read local files and convert them to base64
-# - openai api uses {"image_url": {"url": "..."}}
-# where api catalog uses {"image_url": "..."}
+# - openai api always uses {"image_url": {"url": "..."}}
+# where api catalog sometimes uses {"image_url": "..."}
#
@@ -40,7 +48,7 @@
{
"type": "image_url",
"image_url": {
- "url": f"""data:image/png;base64,{
+ "url": f"""data:image/jpg;base64,{
base64.b64encode(
open('tests/data/nvidia-picasso.jpg', 'rb').read()
).decode('utf-8')
@@ -48,16 +56,254 @@
},
}
],
+ f"""""",
],
- ids=["url", "file", "tag"],
+ ids=["url", "file", "data", "tag"],
+)
+@pytest.mark.parametrize(
+ "func",
+ ["invoke", "stream"],
+)
+def test_vlm_input_style(
+ vlm_model: str,
+ mode: dict,
+ func: str,
+ content: Union[str, List[Union[str, Dict[str, Any]]]],
+) -> None:
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ if func == "invoke":
+ response = chat.invoke([HumanMessage(content=content)])
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+ if func == "stream":
+ for token in chat.stream([HumanMessage(content=content)]):
+ assert isinstance(token.content, str)
+
+
+@pytest.mark.parametrize(
+ "detail",
+ ["low", "high", "auto", None],
+ ids=["low", "high", "auto", "none"],
)
-def test_vlm_model(
- vlm_model: str, mode: dict, content: Union[str, List[Union[str, Dict[Any, Any]]]]
+def test_vlm_detail_accepted(
+ vlm_model: str,
+ mode: dict,
+ detail: str,
) -> None:
chat = ChatNVIDIA(model=vlm_model, **mode)
- response = chat.invoke([HumanMessage(content=content)])
+ response = chat.invoke(
+ [
+ HumanMessage(
+ content=[
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": "tests/data/nvidia-picasso.jpg",
+ "detail": detail,
+ },
+ }
+ ]
+ )
+ ]
+ )
assert isinstance(response, BaseMessage)
assert isinstance(response.content, str)
+ # assert "cat" in response.content.lower()
- for token in chat.stream([HumanMessage(content=content)]):
- assert isinstance(token.content, str)
+
+@pytest.mark.parametrize(
+ "img",
+ [
+ "tests/data/nvidia-picasso.jpg",
+ "tests/data/nvidia-picasso.png",
+ "tests/data/nvidia-picasso.webp",
+ "tests/data/nvidia-picasso.gif",
+ ],
+ ids=["jpg", "png", "webp", "gif"],
+)
+def test_vlm_image_type(
+ vlm_model: str,
+ mode: dict,
+ img: str,
+) -> None:
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ response = chat.invoke(
+ [
+ HumanMessage(
+ content=[
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": img,
+ },
+ }
+ ]
+ )
+ ]
+ )
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+
+
+def test_vlm_image_large(
+ vlm_model: str,
+ mode: dict,
+) -> None:
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ response = chat.invoke(
+ [
+ HumanMessage(
+ content=[
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": "tests/data/nvidia-picasso-large.png",
+ },
+ }
+ ]
+ )
+ ]
+ )
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+
+
+def test_vlm_no_images(
+ vlm_model: str,
+ mode: dict,
+) -> None:
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ response = chat.invoke(
+ [HumanMessage(content="What is the capital of Massachusetts?")]
+ )
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+
+
+def test_vlm_two_images(
+ vlm_model: str,
+ mode: dict,
+) -> None:
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ response = chat.invoke(
+ [
+ HumanMessage(
+ content=[
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": "tests/data/nvidia-picasso.jpg",
+ },
+ },
+ {
+ "type": "image_url",
+ "image_url": {
+ "url": "tests/data/nvidia-picasso.jpg",
+ },
+ },
+ ]
+ )
+ ]
+ )
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+
+
+@pytest.fixture(scope="session")
+def asset_id() -> str:
+ # create an asset following -
+ # https://docs.nvidia.com/cloud-functions/user-guide/latest/cloud-function/assets.html
+
+ def create_asset_and_get_upload_url(
+ token: str, content_type: str, description: str
+ ) -> dict:
+ url = "https://api.nvcf.nvidia.com/v2/nvcf/assets"
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "accept": "application/json",
+ "Content-Type": "application/json",
+ }
+ data = {"contentType": content_type, "description": description}
+ response = requests.post(url, headers=headers, json=data)
+ response.raise_for_status()
+ return response.json()
+
+ def upload_image_to_presigned_url(
+ image_path: str, upload_url: str, content_type: str, description: str
+ ) -> None:
+ headers = {
+ "Content-Type": content_type,
+ "x-amz-meta-nvcf-asset-description": description,
+ }
+ with open(image_path, "rb") as image_file:
+ response = requests.put(upload_url, headers=headers, data=image_file)
+ response.raise_for_status()
+
+ content_type = "image/jpg"
+ description = "lc-nv-ai-e-test-nvidia-picasso"
+
+ asset_info = create_asset_and_get_upload_url(
+ os.environ["NVIDIA_API_KEY"], content_type, description
+ )
+ asset_id = asset_info["assetId"]
+
+ upload_image_to_presigned_url(
+ "tests/data/nvidia-picasso.jpg",
+ asset_info["uploadUrl"],
+ content_type,
+ description,
+ )
+
+ return asset_id
+
+
+@pytest.mark.parametrize(
+ "content",
+ [
+ [
+ {
+ "type": "image_url",
+ "image_url": {"url": "data:image/jpg;asset_id,{asset_id}"},
+ }
+ ],
+ [
+ """""",
+ ],
+ """""",
+ ],
+ ids=["data", "list-of-tag", "tag"],
+)
+@pytest.mark.parametrize(
+ "func",
+ ["invoke", "stream"],
+)
+def test_vlm_asset_id(
+ vlm_model: str,
+ mode: dict,
+ content: Union[str, List[Union[str, Dict[str, Any]]]],
+ func: str,
+ asset_id: str,
+) -> None:
+ if isinstance(content, str):
+ content = content.format(asset_id=asset_id)
+ elif isinstance(content, list):
+ for item in content:
+ if isinstance(item, str):
+ item = item.format(asset_id=asset_id)
+ elif isinstance(item, dict):
+ item["image_url"]["url"] = item["image_url"]["url"].format(
+ asset_id=asset_id
+ )
+
+ chat = ChatNVIDIA(model=vlm_model, **mode)
+ if func == "invoke":
+ response = chat.invoke([HumanMessage(content=content)])
+ assert isinstance(response, BaseMessage)
+ assert isinstance(response.content, str)
+ if func == "stream":
+ for token in chat.stream([HumanMessage(content=content)]):
+ assert isinstance(token.content, str)
diff --git a/libs/ai-endpoints/tests/unit_tests/test_202_polling.py b/libs/ai-endpoints/tests/unit_tests/test_202_polling.py
new file mode 100644
index 00000000..a3a5e644
--- /dev/null
+++ b/libs/ai-endpoints/tests/unit_tests/test_202_polling.py
@@ -0,0 +1,60 @@
+import warnings
+
+import requests_mock
+from langchain_core.messages import AIMessage
+
+from langchain_nvidia_ai_endpoints import ChatNVIDIA
+
+
+def test_polling_auth_header(
+ requests_mock: requests_mock.Mocker,
+ mock_model: str,
+) -> None:
+ infer_url = "https://integrate.api.nvidia.com/v1/chat/completions"
+ polling_url = "https://api.nvcf.nvidia.com/v2/nvcf/pexec/status/test-request-id"
+
+ requests_mock.post(
+ infer_url, status_code=202, headers={"NVCF-REQID": "test-request-id"}, json={}
+ )
+
+ requests_mock.get(
+ polling_url,
+ status_code=200,
+ json={
+ "id": "mock-id",
+ "created": 1234567890,
+ "object": "chat.completion",
+ "model": mock_model,
+ "choices": [
+ {
+ "index": 0,
+ "message": {"role": "assistant", "content": "WORKED"},
+ }
+ ],
+ },
+ )
+
+ warnings.filterwarnings("ignore", r".*type is unknown and inference may fail.*")
+ client = ChatNVIDIA(model=mock_model, api_key="BOGUS")
+ response = client.invoke("IGNORED")
+
+ # expected behavior -
+ # - first a GET request to /v1/models to check the model exists
+ # - second a POST request to /v1/chat/completions
+ # - third a GET request to /v2/nvcf/pexec/status/test-request-id
+ # we want to check on the second and third requests
+
+ assert len(requests_mock.request_history) == 3
+
+ infer_request = requests_mock.request_history[-2]
+ assert infer_request.method == "POST"
+ assert infer_request.url == infer_url
+ assert infer_request.headers["Authorization"] == "Bearer BOGUS"
+
+ poll_request = requests_mock.request_history[-1]
+ assert poll_request.method == "GET"
+ assert poll_request.url == polling_url
+ assert poll_request.headers["Authorization"] == "Bearer BOGUS"
+
+ assert isinstance(response, AIMessage)
+ assert response.content == "WORKED"
diff --git a/libs/ai-endpoints/tests/unit_tests/test_api_key.py b/libs/ai-endpoints/tests/unit_tests/test_api_key.py
index 2564b05b..552c4703 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_api_key.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_api_key.py
@@ -3,7 +3,7 @@
from typing import Any, Generator
import pytest
-from langchain_core.pydantic_v1 import SecretStr
+from pydantic import SecretStr
from requests_mock import Mocker
diff --git a/libs/ai-endpoints/tests/unit_tests/test_base_url.py b/libs/ai-endpoints/tests/unit_tests/test_base_url.py
index 0baaca6c..fb9c513a 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_base_url.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_base_url.py
@@ -28,10 +28,9 @@ def mock_v1_local_models(requests_mock: Mocker) -> None:
def test_create_without_base_url(public_class: type) -> None:
with no_env_var("NVIDIA_BASE_URL"):
- assert (
- public_class(api_key="BOGUS").base_url
- == "https://integrate.api.nvidia.com/v1"
- )
+ x = public_class(api_key="BOGUS")
+ assert x.base_url == "https://integrate.api.nvidia.com/v1"
+ assert x._client.base_url == "https://integrate.api.nvidia.com/v1"
@pytest.mark.parametrize(
diff --git a/libs/ai-endpoints/tests/unit_tests/test_bind_tools.py b/libs/ai-endpoints/tests/unit_tests/test_bind_tools.py
index a8f044f9..175f80dc 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_bind_tools.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_bind_tools.py
@@ -2,7 +2,7 @@
import warnings
from functools import reduce
from operator import add
-from typing import Any, List
+from typing import Annotated, Any, List
import pytest
import requests_mock
@@ -13,8 +13,8 @@
HumanMessage,
ToolMessage,
)
-from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import tool
+from pydantic import BaseModel, Field
from langchain_nvidia_ai_endpoints import ChatNVIDIA
@@ -32,7 +32,7 @@ class xxyyzz_cls(BaseModel):
@tool
-def xxyyzz_tool(
+def xxyyzz_tool_field(
a: int = Field(..., description="First number"),
b: int = Field(..., description="Second number"),
) -> int:
@@ -40,14 +40,24 @@ def xxyyzz_tool(
return 42
+@tool
+def xxyyzz_tool_annotated(
+ a: Annotated[int, "First number"],
+ b: Annotated[int, "Second number"],
+) -> int:
+ """xxyyzz two numbers"""
+ return 42
+
+
@pytest.mark.parametrize(
"tools, choice",
[
([xxyyzz_func], "xxyyzz_func"),
([xxyyzz_cls], "xxyyzz_cls"),
- ([xxyyzz_tool], "xxyyzz_tool"),
+ ([xxyyzz_tool_field], "xxyyzz_tool_field"),
+ ([xxyyzz_tool_annotated], "xxyyzz_tool_annotated"),
],
- ids=["func", "cls", "tool"],
+ ids=["func", "cls", "tool_field", "tool_annotated"],
)
def test_bind_tool_and_select(tools: Any, choice: str) -> None:
warnings.filterwarnings(
@@ -62,9 +72,10 @@ def test_bind_tool_and_select(tools: Any, choice: str) -> None:
([], "wrong"),
([xxyyzz_func], "wrong_xxyyzz_func"),
([xxyyzz_cls], "wrong_xxyyzz_cls"),
- ([xxyyzz_tool], "wrong_xxyyzz_tool"),
+ ([xxyyzz_tool_field], "wrong_xxyyzz_tool_field"),
+ ([xxyyzz_tool_annotated], "wrong_xxyyzz_tool_annotated"),
],
- ids=["empty", "func", "cls", "tool"],
+ ids=["empty", "func", "cls", "tool_field", "tool_annotated"],
)
def test_bind_tool_and_select_negative(tools: Any, choice: str) -> None:
warnings.filterwarnings(
@@ -163,7 +174,7 @@ def test_invoke_response_parsing(
r'"{\""',
r'"input\""',
r'"\":"',
- r"3",
+ r'"3"',
r'"}"',
],
[r'"{\"intput\": 3}"'],
@@ -185,6 +196,7 @@ def test_stream_response_parsing(
for argument in argument_chunks
],
'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}]}', # noqa: E501
+ 'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","choices":[],"usage":{"prompt_tokens":20,"total_tokens":42,"completion_tokens":22}}', # noqa: E501
"data: [DONE]",
]
),
@@ -260,3 +272,31 @@ def test_regression_ai_null_content(
assistant.content = None # type: ignore
llm.invoke([assistant])
llm.stream([assistant])
+
+
+def test_stream_usage_metadata(
+ requests_mock: requests_mock.Mocker,
+) -> None:
+ requests_mock.post(
+ "https://integrate.api.nvidia.com/v1/chat/completions",
+ text="\n\n".join(
+ [
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"role":"assistant","content":null},"logprobs":null,"finish_reason":null}]}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"ID1","type":"function","function":{"name":"magic_function","arguments":""}}]},"logprobs":null,"finish_reason":null}]}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"in"}}]},"logprobs":null,"finish_reason":null}]}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"put\":"}}]},"logprobs":null,"finish_reason":null}]}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"usage":null,"choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":" 3}"}}]},"logprobs":null,"finish_reason":null}]}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"tool_calls"}],"usage":null}', # noqa: E501
+ r'data: {"id":"ID0","object":"chat.completion.chunk","created":1234567890,"model":"BOGUS","system_fingerprint":null,"choices":[],"usage":{"prompt_tokens":76,"completion_tokens":29,"total_tokens":105}}', # noqa: E501
+ r"data: [DONE]",
+ ]
+ ),
+ )
+
+ llm = ChatNVIDIA(api_key="BOGUS")
+ response = reduce(add, llm.stream("IGNROED"))
+ assert isinstance(response, AIMessage)
+ assert response.usage_metadata is not None
+ assert response.usage_metadata["input_tokens"] == 76
+ assert response.usage_metadata["output_tokens"] == 29
+ assert response.usage_metadata["total_tokens"] == 105
diff --git a/libs/ai-endpoints/tests/unit_tests/test_model.py b/libs/ai-endpoints/tests/unit_tests/test_model.py
index bf19b806..4ef37849 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_model.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_model.py
@@ -96,7 +96,7 @@ def test_aliases(alias: str, client: Any) -> None:
"""
with pytest.warns(UserWarning) as record:
x = client(model=alias, nvidia_api_key="a-bogus-key")
- assert x.model == x._client.model_name
+ assert x.model == x._client.mdl_name
assert isinstance(record[0].message, Warning)
assert "deprecated" in record[0].message.args[0]
@@ -163,7 +163,7 @@ def test_default_lora(public_class: type) -> None:
def test_default(public_class: type) -> None:
x = public_class(api_key="BOGUS")
- assert x.model == x._default_model_name
+ assert x.model is not None
@pytest.mark.parametrize(
diff --git a/libs/ai-endpoints/tests/unit_tests/test_register_model.py b/libs/ai-endpoints/tests/unit_tests/test_register_model.py
index 482d40dc..f87efa11 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_register_model.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_register_model.py
@@ -21,9 +21,9 @@
("vlm", "NVIDIAEmbeddings"),
("vlm", "NVIDIARerank"),
("vlm", "NVIDIA"),
- ("embeddings", "ChatNVIDIA"),
- ("embeddings", "NVIDIARerank"),
- ("embeddings", "NVIDIA"),
+ ("embedding", "ChatNVIDIA"),
+ ("embedding", "NVIDIARerank"),
+ ("embedding", "NVIDIA"),
("ranking", "ChatNVIDIA"),
("ranking", "NVIDIAEmbeddings"),
("ranking", "NVIDIA"),
diff --git a/libs/ai-endpoints/tests/unit_tests/test_standard.py b/libs/ai-endpoints/tests/unit_tests/test_standard.py
new file mode 100644
index 00000000..a08cae2a
--- /dev/null
+++ b/libs/ai-endpoints/tests/unit_tests/test_standard.py
@@ -0,0 +1,18 @@
+"""Standard LangChain interface tests"""
+
+from typing import Type
+
+from langchain_core.language_models import BaseChatModel
+from langchain_standard_tests.unit_tests import ChatModelUnitTests
+
+from langchain_nvidia_ai_endpoints import ChatNVIDIA
+
+
+class TestNVIDIAStandard(ChatModelUnitTests):
+ @property
+ def chat_model_class(self) -> Type[BaseChatModel]:
+ return ChatNVIDIA
+
+ @property
+ def chat_model_params(self) -> dict:
+ return {"model": "meta/llama-3.1-8b-instruct"}
diff --git a/libs/ai-endpoints/tests/unit_tests/test_structured_output.py b/libs/ai-endpoints/tests/unit_tests/test_structured_output.py
index 053b10b3..81e4d8eb 100644
--- a/libs/ai-endpoints/tests/unit_tests/test_structured_output.py
+++ b/libs/ai-endpoints/tests/unit_tests/test_structured_output.py
@@ -4,15 +4,14 @@
import pytest
import requests_mock
-from langchain_core.pydantic_v1 import BaseModel as lc_pydanticV1BaseModel
-from langchain_core.pydantic_v1 import Field
from pydantic import BaseModel as pydanticV2BaseModel # ignore: check_pydantic
+from pydantic import Field
from pydantic.v1 import BaseModel as pydanticV1BaseModel # ignore: check_pydantic
from langchain_nvidia_ai_endpoints import ChatNVIDIA
-class Joke(lc_pydanticV1BaseModel):
+class Joke(pydanticV2BaseModel):
"""Joke to tell user."""
setup: str = Field(description="The setup of the joke")
@@ -39,7 +38,7 @@ def test_include_raw() -> None:
with pytest.raises(NotImplementedError):
ChatNVIDIA(api_key="BOGUS").with_structured_output(
- Joke.schema(), include_raw=True
+ Joke.model_json_schema(), include_raw=True
)
@@ -145,11 +144,10 @@ def test_stream_enum_incomplete(
@pytest.mark.parametrize(
"pydanticBaseModel",
[
- lc_pydanticV1BaseModel,
pydanticV1BaseModel,
pydanticV2BaseModel,
],
- ids=["lc-pydantic-v1", "pydantic-v1", "pydantic-v2"],
+ ids=["pydantic-v1", "pydantic-v2"],
)
def test_pydantic_version(
requests_mock: requests_mock.Mocker,
@@ -185,6 +183,7 @@ def test_pydantic_version(
class Person(pydanticBaseModel): # type: ignore
name: str
+ warnings.filterwarnings("ignore", r".*not known to support structured output.*")
llm = ChatNVIDIA(api_key="BOGUS").with_structured_output(Person)
response = llm.invoke("This is ignored.")
assert isinstance(response, Person)
diff --git a/libs/ai-endpoints/tests/unit_tests/test_vlm_models.py b/libs/ai-endpoints/tests/unit_tests/test_vlm_models.py
new file mode 100644
index 00000000..edda88c6
--- /dev/null
+++ b/libs/ai-endpoints/tests/unit_tests/test_vlm_models.py
@@ -0,0 +1,61 @@
+from typing import Any, Dict, List, Union
+
+import pytest
+
+from langchain_nvidia_ai_endpoints.chat_models import _nv_vlm_get_asset_ids
+
+
+@pytest.mark.parametrize(
+ "content, expected",
+ [
+ # Single asset ID in a string (double quotes)
+ ('', ["12345"]),
+ # Multiple asset IDs in a string (double quotes)
+ (
+ (
+ ''
+ ''
+ ),
+ ["12345", "67890"],
+ ),
+ # Single asset ID in list of strings (single quotes)
+ ([""], ["12345"]),
+ # Multiple asset IDs in list of strings (single quotes)
+ (
+ [
+ "",
+ "",
+ ],
+ ["12345", "67890"],
+ ),
+ # Single asset ID in a list of dictionaries
+ ([{"image_url": {"url": "data:image/png;asset_id,12345"}}], ["12345"]),
+ # Multiple asset IDs in a list of dictionaries
+ (
+ [
+ {"image_url": {"url": "data:image/png;asset_id,12345"}},
+ {"image_url": {"url": "data:image/jpeg;asset_id,67890"}},
+ ],
+ ["12345", "67890"],
+ ),
+ # No asset IDs present (double quotes)
+ ('', []),
+ # No asset IDs present (single quotes)
+ ("", []),
+ ],
+ ids=[
+ "single_asset_id_string_double_quotes",
+ "multiple_asset_ids_string_double_quotes",
+ "single_asset_id_list_of_strings_single_quotes",
+ "multiple_asset_ids_list_of_strings_single_quotes",
+ "single_asset_id_list_of_dicts",
+ "multiple_asset_ids_list_of_dicts",
+ "no_asset_ids_double_quotes",
+ "no_asset_ids_single_quotes",
+ ],
+)
+def test_nv_vlm_get_asset_ids(
+ content: Union[str, List[Union[str, Dict[str, Any]]]], expected: List[str]
+) -> None:
+ result = _nv_vlm_get_asset_ids(content)
+ assert result == expected