Skip to content

Commit

Permalink
Dev (#13)
Browse files Browse the repository at this point in the history
* docs: add authorization methods to README for GigaChat usage (#1)

* ci: add workflow for dev branch (#2)

* docs: use monthly downloads counter (#3)

* docs: Readme update

* feat: support loading prompts from github repos (#8)

* docs: use monthly downloads counter
* feat: support loading prompts from github repos

---------

Co-authored-by: Dmitry Labazkin <[email protected]>

* Support few-shots from class description (#10)

* docs: use monthly downloads counter
* few_shot_examples можно задавать через pydantic схему
* feat: add pydantic schema support for few_shot_examples

---------

Co-authored-by: Dmitry Labazkin <[email protected]>
Co-authored-by: NIK-TIGER-BILL <[email protected]>

* chore: minor version up (#14)

---------

Co-authored-by: Dmitry Labazkin <[email protected]>
Co-authored-by: NIK-TIGER-BILL <[email protected]>
  • Loading branch information
3 people authored Nov 26, 2024
1 parent 1a8364a commit 1d57eb3
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 14 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/check_diffs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ name: CI

on:
push:
branches: [master]
branches:
- master
- dev
pull_request:

# If another push to the same PR or branch happens while this workflow is still running,
Expand Down
42 changes: 40 additions & 2 deletions libs/gigachat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,45 @@ This is a library integration with [GigaChat](https://giga.chat/).
[![GitHub Open Issues](https://img.shields.io/github/issues-raw/ai-forever/langchain-gigachat)](https://github.com/ai-forever/langchain-gigachat/issues)

## Installation

```bash
pip install -U langchain-gigachat
```
```

## Quickstart
Follow these simple steps to get up and running quickly.

### Installation
To install the package use following command:
```shell
pip install -U langchain-gigachat
```

### Initialization

To initialize chat model:
```python
from langchain_gigachat.chat_models import GigaChat

giga = GigaChat(credentials="YOUR_AUTHORIZATION_KEY", verify_ssl_certs=False)
```

To initialize embeddings:

```python
from langchain_gigachat.embeddings import GigaChatEmbeddings

embedding = GigaChatEmbeddings(
credentials="YOUR_AUTHORIZATION_KEY",
verify_ssl_certs=False
)
```

### Usage

Use the GigaChat object to generate responses:

```python
print(giga.invoke("Hello, world!"))
```

Now you can use the GigaChat object with LangChain's standard primitives to create LLM-applications.
35 changes: 32 additions & 3 deletions libs/gigachat/langchain_gigachat/chat_models/gigachat.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,43 @@ def trim_content_to_stop_sequence(
class GigaChat(_BaseGigaChat, BaseChatModel):
"""`GigaChat` large language models API.
To use, you should pass login and password to access GigaChat API or use token.
To use, provide credentials via token, login and password,
or mTLS for secure access to the GigaChat API.
Example:
Example Usage:
.. code-block:: python
from langchain_community.chat_models import GigaChat
giga = GigaChat(credentials=..., scope=..., verify_ssl_certs=False)
# Authorization with Token
# (obtainable in the personal cabinet under Authorization Data):
giga = GigaChat(credentials="YOUR_TOKEN")
# Personal Space:
giga = GigaChat(credentials="YOUR_TOKEN", scope="GIGACHAT_API_PERS")
# Corporate Space:
giga = GigaChat(credentials="YOUR_TOKEN", scope="GIGACHAT_API_CORP")
# Authorization with Login and Password:
giga = GigaChat(
base_url="https://gigachat.devices.sberbank.ru/api/v1",
user="YOUR_USERNAME",
password="YOUR_PASSWORD",
)
# Mutual Authentication via TLS (mTLS):
giga = GigaChat(
base_url="https://gigachat.devices.sberbank.ru/api/v1",
ca_bundle_file="certs/ca.pem", # chain_pem.txt
cert_file="certs/tls.pem", # published_pem.txt
key_file="certs/tls.key",
key_file_password="YOUR_KEY_PASSWORD",
)
# Authorization with Temporary Token:
giga = GigaChat(access_token="YOUR_TEMPORARY_TOKEN")
"""

def _build_payload(self, messages: List[BaseMessage], **kwargs: Any) -> gm.Chat:
Expand Down
67 changes: 67 additions & 0 deletions libs/gigachat/langchain_gigachat/tools/load_prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Utilities for loading templates from gigachain
github-based hub or other extenal sources."""

import os
import re
import tempfile
from pathlib import Path, PurePosixPath
from typing import Any, Callable, Optional, Set, TypeVar, Union
from urllib.parse import urljoin

import requests
from langchain_core.prompts.base import BasePromptTemplate
from langchain_core.prompts.loading import _load_prompt_from_file

DEFAULT_REF = os.environ.get("GIGACHAIN_HUB_DEFAULT_REF", "master")
URL_BASE = os.environ.get(
"GIGACHAIN_HUB_DEFAULT_REF",
"https://raw.githubusercontent.com/ai-forever/gigachain/{ref}/hub/",
)
HUB_PATH_RE = re.compile(r"lc(?P<ref>@[^:]+)?://(?P<path>.*)")

T = TypeVar("T")


def _load_from_giga_hub(
path: Union[str, Path],
loader: Callable[[str], T],
valid_prefix: str,
valid_suffixes: Set[str],
**kwargs: Any,
) -> Optional[T]:
"""Load configuration from hub. Returns None if path is not a hub path."""
if not isinstance(path, str) or not (match := HUB_PATH_RE.match(path)):
return None
ref, remote_path_str = match.groups()
ref = ref[1:] if ref else DEFAULT_REF
remote_path = Path(remote_path_str)
if remote_path.parts[0] != valid_prefix:
return None
if remote_path.suffix[1:] not in valid_suffixes:
raise ValueError(f"Unsupported file type, must be one of {valid_suffixes}.")

# Using Path with URLs is not recommended, because on Windows
# the backslash is used as the path separator, which can cause issues
# when working with URLs that use forward slashes as the path separator.
# Instead, use PurePosixPath to ensure that forward slashes are used as the
# path separator, regardless of the operating system.
full_url = urljoin(URL_BASE.format(ref=ref), PurePosixPath(remote_path).__str__())

r = requests.get(full_url, timeout=5)
if r.status_code != 200:
raise ValueError(f"Could not find file at {full_url}")
with tempfile.TemporaryDirectory() as tmpdirname:
file = Path(tmpdirname) / remote_path.name
with open(file, "wb") as f:
f.write(r.content)
return loader(str(file), **kwargs)


def load_from_giga_hub(path: Union[str, Path]) -> BasePromptTemplate:
"""Unified method for loading a prompt from GigaChain repo or local fs."""
if hub_result := _load_from_giga_hub(
path, _load_prompt_from_file, "prompts", {"py", "json", "yaml"}
):
return hub_result
else:
raise ValueError("Prompt not found in GigaChain Hub.")
5 changes: 5 additions & 0 deletions libs/gigachat/langchain_gigachat/utils/function_calling.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ def convert_pydantic_to_gigachat_function(
"Incorrect function or tool description. Description is required."
)

if few_shot_examples is None and hasattr(model, "few_shot_examples"):
few_shot_examples_attr = getattr(model, "few_shot_examples")
if inspect.isfunction(few_shot_examples_attr):
few_shot_examples = few_shot_examples_attr()

return GigaFunctionDescription(
name=name or title,
description=description,
Expand Down
41 changes: 36 additions & 5 deletions libs/gigachat/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions libs/gigachat/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langchain-gigachat"
version = "0.3.0"
version = "0.3.1"
description = "An integration package connecting GigaChat and LangChain"
authors = []
readme = "README.md"
Expand All @@ -13,7 +13,8 @@ license = "MIT"
[tool.poetry.dependencies]
python = ">=3.9,<4.0"
langchain-core = "^0.3"
gigachat = "^0.1.35"
gigachat = "^0.1.36"
types-requests = "^2.32"

[tool.poetry.group.dev]
optional = true
Expand Down Expand Up @@ -41,6 +42,7 @@ pytest = "^8.3.3"
pytest-cov = "^5.0.0"
pytest-asyncio = "^0.24.0"
pytest-mock = "^3.14.0"
requests_mock = "^1.12.1"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
43 changes: 42 additions & 1 deletion libs/gigachat/tests/unit_tests/test_gigachat.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
_convert_dict_to_message,
_convert_message_to_dict,
)
from langchain_gigachat.tools.giga_tool import giga_tool
from langchain_gigachat.tools.giga_tool import FewShotExamples, giga_tool
from tests.unit_tests.stubs import FakeAsyncCallbackHandler, FakeCallbackHandler


Expand Down Expand Up @@ -334,3 +334,44 @@ def test_gigachat_bind_gigatool() -> None:
"required": ["status", "message"],
"type": "object",
}


class SomeResult(BaseModel):
"""My desc"""

@staticmethod
def few_shot_examples() -> FewShotExamples:
return [
{
"request": "request example",
"params": {"is_valid": 1, "description": "correct message"},
}
]

value: int = Field(description="some value")
description: str = Field(description="some descriptin")


def test_structured_output() -> None:
llm = GigaChat().with_structured_output(SomeResult)
assert llm.steps[0].kwargs["function_call"] == {"name": "SomeResult"} # type: ignore[attr-defined]
assert llm.steps[0].kwargs["tools"][0]["function"] == { # type: ignore[attr-defined]
"name": "SomeResult",
"description": "My desc",
"parameters": {
"description": "My desc",
"properties": {
"value": {"description": "some value", "type": "integer"},
"description": {"description": "some descriptin", "type": "string"},
},
"required": ["value", "description"],
"type": "object",
},
"return_parameters": None,
"few_shot_examples": [
{
"request": "request example",
"params": {"is_valid": 1, "description": "correct message"},
}
],
}
31 changes: 31 additions & 0 deletions libs/gigachat/tests/unit_tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import Generator

import pytest
import requests_mock
from langchain_core.prompts.prompt import PromptTemplate

from langchain_gigachat.tools.load_prompt import load_from_giga_hub


@pytest.fixture
def mock_requests_get() -> Generator:
with requests_mock.Mocker() as mocker:
mocker.get(
"https://raw.githubusercontent.com/ai-forever/gigachain/master/hub/prompts/entertainment/meditation.yaml",
text=(
"input_variables: [background, topic]\n"
"output_parser: null\n"
"template: 'Create mediation for {topic} with {background}'\n"
"template_format: f-string\n"
"_type: prompt"
),
)
yield mocker


def test__load_from_giga_hub(mock_requests_get: Generator) -> None:
template = load_from_giga_hub("lc://prompts/entertainment/meditation.yaml")
assert isinstance(template, PromptTemplate)
assert template.template == "Create mediation for {topic} with {background}"
assert "background" in template.input_variables
assert "topic" in template.input_variables

0 comments on commit 1d57eb3

Please sign in to comment.