upstage: init package (#20574)

Co-authored-by: Sean Cho <sean@upstage.ai>
Co-authored-by: JuHyung-Son <sonju0427@gmail.com>
pull/20576/head
Erick Friis 4 weeks ago committed by GitHub
parent 11c9ed3362
commit f09bd0b75b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -78,6 +78,7 @@ jobs:
MONGODB_ATLAS_URI: ${{ secrets.MONGODB_ATLAS_URI }}
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
UPSTAGE_API_KEY: ${{ secrets.UPSTAGE_API_KEY }}
run: |
make integration_tests

@ -215,6 +215,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # for airbyte
MONGODB_ATLAS_URI: ${{ secrets.MONGODB_ATLAS_URI }}
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
UPSTAGE_API_KEY: ${{ secrets.UPSTAGE_API_KEY }}
run: make integration_tests
working-directory: ${{ inputs.working-directory }}

@ -1,80 +0,0 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 5,
"id": "a9667088-04e1-4f67-8221-a0072a2d635f",
"metadata": {
"execution": {
"iopub.execute_input": "2024-03-06T17:04:59.273702Z",
"iopub.status.busy": "2024-03-06T17:04:59.272602Z",
"iopub.status.idle": "2024-03-06T17:05:00.129177Z",
"shell.execute_reply": "2024-03-06T17:05:00.124594Z",
"shell.execute_reply.started": "2024-03-06T17:04:59.273646Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"AIMessage(content='저는 대형 언어 모델 프로젝트를 구축하고 싶습니다.')"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import os\n",
"\n",
"os.environ[\"SOLAR_API_KEY\"] = \"SOLAR_API_KEY\"\n",
"\n",
"from langchain_community.chat_models.solar import SolarChat\n",
"from langchain_core.messages import HumanMessage, SystemMessage\n",
"\n",
"chat = SolarChat(max_tokens=1024)\n",
"\n",
"messages = [\n",
" SystemMessage(\n",
" content=\"You are a helpful assistant who translates English to Korean.\"\n",
" ),\n",
" HumanMessage(\n",
" content=\"Translate this sentence from English to Korean. I want to build a project of large language model.\"\n",
" ),\n",
"]\n",
"\n",
"chat.invoke(messages)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8cb792fe-2844-4969-a9e9-f4c0f97b1699",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -0,0 +1,157 @@
{
"cells": [
{
"cell_type": "raw",
"id": "910f5772b6af13c9",
"metadata": {
"collapsed": false
},
"source": [
"---\n",
"sidebar_label: Upstage\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "433f5422ad8e1efa",
"metadata": {
"collapsed": false
},
"source": [
"# ChatUpstage\n",
"\n",
"This notebook covers how to get started with Upstage chat models.\n",
"\n",
"## Installation\n",
"\n",
"Install `langchain-upstage` package.\n",
"\n",
"```bash\n",
"pip install -U langchain-upstage\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "b3c5c4627fe95eae",
"metadata": {
"collapsed": false
},
"source": [
"## Environment Setup\n",
"\n",
"Make sure to set the following environment variables:\n",
"\n",
"- `UPSTAGE_API_KEY`: Your Upstage API key from [Upstage console](https://console.upstage.ai/).\n",
"\n",
"## Usage"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "20a0067b",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8a4d650d76a33494",
"metadata": {
"collapsed": false,
"is_executing": true
},
"outputs": [],
"source": [
"from langchain_core.prompts import ChatPromptTemplate\n",
"from langchain_upstage import ChatUpstage\n",
"\n",
"chat = ChatUpstage()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a1679b5cafaf88b9",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# using chat invoke\n",
"chat.invoke(\"Hello, how are you?\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "698a788a63b5c3e5",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# using chat stream\n",
"for m in chat.stream(\"Hello, how are you?\"):\n",
" print(m)"
]
},
{
"cell_type": "markdown",
"id": "36f8a703",
"metadata": {},
"source": [
"## Chaining"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "efa06617e5d4f6b2",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# using chain\n",
"prompt = ChatPromptTemplate.from_messages(\n",
" [\n",
" (\"system\", \"You are a helpful assistant that translates English to French.\"),\n",
" (\"human\", \"Translate this sentence from English to French. {english_text}.\"),\n",
" ]\n",
")\n",
"chain = prompt | chat\n",
"\n",
"chain.invoke({\"english_text\": \"Hello, how are you?\"})"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "3.9.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -1,30 +1,19 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Solar\n",
"\n",
"*This community integration is deprecated. You should use [`ChatUpstage`](../../chat/upstage) instead to access Solar LLM via the chat model connector.*"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2ff00e23-1a90-4a39-b220-83ebfffd96d6",
"metadata": {
"execution": {
"iopub.execute_input": "2024-03-06T17:10:57.375714Z",
"iopub.status.busy": "2024-03-06T17:10:57.375261Z",
"iopub.status.idle": "2024-03-06T17:11:03.473978Z",
"shell.execute_reply": "2024-03-06T17:11:03.472875Z",
"shell.execute_reply.started": "2024-03-06T17:10:57.375670Z"
}
},
"outputs": [
{
"data": {
"text/plain": [
"\"Once upon a time, in a far-off land, there was a young girl named Lily. Lily was a kind and curious girl who loved to explore the world around her. One day, while wandering through the forest, she came across a small, shimmering pond.\\n\\nAs she approached the pond, she saw a beautiful, glowing flower floating on the water's surface. Lily reached out to touch the flower, and as she did, she felt a strange tingling sensation. Suddenly, the flower began to glow even brighter, and Lily was transported to a magical world filled with talking animals and enchanted forests.\\n\\nIn this world, Lily met a wise old owl named Winston who told her that the flower she had touched was a magical one that could grant her any wish she desired. Lily was overjoyed and asked Winston to show her around the magical world.\\n\\nTogether, they explored the enchanted forests, met friendly animals, and discovered hidden treasures. Lily was having the time of her life, but she knew that she couldn't stay in this magical world forever. Eventually, she had to return home.\\n\\nAs she said goodbye to Winston and the magical world, Lily realized that she had learned an important lesson. She had discovered that sometimes, the most magical things in life are the ones that are right in front of us, if we only take the time to look.\\n\\nFrom that day on, Lily always kept her eyes open for the magic in the world around her, and she never forgot the adventure she had in the enchanted forest.\""
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
@ -37,37 +26,9 @@
},
{
"cell_type": "code",
"execution_count": 2,
"id": "67fa1711-f08f-43fa-a3bd-75ae5bc6b988",
"metadata": {
"execution": {
"iopub.execute_input": "2024-03-06T17:11:11.359924Z",
"iopub.status.busy": "2024-03-06T17:11:11.358357Z",
"iopub.status.idle": "2024-03-06T17:11:16.692138Z",
"shell.execute_reply": "2024-03-06T17:11:16.686492Z",
"shell.execute_reply.started": "2024-03-06T17:11:11.359835Z"
}
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/Users/ary/dev/llm/langchain/libs/core/langchain_core/_api/deprecation.py:117: LangChainDeprecationWarning: The function `run` was deprecated in LangChain 0.1.0 and will be removed in 0.2.0. Use invoke instead.\n",
" warn_deprecated(\n"
]
},
{
"data": {
"text/plain": [
"'Step 1: Determine the year Justin Bieber was born.\\nJustin Bieber was born on March 1, 1994.\\n\\nStep 2: Determine the Super Bowl held in 1994.\\nSuper Bowl XXVIII was held in 1994.\\n\\nStep 3: Determine the winning team of Super Bowl XXVIII.\\nThe Dallas Cowboys won Super Bowl XXVIII in 1994.\\n\\nFinal Answer: The Dallas Cowboys won the Super Bowl in the year Justin Bieber was born (1994).'"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain.chains import LLMChain\n",
"from langchain.prompts import PromptTemplate\n",
@ -86,35 +47,13 @@
"\n",
"llm_chain.run(question)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "91961983-d0d5-4901-b854-531e158c0416",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.0"
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
"nbformat_minor": 2
}

@ -30,6 +30,7 @@ These providers have standalone `langchain-{provider}` packages for improved ver
- [Pinecone](/docs/integrations/providers/pinecone)
- [Robocorp](/docs/integrations/providers/robocorp)
- [Together AI](/docs/integrations/providers/together)
- [Upstage](/docs/integrations/providers/upstage)
- [Voyage AI](/docs/integrations/providers/voyageai)

@ -0,0 +1,149 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Upstage\n",
"\n",
"[Upstage](https://upstage.ai) is a leading artificial intelligence (AI) company specializing in delivering above-human-grade performance LLM components. \n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Solar LLM\n",
"\n",
"**Solar Mini Chat** is a fast yet powerful advanced large language model focusing on English and Korean. It has been specifically fine-tuned for multi-turn chat purposes, showing enhanced performance across a wide range of natural language processing tasks, like multi-turn conversation or tasks that require an understanding of long contexts, such as RAG (Retrieval-Augmented Generation), compared to other models of a similar size. This fine-tuning equips it with the ability to handle longer conversations more effectively, making it particularly adept for interactive applications."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Installation and Setup\n",
"\n",
"Install `langchain-upstage` package:\n",
"\n",
"```bash\n",
"pip install -qU langchain-core langchain-upstage\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Get an [access token](https://console.upstage.ai) and set it as an environment variable (`UPSTAGE_API_KEY`)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Upstage LangChain integrations\n",
"\n",
"| API | Description | Import | Example usage |\n",
"| --- | --- | --- | --- |\n",
"| Chat | Build assistants using Solar Mini Chat | `from langchain_upstage import ChatUpstage` | [Go](../../chat/upstage) |\n",
"| Text Embedding | Embed strings to vectors | `from langchain_upstage import UpstageEmbeddings` | [Go](../../text_embedding/upstage) |\n",
"\n",
"See [documentations](https://developers.upstage.ai/) for more details about the features."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Quick Examples\n",
"\n",
"### Environment Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"### Chat\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_upstage import ChatUpstage\n",
"\n",
"chat = ChatUpstage()\n",
"response = chat.invoke(\"Hello, how are you?\")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"\n",
"\n",
"### Text embedding\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from langchain_upstage import UpstageEmbeddings\n",
"\n",
"embeddings = UpstageEmbeddings()\n",
"doc_result = embeddings.embed_documents(\n",
" [\"Sam is a teacher.\", \"This is another document\"]\n",
")\n",
"print(doc_result)\n",
"\n",
"query_result = embeddings.embed_query(\"What does Sam do?\")\n",
"print(query_result)"
]
}
],
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.11"
}
},
"nbformat": 4,
"nbformat_minor": 1
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,216 @@
{
"cells": [
{
"cell_type": "raw",
"id": "a1915c573ecefe5e",
"metadata": {
"collapsed": false
},
"source": [
"---\n",
"sidebar_label: Upstage\n",
"---"
]
},
{
"cell_type": "markdown",
"id": "c07bf6cf93adec81",
"metadata": {
"collapsed": false
},
"source": [
"# UpstageEmbeddings\n",
"\n",
"This notebook covers how to get started with Upstage embedding models.\n",
"\n",
"## Installation\n",
"\n",
"Install `langchain-upstage` package.\n",
"\n",
"```bash\n",
"pip install -U langchain-upstage\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "c2b1c8fd01d71683",
"metadata": {
"collapsed": false
},
"source": [
"## Environment Setup\n",
"\n",
"Make sure to set the following environment variables:\n",
"\n",
"- `UPSTAGE_API_KEY`: Your Upstage API key from [Upstage console](https://console.upstage.ai/)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a50c04f9",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"os.environ[\"UPSTAGE_API_KEY\"] = \"YOUR_API_KEY\""
]
},
{
"cell_type": "markdown",
"id": "c02e30aa",
"metadata": {},
"source": [
"\n",
"## Usage\n",
"\n",
"Initialize `UpstageEmbeddings` class."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea89ac9da2520b91",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from langchain_upstage import UpstageEmbeddings\n",
"\n",
"embeddings = UpstageEmbeddings()"
]
},
{
"cell_type": "markdown",
"id": "8be6eab1",
"metadata": {},
"source": [
"Use `embed_documents` to embed list of texts or documents. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "26aa179f7ad60cbe",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"doc_result = embeddings.embed_documents(\n",
" [\"Sam is a teacher.\", \"This is another document\"]\n",
")\n",
"print(doc_result)"
]
},
{
"cell_type": "markdown",
"id": "0197135c",
"metadata": {},
"source": [
"Use `embed_query` to embed query string."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a80d47413c27bbc",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"query_result = embeddings.embed_query(\"What does Sam do?\")\n",
"print(query_result)"
]
},
{
"cell_type": "markdown",
"id": "6d5ff58e",
"metadata": {},
"source": [
"Use `aembed_documents` and `aembed_query` for async operations."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "af75139d0e1d9ba2",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# async embed query\n",
"await embeddings.aembed_query(\"My query to look up\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "17968d20c0dfb2f9",
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# async embed documents\n",
"await embeddings.aembed_documents(\n",
" [\"This is a content of the document\", \"This is another document\"]\n",
")"
]
},
{
"cell_type": "markdown",
"id": "6429f2f8",
"metadata": {},
"source": [
"## Using with vector store\n",
"\n",
"You can use `UpstageEmbeddings` with vector store component. The following demonstrates a simple example."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "09ac41d5",
"metadata": {},
"outputs": [],
"source": [
"from langchain_community.vectorstores import DocArrayInMemorySearch\n",
"\n",
"vectorstore = DocArrayInMemorySearch.from_texts(\n",
" [\"harrison worked at kensho\", \"bears like to eat honey\"],\n",
" embedding=UpstageEmbeddings(),\n",
")\n",
"retriever = vectorstore.as_retriever()\n",
"docs = retriever.get_relevant_documents(\"Where did Harrison work?\")\n",
"print(docs)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

@ -1185,6 +1185,14 @@
{
"source": "/docs/guides/evaluation/:path*(/?)",
"destination": "/docs/guides/productionization/evaluation/:path*/"
},
{
"source": "/docs/integrations/text_embedding/solar(/?)",
"destination": "/docs/integrations/text_embedding/upstage"
},
{
"source": "/docs/integrations/chat/solar(/?)",
"destination": "/docs/integrations/chat/upstage"
}
]
}

@ -2,16 +2,19 @@
from typing import Dict
from langchain_core.pydantic_v1 import root_validator
from langchain_core._api import deprecated
from langchain_core.pydantic_v1 import Field, root_validator
from langchain_core.utils import get_from_dict_or_env
from langchain_community.chat_models import ChatOpenAI
from langchain_community.llms.solar import SOLAR_SERVICE_URL_BASE, SolarCommon
class SolarChat(SolarCommon, ChatOpenAI): # type: ignore[misc]
"""Solar large language models.
@deprecated(
since="0.0.34", removal="0.2.0", alternative_import="langchain_upstage.ChatUpstage"
)
class SolarChat(SolarCommon, ChatOpenAI):
"""Wrapper around Solar large language models.
To use, you should have the ``openai`` python package installed, and the
environment variable ``SOLAR_API_KEY`` set with your API key.
(Solar's chat API is compatible with OpenAI's SDK.)
@ -24,6 +27,16 @@ class SolarChat(SolarCommon, ChatOpenAI): # type: ignore[misc]
solar = SolarChat(model="solar-1-mini-chat")
"""
max_tokens: int = Field(default=1024)
# this is needed to match ChatOpenAI superclass
class Config:
"""Configuration for this pydantic object."""
allow_population_by_field_name = True
arbitrary_types_allowed = True
extra = "ignore"
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
"""Validate that the environment is set up correctly."""
@ -42,9 +55,9 @@ class SolarChat(SolarCommon, ChatOpenAI): # type: ignore[misc]
client_params = {
"api_key": values["solar_api_key"],
"base_url": values["base_url"]
if "base_url" in values
else SOLAR_SERVICE_URL_BASE,
"base_url": (
values["base_url"] if "base_url" in values else SOLAR_SERVICE_URL_BASE
),
}
if not values.get("client"):

@ -4,6 +4,7 @@ import logging
from typing import Any, Callable, Dict, List, Optional
import requests
from langchain_core._api import deprecated
from langchain_core.embeddings import Embeddings
from langchain_core.pydantic_v1 import BaseModel, Extra, SecretStr, root_validator
from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env
@ -44,6 +45,9 @@ def embed_with_retry(embeddings: SolarEmbeddings, *args: Any, **kwargs: Any) ->
return _embed_with_retry(*args, **kwargs)
@deprecated(
since="0.0.34", removal="0.2.0", alternative_import="langchain_upstage.ChatUpstage"
)
class SolarEmbeddings(BaseModel, Embeddings):
"""Solar's embedding service.

@ -40,7 +40,7 @@ class SolarCommon(BaseModel):
"""Solar API key. Get it here: https://console.upstage.ai/services/solar"""
model_name: str = Field(default="solar-1-mini-chat", alias="model")
"""Model name. Available models listed here: https://console.upstage.ai/services/solar"""
max_tokens: int = Field(default=1024, alias="max context")
max_tokens: int = Field(default=1024)
temperature = 0.3
class Config:

@ -0,0 +1 @@
__pycache__

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 LangChain, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,57 @@
.PHONY: all format lint test tests integration_tests docker_tests help extended_tests
# Default target executed when no arguments are given to make.
all: help
# Define a variable for the test file path.
TEST_FILE ?= tests/unit_tests/
integration_test integration_tests: TEST_FILE=tests/integration_tests/
test tests integration_test integration_tests:
poetry run pytest $(TEST_FILE)
######################
# LINTING AND FORMATTING
######################
# Define a variable for Python and notebook files.
PYTHON_FILES=.
MYPY_CACHE=.mypy_cache
lint format: PYTHON_FILES=.
lint_diff format_diff: PYTHON_FILES=$(shell git diff --relative=libs/partners/upstage --name-only --diff-filter=d master | grep -E '\.py$$|\.ipynb$$')
lint_package: PYTHON_FILES=langchain_upstage
lint_tests: PYTHON_FILES=tests
lint_tests: MYPY_CACHE=.mypy_cache_test
lint lint_diff lint_package lint_tests:
poetry run ruff .
poetry run ruff format $(PYTHON_FILES) --diff
poetry run ruff --select I $(PYTHON_FILES)
mkdir $(MYPY_CACHE); poetry run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
format format_diff:
poetry run ruff format $(PYTHON_FILES)
poetry run ruff --select I --fix $(PYTHON_FILES)
spell_check:
poetry run codespell --toml pyproject.toml
spell_fix:
poetry run codespell --toml pyproject.toml -w
check_imports: $(shell find langchain_upstage -name '*.py')
poetry run python ./scripts/check_imports.py $^
######################
# HELP
######################
help:
@echo '----'
@echo 'check_imports - check imports'
@echo 'format - run code formatters'
@echo 'lint - run linters'
@echo 'test - run unit tests'
@echo 'tests - run unit tests'
@echo 'test TEST_FILE=<test_file> - run all tests in file'

@ -0,0 +1,25 @@
# langchain-upstage
This package contains the LangChain integrations for [Upstage](https://upstage.ai) through their [APIs](https://developers.upstage.ai/docs/getting-started/models).
## Installation and Setup
- Install the LangChain partner package
```bash
pip install -U langchain-upstage
```
- Get an Upstage api key from [Upstage Console](https://console.upstage.ai/home) and set it as an environment variable (`UPSTAGE_API_KEY`)
## Chat Models
This package contains the `ChatUpstage` class, which is the recommended way to interface with Upstage models.
See a [usage example](https://python.langchain.com/docs/integrations/chat/upstage)
## Embeddings
See a [usage example](https://python.langchain.com/docs/integrations/text_embedding/upstage)
Use `solar-1-mini-embedding` as the default model for embeddings. Do not add suffixes such as `-query` or `-passage` to the model name.
`UpstageEmbeddings` will automatically add the suffixes based on the method called.

@ -0,0 +1,4 @@
from langchain_upstage.chat_models import ChatUpstage
from langchain_upstage.embeddings import UpstageEmbeddings
__all__ = ["ChatUpstage", "UpstageEmbeddings"]

@ -0,0 +1,101 @@
import os
from typing import (
Any,
Dict,
List,
Optional,
)
import openai
from langchain_core.pydantic_v1 import Field, SecretStr, root_validator
from langchain_core.utils import (
convert_to_secret_str,
get_from_dict_or_env,
)
from langchain_openai import ChatOpenAI
class ChatUpstage(ChatOpenAI):
"""ChatUpstage chat model.
To use, you should have the environment variable `UPSTAGE_API_KEY`
set with your API key or pass it as a named parameter to the constructor.
Example:
.. code-block:: python
from langchain_upstage import ChatUpstage
model = ChatUpstage()
"""
@property
def lc_secrets(self) -> Dict[str, str]:
return {"upstage_api_key": "UPSTAGE_API_KEY"}
@classmethod
def get_lc_namespace(cls) -> List[str]:
return ["langchain", "chat_models", "upstage"]
@property
def lc_attributes(self) -> Dict[str, Any]:
attributes: Dict[str, Any] = {}
if self.upstage_api_base:
attributes["upstage_api_base"] = self.upstage_api_base
return attributes
@property
def _llm_type(self) -> str:
"""Return type of chat model."""
return "upstage-chat"
model_name: str = Field(default="solar-1-mini-chat", alias="model")
"""Model name to use."""
upstage_api_key: Optional[SecretStr] = Field(default=None, alias="api_key")
"""Automatically inferred from env are `UPSTAGE_API_KEY` if not provided."""
upstage_api_base: Optional[str] = Field(
default="https://api.upstage.ai/v1/solar", alias="base_url"
)
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
"""Validate that api key and python package exists in environment."""
if values["n"] < 1:
raise ValueError("n must be at least 1.")
if values["n"] > 1 and values["streaming"]:
raise ValueError("n must be 1 when streaming.")
values["upstage_api_key"] = convert_to_secret_str(
get_from_dict_or_env(values, "upstage_api_key", "UPSTAGE_API_KEY")
)
values["upstage_api_base"] = values["upstage_api_base"] or os.getenv(
"UPSTAGE_API_BASE"
)
client_params = {
"api_key": (
values["upstage_api_key"].get_secret_value()
if values["upstage_api_key"]
else None
),
"base_url": values["upstage_api_base"],
"timeout": values["request_timeout"],
"max_retries": values["max_retries"],
"default_headers": values["default_headers"],
"default_query": values["default_query"],
}
if not values.get("client"):
sync_specific = {"http_client": values["http_client"]}
values["client"] = openai.OpenAI(
**client_params, **sync_specific
).chat.completions
if not values.get("async_client"):
async_specific = {"http_client": values["http_async_client"]}
values["async_client"] = openai.AsyncOpenAI(
**client_params, **async_specific
).chat.completions
return values

@ -0,0 +1,263 @@
import logging
import os
import warnings
from typing import (
Any,
Dict,
List,
Literal,
Mapping,
Optional,
Sequence,
Set,
Tuple,
Union,
)
import openai
from langchain_core.embeddings import Embeddings
from langchain_core.pydantic_v1 import (
BaseModel,
Extra,
Field,
SecretStr,
root_validator,
)
from langchain_core.utils import (
convert_to_secret_str,
get_from_dict_or_env,
get_pydantic_field_names,
)
logger = logging.getLogger(__name__)
class UpstageEmbeddings(BaseModel, Embeddings):
"""UpstageEmbeddings embedding model.
To use, set the environment variable `UPSTAGE_API_KEY` with your API key or
pass it as a named parameter to the constructor.
Example:
.. code-block:: python
from langchain_upstage import UpstageEmbeddings
model = UpstageEmbeddings()
"""
client: Any = Field(default=None, exclude=True) #: :meta private:
async_client: Any = Field(default=None, exclude=True) #: :meta private:
model: str = "solar-1-mini-embedding"
"""Embeddings model name to use. Do not add suffixes like `-query` and `-passage`.
Instead, use 'solar-1-mini-embedding' for example.
"""
dimensions: Optional[int] = None
"""The number of dimensions the resulting output embeddings should have.
Not yet supported.
"""
upstage_api_key: Optional[SecretStr] = Field(default=None, alias="api_key")
"""API Key for Solar API."""
upstage_api_base: str = Field(
default="https://api.upstage.ai/v1/solar", alias="base_url"
)
"""Endpoint URL to use."""
embedding_ctx_length: int = 4096
"""The maximum number of tokens to embed at once.
Not yet supported.
"""
allowed_special: Union[Literal["all"], Set[str]] = set()
"""Not yet supported."""
disallowed_special: Union[Literal["all"], Set[str], Sequence[str]] = "all"
"""Not yet supported."""
chunk_size: int = 1000
"""Maximum number of texts to embed in each batch.
Not yet supported.
"""
max_retries: int = 2
"""Maximum number of retries to make when generating."""
request_timeout: Optional[Union[float, Tuple[float, float], Any]] = Field(
default=None, alias="timeout"
)
"""Timeout for requests to Upstage embedding API. Can be float, httpx.Timeout or
None."""
show_progress_bar: bool = False
"""Whether to show a progress bar when embedding.
Not yet supported.
"""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""Holds any model parameters valid for `create` call not explicitly specified."""
skip_empty: bool = False
"""Whether to skip empty strings when embedding or raise an error.
Defaults to not skipping.
Not yet supported."""
default_headers: Union[Mapping[str, str], None] = None
default_query: Union[Mapping[str, object], None] = None
# Configure a custom httpx client. See the
# [httpx documentation](https://www.python-httpx.org/api/#client) for more details.
http_client: Union[Any, None] = None
"""Optional httpx.Client. Only used for sync invocations. Must specify
http_async_client as well if you'd like a custom client for async invocations.
"""
http_async_client: Union[Any, None] = None
"""Optional httpx.AsyncClient. Only used for async invocations. Must specify
http_client as well if you'd like a custom client for sync invocations."""
class Config:
extra = Extra.forbid
allow_population_by_field_name = True
@root_validator(pre=True)
def build_extra(cls, values: Dict[str, Any]) -> Dict[str, Any]:
"""Build extra kwargs from additional params that were passed in."""
all_required_field_names = get_pydantic_field_names(cls)
extra = values.get("model_kwargs", {})
for field_name in list(values):
if field_name in extra:
raise ValueError(f"Found {field_name} supplied twice.")
if field_name not in all_required_field_names:
warnings.warn(
f"""WARNING! {field_name} is not default parameter.
{field_name} was transferred to model_kwargs.
Please confirm that {field_name} is what you intended."""
)
extra[field_name] = values.pop(field_name)
invalid_model_kwargs = all_required_field_names.intersection(extra.keys())
if invalid_model_kwargs:
raise ValueError(
f"Parameters {invalid_model_kwargs} should be specified explicitly. "
f"Instead they were passed in as part of `model_kwargs` parameter."
)
values["model_kwargs"] = extra
return values
@root_validator()
def validate_environment(cls, values: Dict) -> Dict:
"""Validate that api key and python package exists in environment."""
upstage_api_key = get_from_dict_or_env(
values, "upstage_api_key", "UPSTAGE_API_KEY"
)
values["upstage_api_key"] = (
convert_to_secret_str(upstage_api_key) if upstage_api_key else None
)
values["upstage_api_base"] = values["upstage_api_base"] or os.getenv(
"UPSTAGE_API_BASE"
)
client_params = {
"api_key": (
values["upstage_api_key"].get_secret_value()
if values["upstage_api_key"]
else None
),
"base_url": values["upstage_api_base"],
"timeout": values["request_timeout"],
"max_retries": values["max_retries"],
"default_headers": values["default_headers"],
"default_query": values["default_query"],
}
if not values.get("client"):
sync_specific = {"http_client": values["http_client"]}
values["client"] = openai.OpenAI(
**client_params, **sync_specific
).embeddings
if not values.get("async_client"):
async_specific = {"http_client": values["http_async_client"]}
values["async_client"] = openai.AsyncOpenAI(
**client_params, **async_specific
).embeddings
return values
@property
def _invocation_params(self) -> Dict[str, Any]:
self.model = self.model.replace("-query", "").replace("-passage", "")
params: Dict = {"model": self.model, **self.model_kwargs}
if self.dimensions is not None:
params["dimensions"] = self.dimensions
return params
def embed_documents(self, texts: List[str]) -> List[List[float]]:
"""Embed a list of document texts using passage model.
Args:
texts: The list of texts to embed.
Returns:
List of embeddings, one for each text.
"""
embeddings = []
params = self._invocation_params
params["model"] = params["model"] + "-passage"
for text in texts:
response = self.client.create(input=text, **params)
if not isinstance(response, dict):
response = response.model_dump()
embeddings.extend([i["embedding"] for i in response["data"]])
return embeddings
def embed_query(self, text: str) -> List[float]:
"""Embed query text using query model.
Args:
text: The text to embed.
Returns:
Embedding for the text.
"""
params = self._invocation_params
params["model"] = params["model"] + "-query"
response = self.client.create(input=text, **params)
if not isinstance(response, dict):
response = response.model_dump()
return response["data"][0]["embedding"]
async def aembed_documents(self, texts: List[str]) -> List[List[float]]:
"""Embed a list of document texts using passage model asynchronously.
Args:
texts: The list of texts to embed.
Returns:
List of embeddings, one for each text.
"""
embeddings = []
params = self._invocation_params
params["model"] = params["model"] + "-passage"
for text in texts:
response = await self.async_client.create(input=text, **params)
if not isinstance(response, dict):
response = response.model_dump()
embeddings.extend([i["embedding"] for i in response["data"]])
return embeddings
async def aembed_query(self, text: str) -> List[float]:
"""Asynchronous Embed query text using query model.
Args:
text: The text to embed.
Returns:
Embedding for the text.
"""
params = self._invocation_params
params["model"] = params["model"] + "-query"
response = await self.async_client.create(input=text, **params)
if not isinstance(response, dict):
response = response.model_dump()
return response["data"][0]["embedding"]

File diff suppressed because it is too large Load Diff

@ -0,0 +1,96 @@
[tool.poetry]
name = "langchain-upstage"
version = "0.1.0rc0"
description = "An integration package connecting Upstage and LangChain"
authors = []
readme = "README.md"
repository = "https://github.com/langchain-ai/langchain"
license = "MIT"
[tool.poetry.urls]
"Source Code" = "https://github.com/langchain-ai/langchain/tree/master/libs/partners/upstage"
[tool.poetry.dependencies]
python = ">=3.8.1,<4.0"
langchain-core = "^0.1.42"
langchain-openai = "^0.1.3"
[tool.poetry.group.test]
optional = true
[tool.poetry.group.test.dependencies]
pytest = "^7.3.0"
freezegun = "^1.2.2"
pytest-mock = "^3.10.0"
syrupy = "^4.0.2"
pytest-watcher = "^0.3.4"
pytest-asyncio = "^0.21.1"
langchain-openai = { path = "../openai", develop = true }
langchain-core = { path = "../../core", develop = true }
docarray = "^0.32.1"
pydantic = "^1.10.9"
langchain-standard-tests = { path = "../../standard-tests", develop = true }
[tool.poetry.group.codespell]
optional = true
[tool.poetry.group.codespell.dependencies]
codespell = "^2.2.0"
[tool.poetry.group.test_integration]
optional = true
[tool.poetry.group.test_integration.dependencies]
[tool.poetry.group.lint]
optional = true
[tool.poetry.group.lint.dependencies]
ruff = "^0.1.5"
[tool.poetry.group.typing.dependencies]
mypy = "^0.991"
langchain-core = { path = "../../core", develop = true }
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
langchain-core = { path = "../../core", develop = true }
[tool.ruff]
select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
]
[tool.mypy]
disallow_untyped_defs = "True"
[tool.coverage.run]
omit = ["tests/*"]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.pytest.ini_options]
# --strict-markers will raise errors on unknown marks.
# https://docs.pytest.org/en/7.1.x/how-to/mark.html#raising-errors-on-unknown-marks
#
# https://docs.pytest.org/en/7.1.x/reference/reference.html
# --strict-config any warnings encountered while parsing the `pytest`
# section of the configuration file raise errors.
#
# https://github.com/tophat/syrupy
# --snapshot-warn-unused Prints a warning on unused snapshots rather than fail the test suite.
addopts = "--snapshot-warn-unused --strict-markers --strict-config --durations=5"
# Registering custom markers.
# https://docs.pytest.org/en/7.1.x/example/markers.html#registering-markers
markers = [
"requires: mark tests as requiring a specific library",
"asyncio: mark tests as requiring asyncio",
"compile: mark placeholder test used to compile integration tests without running them",
]
asyncio_mode = "auto"

@ -0,0 +1,17 @@
import sys
import traceback
from importlib.machinery import SourceFileLoader
if __name__ == "__main__":
files = sys.argv[1:]
has_failure = False
for file in files:
try:
SourceFileLoader("x", file).load_module()
except Exception:
has_faillure = True
print(file)
traceback.print_exc()
print()
sys.exit(1 if has_failure else 0)

@ -0,0 +1,27 @@
#!/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')
# 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

@ -0,0 +1,17 @@
#!/bin/bash
set -eu
# Initialize a variable to keep track of errors
errors=0
# make sure not importing from langchain or langchain_experimental
git --no-pager grep '^from langchain\.' . && errors=$((errors+1))
git --no-pager grep '^from langchain_experimental\.' . && errors=$((errors+1))
# Decide on an exit status based on the errors
if [ "$errors" -gt 0 ]; then
exit 1
else
exit 0
fi

@ -0,0 +1,136 @@
import pytest
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage
from langchain_upstage import ChatUpstage
def test_chat_upstage_model() -> None:
"""Test ChatUpstage wrapper handles model_name."""
chat = ChatUpstage(model="foo")
assert chat.model_name == "foo"
chat = ChatUpstage(model_name="bar")
assert chat.model_name == "bar"
def test_chat_upstage_system_message() -> None:
"""Test ChatOpenAI wrapper with system message."""
chat = ChatUpstage(max_tokens=10)
system_message = SystemMessage(content="You are to chat with the user.")
human_message = HumanMessage(content="Hello")
response = chat([system_message, human_message])
assert isinstance(response, BaseMessage)
assert isinstance(response.content, str)
def test_chat_upstage_llm_output_contains_model_name() -> None:
"""Test llm_output contains model_name."""
chat = ChatUpstage(max_tokens=10)
message = HumanMessage(content="Hello")
llm_result = chat.generate([[message]])
assert llm_result.llm_output is not None
assert llm_result.llm_output["model_name"] == chat.model_name
def test_chat_upstage_streaming_llm_output_contains_model_name() -> None:
"""Test llm_output contains model_name."""
chat = ChatUpstage(max_tokens=10, streaming=True)
message = HumanMessage(content="Hello")
llm_result = chat.generate([[message]])
assert llm_result.llm_output is not None
assert llm_result.llm_output["model_name"] == chat.model_name
def test_chat_upstage_invalid_streaming_params() -> None:
"""Test that streaming correctly invokes on_llm_new_token callback."""
with pytest.raises(ValueError):
ChatUpstage(
max_tokens=10,
streaming=True,
temperature=0,
n=5,
)
def test_chat_upstage_extra_kwargs() -> None:
"""Test extra kwargs to chat upstage."""
# Check that foo is saved in extra_kwargs.
llm = ChatUpstage(foo=3, max_tokens=10)
assert llm.max_tokens == 10
assert llm.model_kwargs == {"foo": 3}
# Test that if extra_kwargs are provided, they are added to it.
llm = ChatUpstage(foo=3, model_kwargs={"bar": 2})
assert llm.model_kwargs == {"foo": 3, "bar": 2}
# Test that if provided twice it errors
with pytest.raises(ValueError):
ChatUpstage(foo=3, model_kwargs={"foo": 2})
# Test that if explicit param is specified in kwargs it errors
with pytest.raises(ValueError):
ChatUpstage(model_kwargs={"temperature": 0.2})
# Test that "model" cannot be specified in kwargs
with pytest.raises(ValueError):
ChatUpstage(model_kwargs={"model": "solar-1-mini-chat"})
def test_stream() -> None:
"""Test streaming tokens from OpenAI."""
llm = ChatUpstage()
for token in llm.stream("I'm Pickle Rick"):
assert isinstance(token.content, str)
async def test_astream() -> None:
"""Test streaming tokens from OpenAI."""
llm = ChatUpstage()
async for token in llm.astream("I'm Pickle Rick"):
assert isinstance(token.content, str)
async def test_abatch() -> None:
"""Test streaming tokens from ChatUpstage."""
llm = ChatUpstage()
result = await llm.abatch(["I'm Pickle Rick", "I'm not Pickle Rick"])
for token in result:
assert isinstance(token.content, str)
async def test_abatch_tags() -> None:
"""Test batch tokens from ChatUpstage."""
llm = ChatUpstage()
result = await llm.abatch(
["I'm Pickle Rick", "I'm not Pickle Rick"], config={"tags": ["foo"]}
)
for token in result:
assert isinstance(token.content, str)
def test_batch() -> None:
"""Test batch tokens from ChatUpstage."""
llm = ChatUpstage()
result = llm.batch(["I'm Pickle Rick", "I'm not Pickle Rick"])
for token in result:
assert isinstance(token.content, str)
async def test_ainvoke() -> None:
"""Test invoke tokens from ChatUpstage."""
llm = ChatUpstage()
result = await llm.ainvoke("I'm Pickle Rick", config={"tags": ["foo"]})
assert isinstance(result.content, str)
def test_invoke() -> None:
"""Test invoke tokens from ChatUpstage."""
llm = ChatUpstage()
result = llm.invoke("I'm Pickle Rick", config=dict(tags=["foo"]))
assert isinstance(result.content, str)

@ -0,0 +1,32 @@
"""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_upstage import ChatUpstage
class TestUpstageStandard(ChatModelIntegrationTests):
@pytest.fixture
def chat_model_class(self) -> Type[BaseChatModel]:
return ChatUpstage
@pytest.fixture
def chat_model_params(self) -> dict:
return {
"model": "solar-1-mini-chat",
}
@pytest.mark.xfail(reason="400s with tool calling currently")
def test_tool_message_histories(
self,
chat_model_class: Type[BaseChatModel],
chat_model_params: dict,
chat_model_has_tool_calling: bool,
) -> None:
super().test_tool_message_histories(
chat_model_class, chat_model_params, chat_model_has_tool_calling
)

@ -0,0 +1,7 @@
import pytest
@pytest.mark.compile
def test_placeholder() -> None:
"""Used for compiling integration tests without running any real tests."""
pass

@ -0,0 +1,36 @@
"""Test Upstage embeddings."""
from langchain_upstage import UpstageEmbeddings
def test_langchain_upstage_embed_documents() -> None:
"""Test Upstage embeddings."""
documents = ["foo bar", "bar foo"]
embedding = UpstageEmbeddings()
output = embedding.embed_documents(documents)
assert len(output) == 2
assert len(output[0]) > 0
def test_langchain_upstage_embed_query() -> None:
"""Test Upstage embeddings."""
query = "foo bar"
embedding = UpstageEmbeddings()
output = embedding.embed_query(query)
assert len(output) > 0
async def test_langchain_upstage_aembed_documents() -> None:
"""Test Upstage embeddings asynchronous."""
documents = ["foo bar", "bar foo"]
embedding = UpstageEmbeddings()
output = await embedding.aembed_documents(documents)
assert len(output) == 2
assert len(output[0]) > 0
async def test_langchain_upstage_aembed_query() -> None:
"""Test Upstage embeddings asynchronous."""
query = "foo bar"
embedding = UpstageEmbeddings()
output = await embedding.aembed_query(query)
assert len(output) > 0

@ -0,0 +1,192 @@
import json
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from langchain_core.messages import (
AIMessage,
FunctionMessage,
HumanMessage,
SystemMessage,
ToolMessage,
)
from langchain_openai.chat_models.base import (
_convert_dict_to_message,
_convert_message_to_dict,
)
from langchain_upstage import ChatUpstage
def test_initialization() -> None:
"""Test chat model initialization."""
ChatUpstage()
def test_upstage_model_param() -> None:
llm = ChatUpstage(model="foo")
assert llm.model_name == "foo"
llm = ChatUpstage(model_name="foo")
assert llm.model_name == "foo"
def test_function_dict_to_message_function_message() -> None:
content = json.dumps({"result": "Example #1"})
name = "test_function"
result = _convert_dict_to_message(
{
"role": "function",
"name": name,
"content": content,
}
)
assert isinstance(result, FunctionMessage)
assert result.name == name
assert result.content == content
def test_convert_dict_to_message_human() -> None:
message = {"role": "user", "content": "foo"}
result = _convert_dict_to_message(message)
expected_output = HumanMessage(content="foo")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test__convert_dict_to_message_human_with_name() -> None:
message = {"role": "user", "content": "foo", "name": "test"}
result = _convert_dict_to_message(message)
expected_output = HumanMessage(content="foo", name="test")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test_convert_dict_to_message_ai() -> None:
message = {"role": "assistant", "content": "foo"}
result = _convert_dict_to_message(message)
expected_output = AIMessage(content="foo")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test_convert_dict_to_message_ai_with_name() -> None:
message = {"role": "assistant", "content": "foo", "name": "test"}
result = _convert_dict_to_message(message)
expected_output = AIMessage(content="foo", name="test")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test_convert_dict_to_message_system() -> None:
message = {"role": "system", "content": "foo"}
result = _convert_dict_to_message(message)
expected_output = SystemMessage(content="foo")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test_convert_dict_to_message_system_with_name() -> None:
message = {"role": "system", "content": "foo", "name": "test"}
result = _convert_dict_to_message(message)
expected_output = SystemMessage(content="foo", name="test")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
def test_convert_dict_to_message_tool() -> None:
message = {"role": "tool", "content": "foo", "tool_call_id": "bar"}
result = _convert_dict_to_message(message)
expected_output = ToolMessage(content="foo", tool_call_id="bar")
assert result == expected_output
assert _convert_message_to_dict(expected_output) == message
@pytest.fixture
def mock_completion() -> dict:
return {
"id": "chatcmpl-7fcZavknQda3SQ",
"object": "chat.completion",
"created": 1689989000,
"model": "solar-1-mini-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Bab",
"name": "KimSolar",
},
"finish_reason": "stop",
}
],
}
def test_upstage_invoke(mock_completion: dict) -> None:
llm = ChatUpstage()
mock_client = MagicMock()
completed = False
def mock_create(*args: Any, **kwargs: Any) -> Any:
nonlocal completed
completed = True
return mock_completion
mock_client.create = mock_create
with patch.object(
llm,
"client",
mock_client,
):
res = llm.invoke("bab")
assert res.content == "Bab"
assert completed
async def test_upstage_ainvoke(mock_completion: dict) -> None:
llm = ChatUpstage()
mock_client = AsyncMock()
completed = False
async def mock_create(*args: Any, **kwargs: Any) -> Any:
nonlocal completed
completed = True
return mock_completion
mock_client.create = mock_create
with patch.object(
llm,
"async_client",
mock_client,
):
res = await llm.ainvoke("bab")
assert res.content == "Bab"
assert completed
def test_upstage_invoke_name(mock_completion: dict) -> None:
llm = ChatUpstage()
mock_client = MagicMock()
mock_client.create.return_value = mock_completion
with patch.object(
llm,
"client",
mock_client,
):
messages = [
HumanMessage(content="Foo", name="Zorba"),
]
res = llm.invoke(messages)
call_args, call_kwargs = mock_client.create.call_args
assert len(call_args) == 0 # no positional args
call_messages = call_kwargs["messages"]
assert len(call_messages) == 1
assert call_messages[0]["role"] == "user"
assert call_messages[0]["content"] == "Foo"
assert call_messages[0]["name"] == "Zorba"
# check return type has name
assert res.content == "Bab"
assert res.name == "KimSolar"

@ -0,0 +1,20 @@
"""Standard LangChain interface tests"""
from typing import Type
import pytest
from langchain_core.language_models import BaseChatModel
from langchain_standard_tests.unit_tests import ChatModelUnitTests
from langchain_upstage import ChatUpstage
class TestUpstageStandard(ChatModelUnitTests):
@pytest.fixture
def chat_model_class(self) -> Type[BaseChatModel]:
return ChatUpstage
@pytest.fixture
def chat_model_params(self) -> dict:
return {
"model": "solar-1-mini-chat",
}

@ -0,0 +1,24 @@
"""Test embedding model integration."""
import os
import pytest
from langchain_upstage import UpstageEmbeddings
os.environ["UPSTAGE_API_KEY"] = "foo"
def test_initialization() -> None:
"""Test embedding model initialization."""
UpstageEmbeddings()
def test_upstage_invalid_model_kwargs() -> None:
with pytest.raises(ValueError):
UpstageEmbeddings(model_kwargs={"model": "foo"})
def test_upstage_incorrect_field() -> None:
with pytest.warns(match="not default parameter"):
llm = UpstageEmbeddings(foo="bar")
assert llm.model_kwargs == {"foo": "bar"}

@ -0,0 +1,10 @@
from langchain_upstage import __all__
EXPECTED_ALL = [
"ChatUpstage",
"UpstageEmbeddings",
]
def test_all_imports() -> None:
assert sorted(EXPECTED_ALL) == sorted(__all__)

@ -0,0 +1,13 @@
from langchain_upstage import ChatUpstage, UpstageEmbeddings
def test_chat_upstage_secrets() -> None:
o = ChatUpstage(upstage_api_key="foo")
s = str(o)
assert "foo" not in s
def test_upstage_embeddings_secrets() -> None:
o = UpstageEmbeddings(upstage_api_key="foo")
s = str(o)
assert "foo" not in s
Loading…
Cancel
Save