Skip to content

Commit 7a95ffc

Browse files
authored
community: fix some features on Naver ChatModel & embedding model 2 (#29243)
## Description - Responding to `NCP API Key` changes. - To fix `ChatClovaX` `astream` function to raise `SSEError` when an error event occurs. - To add `token length` and `ai_filter` to ChatClovaX's `response_metadata`. - To update document for apply NCP API Key changes. cc. @efriis @vbarda
1 parent 5d64597 commit 7a95ffc

File tree

7 files changed

+240
-97
lines changed

7 files changed

+240
-97
lines changed

docs/docs/integrations/chat/naver.ipynb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,25 +35,24 @@
3535
"\n",
3636
"## Setup\n",
3737
"\n",
38-
"Before using the chat model, you must go through the three steps below.\n",
38+
"Before using the chat model, you must go through the four steps below.\n",
3939
"\n",
4040
"1. Creating [NAVER Cloud Platform](https://www.ncloud.com/) account \n",
4141
"2. Apply to use [CLOVA Studio](https://www.ncloud.com/product/aiService/clovaStudio)\n",
42-
"3. Find API Keys after creating CLOVA Studio Test App or Service App (See [here](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#테스트앱생성).)\n",
42+
"3. Create a CLOVA Studio Test App or Service App of a model to use (See [here](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#테스트앱생성).)\n",
43+
"4. Issue a Test or Service API key (See [here](https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary#API%ED%82%A4).)\n",
4344
"\n",
4445
"### Credentials\n",
4546
"\n",
46-
"CLOVA Studio requires 2 keys (`NCP_CLOVASTUDIO_API_KEY` and `NCP_APIGW_API_KEY`).\n",
47-
" - `NCP_CLOVASTUDIO_API_KEY` is issued per Test App or Service App\n",
48-
" - `NCP_APIGW_API_KEY` is issued per account, could be optional depending on the region you are using\n",
49-
"\n",
50-
"The two API Keys could be found by clicking `App Request Status` > `Service App, Test App List` > `‘Details’ button for each app` in [CLOVA Studio](https://clovastudio.ncloud.com/studio-application/service-app)\n",
47+
"Set the `NCP_CLOVASTUDIO_API_KEY` environment variable with your API key.\n",
48+
" - Note that if you are using a legacy API Key (that doesn't start with `nv-*` prefix), you might need to get an additional API Key by clicking `App Request Status` > `Service App, Test App List` > `‘Details’ button for each app` in [CLOVA Studio](https://clovastudio.ncloud.com/studio-application/service-app) and set it as `NCP_APIGW_API_KEY`.\n",
5149
"\n",
5250
"You can add them to your environment variables as below:\n",
5351
"\n",
5452
"``` bash\n",
5553
"export NCP_CLOVASTUDIO_API_KEY=\"your-api-key-here\"\n",
56-
"export NCP_APIGW_API_KEY=\"your-api-key-here\"\n",
54+
"# Uncomment below to use a legacy API key\n",
55+
"# export NCP_APIGW_API_KEY=\"your-api-key-here\"\n",
5756
"```"
5857
]
5958
},
@@ -71,10 +70,11 @@
7170
" os.environ[\"NCP_CLOVASTUDIO_API_KEY\"] = getpass.getpass(\n",
7271
" \"Enter your NCP CLOVA Studio API Key: \"\n",
7372
" )\n",
74-
"if not os.getenv(\"NCP_APIGW_API_KEY\"):\n",
75-
" os.environ[\"NCP_APIGW_API_KEY\"] = getpass.getpass(\n",
76-
" \"Enter your NCP API Gateway API key: \"\n",
77-
" )"
73+
"# Uncomment below to use a legacy API key\n",
74+
"# if not os.getenv(\"NCP_APIGW_API_KEY\"):\n",
75+
"# os.environ[\"NCP_APIGW_API_KEY\"] = getpass.getpass(\n",
76+
"# \"Enter your NCP API Gateway API key: \"\n",
77+
"# )"
7878
]
7979
},
8080
{
@@ -340,7 +340,7 @@
340340
"\n",
341341
"When going live with production-level application using CLOVA Studio, you should apply for and use Service App. (See [here](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#서비스앱신청).)\n",
342342
"\n",
343-
"For a Service App, a corresponding `NCP_CLOVASTUDIO_API_KEY` is issued and can only be called with it."
343+
"For a Service App, you should use a corresponding Service API key and can only be called with it."
344344
]
345345
},
346346
{
@@ -353,7 +353,7 @@
353353
"# Update environment variables\n",
354354
"\n",
355355
"os.environ[\"NCP_CLOVASTUDIO_API_KEY\"] = getpass.getpass(\n",
356-
" \"Enter NCP CLOVA Studio API Key for Service App: \"\n",
356+
" \"Enter NCP CLOVA Studio Service API Key: \"\n",
357357
")"
358358
]
359359
},

docs/docs/integrations/providers/naver.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ Please refer to [NCP User Guide](https://guide.ncloud-docs.com/docs/clovastudio-
1010

1111
## Installation and Setup
1212

13-
- Get both CLOVA Studio API Key and API Gateway Key by [creating your app](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#create-test-app) and set them as environment variables respectively (`NCP_CLOVASTUDIO_API_KEY`, `NCP_APIGW_API_KEY`).
13+
- Get a CLOVA Studio API Key by [issuing it](https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary#API%ED%82%A4) and set it as an environment variable (`NCP_CLOVASTUDIO_API_KEY`).
14+
- If you are using a legacy API Key (that doesn't start with `nv-*` prefix), you might need to get an additional API Key by [creating your app](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#create-test-app) and set it as `NCP_APIGW_API_KEY`.
1415
- Install the integration Python package with:
1516

1617
```bash

docs/docs/integrations/text_embedding/naver.ipynb

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,13 @@
3232
"\n",
3333
"1. Creating [NAVER Cloud Platform](https://www.ncloud.com/) account \n",
3434
"2. Apply to use [CLOVA Studio](https://www.ncloud.com/product/aiService/clovaStudio)\n",
35-
"3. Find API Keys after creating CLOVA Studio Test App or Service App (See [here](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#테스트앱생성).)\n",
35+
"3. Create a CLOVA Studio Test App or Service App of a model to use (See [here](https://guide.ncloud-docs.com/docs/clovastudio-explorer03#%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%95%B1%EC%83%9D%EC%84%B1).)\n",
36+
"4. Issue a Test or Service API key (See [here](https://api.ncloud-docs.com/docs/ai-naver-clovastudio-summary#API%ED%82%A4).)\n",
3637
"\n",
3738
"### Credentials\n",
3839
"\n",
39-
"CLOVA Studio requires 3 keys (`NCP_CLOVASTUDIO_API_KEY`, `NCP_APIGW_API_KEY` and `NCP_CLOVASTUDIO_APP_ID`) for embeddings.\n",
40-
"- `NCP_CLOVASTUDIO_API_KEY` and `NCP_CLOVASTUDIO_APP_ID` is issued per serviceApp or testApp\n",
41-
"- `NCP_APIGW_API_KEY` is issued per account\n",
42-
"\n",
43-
"The two API Keys could be found by clicking `App Request Status` > `Service App, Test App List` > `‘Details’ button for each app` in [CLOVA Studio](https://clovastudio.ncloud.com/studio-application/service-app)."
40+
"Set the `NCP_CLOVASTUDIO_API_KEY` environment variable with your API key.\n",
41+
" - Note that if you are using a legacy API Key (that doesn't start with `nv-*` prefix), you might need two additional keys to be set as environment variables (`NCP_APIGW_API_KEY` and `NCP_CLOVASTUDIO_APP_ID`. They could be found by clicking `App Request Status` > `Service App, Test App List` > `Details` button for each app in [CLOVA Studio](https://clovastudio.ncloud.com/studio-application/service-app)."
4442
]
4543
},
4644
{
@@ -56,9 +54,15 @@
5654
"if not os.getenv(\"NCP_CLOVASTUDIO_API_KEY\"):\n",
5755
" os.environ[\"NCP_CLOVASTUDIO_API_KEY\"] = getpass.getpass(\n",
5856
" \"Enter NCP CLOVA Studio API Key: \"\n",
59-
" )\n",
60-
"if not os.getenv(\"NCP_APIGW_API_KEY\"):\n",
61-
" os.environ[\"NCP_APIGW_API_KEY\"] = getpass.getpass(\"Enter NCP API Gateway API Key: \")"
57+
" )"
58+
]
59+
},
60+
{
61+
"cell_type": "markdown",
62+
"id": "b31fc062",
63+
"metadata": {},
64+
"source": [
65+
"Uncomment below to use a legacy API key:"
6266
]
6367
},
6468
{
@@ -68,7 +72,9 @@
6872
"metadata": {},
6973
"outputs": [],
7074
"source": [
71-
"os.environ[\"NCP_CLOVASTUDIO_APP_ID\"] = input(\"Enter NCP CLOVA Studio App ID: \")"
75+
"# if not os.getenv(\"NCP_APIGW_API_KEY\"):\n",
76+
"# os.environ[\"NCP_APIGW_API_KEY\"] = getpass.getpass(\"Enter NCP API Gateway API Key: \")\n",
77+
"# os.environ[\"NCP_CLOVASTUDIO_APP_ID\"] = input(\"Enter NCP CLOVA Studio App ID: \")"
7278
]
7379
},
7480
{
@@ -118,8 +124,7 @@
118124
"from langchain_community.embeddings import ClovaXEmbeddings\n",
119125
"\n",
120126
"embeddings = ClovaXEmbeddings(\n",
121-
" model=\"clir-emb-dolphin\", # set with the model name of corresponding app id. Default is `clir-emb-dolphin`\n",
122-
" # app_id=\"...\" # set if you prefer to pass app id directly instead of using environment variables\n",
127+
" model=\"clir-emb-dolphin\" # set with the model name of corresponding app id. Default is `clir-emb-dolphin`\n",
123128
")"
124129
]
125130
},
@@ -251,7 +256,7 @@
251256
"\n",
252257
"When going live with production-level application using CLOVA Studio, you should apply for and use Service App. (See [here](https://guide.ncloud-docs.com/docs/en/clovastudio-playground01#서비스앱신청).)\n",
253258
"\n",
254-
"For a Service App, corresponding `NCP_CLOVASTUDIO_API_KEY` and `NCP_CLOVASTUDIO_APP_ID` are issued and can only be called with them."
259+
"For a Service App, you should use a corresponding Service API key and can only be called with it."
255260
]
256261
},
257262
{
@@ -266,6 +271,7 @@
266271
"os.environ[\"NCP_CLOVASTUDIO_API_KEY\"] = getpass.getpass(\n",
267272
" \"Enter NCP CLOVA Studio API Key for Service App: \"\n",
268273
")\n",
274+
"# Uncomment below to use a legacy API key:\n",
269275
"os.environ[\"NCP_CLOVASTUDIO_APP_ID\"] = input(\"Enter NCP CLOVA Studio Service App ID: \")"
270276
]
271277
},
@@ -279,7 +285,6 @@
279285
"embeddings = ClovaXEmbeddings(\n",
280286
" service_app=True,\n",
281287
" model=\"clir-emb-dolphin\", # set with the model name of corresponding app id of your Service App\n",
282-
" # app_id=\"...\" # set if you prefer to pass app id directly instead of using environment variables\n",
283288
")"
284289
]
285290
},

libs/community/langchain_community/chat_models/naver.py

Lines changed: 67 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
)
1616

1717
import httpx
18+
from httpx_sse import SSEError
1819
from langchain_core.callbacks import (
1920
AsyncCallbackManagerForLLMRun,
2021
CallbackManagerForLLMRun,
@@ -35,7 +36,13 @@
3536
)
3637
from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResult
3738
from langchain_core.utils import convert_to_secret_str, get_from_env
38-
from pydantic import AliasChoices, ConfigDict, Field, SecretStr, model_validator
39+
from pydantic import (
40+
AliasChoices,
41+
ConfigDict,
42+
Field,
43+
SecretStr,
44+
model_validator,
45+
)
3946
from typing_extensions import Self
4047

4148
_DEFAULT_BASE_URL = "https://clovastudio.stream.ntruss.com"
@@ -47,16 +54,13 @@ def _convert_chunk_to_message_chunk(
4754
sse: Any, default_class: Type[BaseMessageChunk]
4855
) -> BaseMessageChunk:
4956
sse_data = sse.json()
50-
message = sse_data.get("message")
51-
role = message.get("role")
52-
content = message.get("content") or ""
53-
5457
if sse.event == "result":
55-
response_metadata = {}
56-
if "stopReason" in sse_data:
57-
response_metadata["stopReason"] = sse_data["stopReason"]
58+
response_metadata = _sse_data_to_response_metadata(sse_data)
5859
return AIMessageChunk(content="", response_metadata=response_metadata)
5960

61+
message = sse_data.get("message")
62+
role = message.get("role")
63+
content = message.get("content") or ""
6064
if role == "user" or default_class == HumanMessageChunk:
6165
return HumanMessageChunk(content=content)
6266
elif role == "assistant" or default_class == AIMessageChunk:
@@ -69,6 +73,21 @@ def _convert_chunk_to_message_chunk(
6973
return default_class(content=content) # type: ignore[call-arg]
7074

7175

76+
def _sse_data_to_response_metadata(sse_data: Dict) -> Dict[str, Any]:
77+
response_metadata = {}
78+
if "stopReason" in sse_data:
79+
response_metadata["stop_reason"] = sse_data["stopReason"]
80+
if "inputLength" in sse_data:
81+
response_metadata["input_length"] = sse_data["inputLength"]
82+
if "outputLength" in sse_data:
83+
response_metadata["output_length"] = sse_data["outputLength"]
84+
if "seed" in sse_data:
85+
response_metadata["seed"] = sse_data["seed"]
86+
if "aiFilter" in sse_data:
87+
response_metadata["ai_filter"] = sse_data["aiFilter"]
88+
return response_metadata
89+
90+
7291
def _convert_message_to_naver_chat_message(
7392
message: BaseMessage,
7493
) -> Dict:
@@ -130,6 +149,8 @@ async def _aiter_sse(
130149
event_data = sse.json()
131150
if sse.event == "signal" and event_data.get("data", {}) == "[DONE]":
132151
return
152+
if sse.event == "error":
153+
raise SSEError(message=sse.data)
133154
yield sse
134155

135156

@@ -240,10 +261,15 @@ def _identifying_params(self) -> Dict[str, Any]:
240261

241262
@property
242263
def lc_secrets(self) -> Dict[str, str]:
243-
return {
244-
"ncp_clovastudio_api_key": "NCP_CLOVASTUDIO_API_KEY",
245-
"ncp_apigw_api_key": "NCP_APIGW_API_KEY",
246-
}
264+
if not self._is_new_api_key():
265+
return {
266+
"ncp_clovastudio_api_key": "NCP_CLOVASTUDIO_API_KEY",
267+
}
268+
else:
269+
return {
270+
"ncp_clovastudio_api_key": "NCP_CLOVASTUDIO_API_KEY",
271+
"ncp_apigw_api_key": "NCP_APIGW_API_KEY",
272+
}
247273

248274
@property
249275
def _llm_type(self) -> str:
@@ -285,10 +311,8 @@ def validate_model_after(self) -> Self:
285311
get_from_env("ncp_clovastudio_api_key", "NCP_CLOVASTUDIO_API_KEY")
286312
)
287313

288-
if not self.ncp_apigw_api_key:
289-
self.ncp_apigw_api_key = convert_to_secret_str(
290-
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY", "")
291-
)
314+
if not self._is_new_api_key():
315+
self._init_fields_on_old_api_key()
292316

293317
if not self.base_url:
294318
self.base_url = get_from_env(
@@ -311,6 +335,18 @@ def validate_model_after(self) -> Self:
311335

312336
return self
313337

338+
def _is_new_api_key(self) -> bool:
339+
if self.ncp_clovastudio_api_key:
340+
return self.ncp_clovastudio_api_key.get_secret_value().startswith("nv-")
341+
else:
342+
return False
343+
344+
def _init_fields_on_old_api_key(self) -> None:
345+
if not self.ncp_apigw_api_key:
346+
self.ncp_apigw_api_key = convert_to_secret_str(
347+
get_from_env("ncp_apigw_api_key", "NCP_APIGW_API_KEY", "")
348+
)
349+
314350
def default_headers(self) -> Dict[str, Any]:
315351
headers = {
316352
"Content-Type": "application/json",
@@ -322,16 +358,22 @@ def default_headers(self) -> Dict[str, Any]:
322358
if self.ncp_clovastudio_api_key
323359
else None
324360
)
325-
if clovastudio_api_key:
326-
headers["X-NCP-CLOVASTUDIO-API-KEY"] = clovastudio_api_key
327361

328-
apigw_api_key = (
329-
self.ncp_apigw_api_key.get_secret_value()
330-
if self.ncp_apigw_api_key
331-
else None
332-
)
333-
if apigw_api_key:
334-
headers["X-NCP-APIGW-API-KEY"] = apigw_api_key
362+
if self._is_new_api_key():
363+
### headers on new api key
364+
headers["Authorization"] = f"Bearer {clovastudio_api_key}"
365+
else:
366+
### headers on old api key
367+
if clovastudio_api_key:
368+
headers["X-NCP-CLOVASTUDIO-API-KEY"] = clovastudio_api_key
369+
370+
apigw_api_key = (
371+
self.ncp_apigw_api_key.get_secret_value()
372+
if self.ncp_apigw_api_key
373+
else None
374+
)
375+
if apigw_api_key:
376+
headers["X-NCP-APIGW-API-KEY"] = apigw_api_key
335377

336378
return headers
337379

@@ -348,7 +390,6 @@ def _create_message_dicts(
348390
def _completion_with_retry(self, **kwargs: Any) -> Any:
349391
from httpx_sse import (
350392
ServerSentEvent,
351-
SSEError,
352393
connect_sse,
353394
)
354395

0 commit comments

Comments
 (0)