Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fixing git history, merge master to dev #16

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
44 changes: 41 additions & 3 deletions libs/gigachat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,50 @@ This is a library integration with [GigaChat](https://giga.chat/).
[![GitHub Release](https://img.shields.io/github/v/release/ai-forever/langchain-gigachat?style=flat-square)](https://github.com/ai-forever/langchain-gigachat/releases)
[![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ai-forever/langchain-gigachat/check_diffs.yml?style=flat-square)](https://github.com/ai-forever/langchain-gigachat/actions/workflows/check_diffs.yml)
[![GitHub License](https://img.shields.io/github/license/ai-forever/langchain-gigachat?style=flat-square)](https://opensource.org/license/MIT)
[![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/ai-forever/langchain-gigachat/total?style=flat-square)](https://pypistats.org/packages/langchain-gigachat)
[![GitHub Downloads (all assets, all releases)](https://img.shields.io/pypi/dm/langchain-gigachat?style=flat-square?style=flat-square)](https://pypistats.org/packages/langchain-gigachat)
[![GitHub Repo stars](https://img.shields.io/github/stars/ai-forever/langchain-gigachat?style=flat-square)](https://star-history.com/#ai-forever/langchain-gigachat)
[![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
Loading