community(you): Integrate You.com conversational APIs (#23046)

You.com is releasing two new conversational APIs — Smart and Research. 

This PR:
- integrates those APIs with Langchain, as an LLM
- streaming is supported

If no one reviews your PR within a few days, please @-mention one of
baskaryan, efriis, eyurtsev, ccurme, vbarda, hwchase17.
This commit is contained in:
Christopher Tee
2024-07-15 17:46:58 -04:00
committed by GitHub
parent 6c7d9f93b9
commit 5171ffc026
5 changed files with 189 additions and 0 deletions

View File

@@ -72,6 +72,7 @@ rspace_client>=2.5.0,<3
scikit-learn>=1.2.2,<2
simsimd>=4.3.1,<5
sqlite-vss>=0.1.2,<0.2
sseclient-py>=1.8.0,<2
streamlit>=1.18.0,<2
sympy>=1.12,<2
telethon>=1.28.5,<2

View File

@@ -640,6 +640,12 @@ def _import_yuan2() -> Type[BaseLLM]:
return Yuan2
def _import_you() -> Type[BaseLLM]:
from langchain_community.llms.you import You
return You
def _import_volcengine_maas() -> Type[BaseLLM]:
from langchain_community.llms.volcengine_maas import VolcEngineMaasLLM
@@ -847,6 +853,8 @@ def __getattr__(name: str) -> Any:
return _import_yandex_gpt()
elif name == "Yuan2":
return _import_yuan2()
elif name == "You":
return _import_you()
elif name == "VolcEngineMaasLLM":
return _import_volcengine_maas()
elif name == "type_to_cls_dict":
@@ -959,6 +967,7 @@ __all__ = [
"Writer",
"Xinference",
"YandexGPT",
"You",
"Yuan2",
]
@@ -1056,6 +1065,7 @@ def get_type_to_cls_dict() -> Dict[str, Callable[[], Type[BaseLLM]]]:
"qianfan_endpoint": _import_baidu_qianfan_endpoint,
"yandex_gpt": _import_yandex_gpt,
"yuan2": _import_yuan2,
"you": _import_you,
"VolcEngineMaasLLM": _import_volcengine_maas,
"SparkLLM": _import_sparkllm,
}

View File

@@ -0,0 +1,140 @@
import os
from typing import Any, Dict, Generator, Iterator, List, Literal, Optional
import requests
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
SMART_ENDPOINT = "https://chat-api.you.com/smart"
RESEARCH_ENDPOINT = "https://chat-api.you.com/research"
def _request(base_url: str, api_key: str, **kwargs: Any) -> Dict[str, Any]:
"""
NOTE: This function can be replaced by a OpenAPI-generated Python SDK in the future,
for better input/output typing support.
"""
headers = {"x-api-key": api_key}
response = requests.post(base_url, headers=headers, json=kwargs)
response.raise_for_status()
return response.json()
def _request_stream(
base_url: str, api_key: str, **kwargs: Any
) -> Generator[str, None, None]:
headers = {"x-api-key": api_key}
params = dict(**kwargs, stream=True)
response = requests.post(base_url, headers=headers, stream=True, json=params)
response.raise_for_status()
# Explicitly coercing the response to a generator to satisfy mypy
event_source = (bytestring for bytestring in response)
try:
import sseclient
client = sseclient.SSEClient(event_source)
except ImportError:
raise ImportError(
(
"Could not import `sseclient`. "
"Please install it with `pip install sseclient-py`."
)
)
for event in client.events():
if event.event in ("search_results", "done"):
pass
elif event.event == "token":
yield event.data
elif event.event == "error":
raise ValueError(f"Error in response: {event.data}")
else:
raise NotImplementedError(f"Unknown event type {event.event}")
class You(LLM):
"""Wrapper around You.com's conversational Smart and Research APIs.
Each API endpoint is designed to generate conversational
responses to a variety of query types, including inline citations
and web results when relevant.
Smart Endpoint:
- Quick, reliable answers for a variety of questions
- Cites the entire web page URL
Research Endpoint:
- In-depth answers with extensive citations for a variety of questions
- Cites the specific web page snippet relevant to the claim
To connect to the You.com api requires an API key which
you can get at https://api.you.com.
For more information, check out the documentations at
https://documentation.you.com/api-reference/.
Args:
endpoint: You.com conversational endpoints. Choose from "smart" or "research"
ydc_api_key: You.com API key, if `YDC_API_KEY` is not set in the environment
"""
endpoint: Literal["smart", "research"] = Field(
"smart",
description=(
'You.com conversational endpoints. Choose from "smart" or "research"'
),
)
ydc_api_key: Optional[str] = Field(
None,
description="You.com API key, if `YDC_API_KEY` is not set in the envrioment",
)
def _call(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> str:
if stop:
raise NotImplementedError(
"Stop words are not implemented for You.com endpoints."
)
params = {"query": prompt}
response = _request(self._request_endpoint, api_key=self._api_key, **params)
return response["answer"]
def _stream(
self,
prompt: str,
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> Iterator[GenerationChunk]:
if stop:
raise NotImplementedError(
"Stop words are not implemented for You.com endpoints."
)
params = {"query": prompt}
for token in _request_stream(
self._request_endpoint, api_key=self._api_key, **params
):
yield GenerationChunk(text=token)
@property
def _request_endpoint(self) -> str:
if self.endpoint == "smart":
return SMART_ENDPOINT
return RESEARCH_ENDPOINT
@property
def _api_key(self) -> str:
return self.ydc_api_key or os.environ["YDC_API_KEY"]
@property
def _llm_type(self) -> str:
return "you.com"

View File

@@ -98,6 +98,7 @@ EXPECT_ALL = [
"QianfanLLMEndpoint",
"YandexGPT",
"Yuan2",
"You",
"VolcEngineMaasLLM",
"WatsonxLLM",
"SparkLLM",

View File

@@ -0,0 +1,37 @@
import pytest
import requests_mock
@pytest.mark.parametrize("endpoint", ("smart", "research"))
@pytest.mark.requires("sseclient")
def test_invoke(
endpoint: str, requests_mock: requests_mock.Mocker, monkeypatch: pytest.MonkeyPatch
) -> None:
from langchain_community.llms import You
from langchain_community.llms.you import RESEARCH_ENDPOINT, SMART_ENDPOINT
json = {
"answer": (
"A solar eclipse occurs when the Moon passes between the Sun and Earth, "
"casting a shadow on Earth and ..."
),
"search_results": [
{
"url": "https://en.wikipedia.org/wiki/Solar_eclipse",
"name": "Solar eclipse - Wikipedia",
"snippet": (
"A solar eclipse occurs when the Moon passes "
"between Earth and the Sun, thereby obscuring the view of the Sun "
"from a small part of Earth, totally or partially. "
),
}
],
}
request_endpoint = SMART_ENDPOINT if endpoint == "smart" else RESEARCH_ENDPOINT
requests_mock.post(request_endpoint, json=json)
monkeypatch.setenv("YDC_API_KEY", "...")
llm = You(endpoint=endpoint)
output = llm.invoke("What is a solar eclipse?")
assert output == json["answer"]