Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
baur-krykpayev authored Jun 17, 2024
2 parents 99a54ac + dd25d08 commit 13e522b
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 58 deletions.
166 changes: 149 additions & 17 deletions docs/docs/concepts.mdx

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/docs/how_to/chat_models_universal_init.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"id": "cfdf4f09-8125-4ed1-8063-6feed57da8a3",
"metadata": {},
"source": [
"# How to let your end users choose their model\n",
"# How to init any model in one line\n",
"\n",
"Many LLM applications let end users specify what model provider and model they want the application to be powered by. This requires writing some logic to initialize different ChatModels based on some user configuration. The `init_chat_model()` helper method makes it easy to initialize a number of different model integrations without having to worry about import paths and class names.\n",
"\n",
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/how_to/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ These are the core building blocks you can use when building applications.
- [How to: stream a response back](/docs/how_to/chat_streaming)
- [How to: track token usage](/docs/how_to/chat_token_usage_tracking)
- [How to: track response metadata across providers](/docs/how_to/response_metadata)
- [How to: let your end users choose their model](/docs/how_to/chat_models_universal_init/)
- [How to: init any model in one line](/docs/how_to/chat_models_universal_init/)

### LLMs

Expand Down
72 changes: 72 additions & 0 deletions docs/docs/integrations/chat/deepinfra.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,78 @@
")\n",
"chat.invoke(messages)"
]
},
{
"cell_type": "markdown",
"id": "466c3cb41ace1410",
"metadata": {},
"source": [
"# Tool Calling\n",
"\n",
"DeepInfra currently supports only invoke and async invoke tool calling.\n",
"\n",
"For a complete list of models that support tool calling, please refer to our [tool calling documentation](https://deepinfra.com/docs/advanced/function_calling)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ddc4f4299763651c",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"\n",
"from dotenv import find_dotenv, load_dotenv\n",
"from langchain_community.chat_models import ChatDeepInfra\n",
"from langchain_core.messages import HumanMessage\n",
"from langchain_core.pydantic_v1 import BaseModel\n",
"from langchain_core.tools import tool\n",
"\n",
"model_name = \"meta-llama/Meta-Llama-3-70B-Instruct\"\n",
"\n",
"_ = load_dotenv(find_dotenv())\n",
"\n",
"\n",
"# Langchain tool\n",
"@tool\n",
"def foo(something):\n",
" \"\"\"\n",
" Called when foo\n",
" \"\"\"\n",
" pass\n",
"\n",
"\n",
"# Pydantic class\n",
"class Bar(BaseModel):\n",
" \"\"\"\n",
" Called when Bar\n",
" \"\"\"\n",
"\n",
" pass\n",
"\n",
"\n",
"llm = ChatDeepInfra(model=model_name)\n",
"tools = [foo, Bar]\n",
"llm_with_tools = llm.bind_tools(tools)\n",
"messages = [\n",
" HumanMessage(\"Foo and bar, please.\"),\n",
"]\n",
"\n",
"response = llm_with_tools.invoke(messages)\n",
"print(response.tool_calls)\n",
"# [{'name': 'foo', 'args': {'something': None}, 'id': 'call_Mi4N4wAtW89OlbizFE1aDxDj'}, {'name': 'Bar', 'args': {}, 'id': 'call_daiE0mW454j2O1KVbmET4s2r'}]\n",
"\n",
"\n",
"async def call_ainvoke():\n",
" result = await llm_with_tools.ainvoke(messages)\n",
" print(result.tool_calls)\n",
"\n",
"\n",
"# Async call\n",
"asyncio.run(call_ainvoke())\n",
"# [{'name': 'foo', 'args': {'something': None}, 'id': 'call_ZH7FetmgSot4LHcMU6CEb8tI'}, {'name': 'Bar', 'args': {}, 'id': 'call_2MQhDifAJVoijZEvH8PeFSVB'}]"
]
}
],
"metadata": {
Expand Down
Binary file added docs/static/img/colbert.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/static/img/langgraph_rag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/static/img/rag_landscape.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
96 changes: 79 additions & 17 deletions libs/community/langchain_community/chat_models/deepinfra.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
List,
Mapping,
Optional,
Sequence,
Tuple,
Type,
Union,
Expand All @@ -24,6 +25,7 @@
AsyncCallbackManagerForLLMRun,
CallbackManagerForLLMRun,
)
from langchain_core.language_models import LanguageModelInput
from langchain_core.language_models.chat_models import (
BaseChatModel,
agenerate_from_stream,
Expand All @@ -44,15 +46,18 @@
SystemMessage,
SystemMessageChunk,
)
from langchain_core.messages.tool import ToolCall
from langchain_core.outputs import (
ChatGeneration,
ChatGenerationChunk,
ChatResult,
)
from langchain_core.pydantic_v1 import Field, root_validator
from langchain_core.pydantic_v1 import BaseModel, Field, root_validator
from langchain_core.runnables import Runnable
from langchain_core.tools import BaseTool
from langchain_core.utils import get_from_dict_or_env
from langchain_core.utils.function_calling import convert_to_openai_tool

# from langchain.llms.base import create_base_retry_decorator
from langchain_community.utilities.requests import Requests

logger = logging.getLogger(__name__)
Expand All @@ -78,19 +83,51 @@ def _create_retry_decorator(
)


def _parse_tool_calling(tool_call: dict) -> ToolCall:
"""
Convert a tool calling response from server to a ToolCall object.
Args:
tool_call:
Returns:
"""
name = tool_call.get("name", "")
args = json.loads(tool_call["function"]["arguments"])
id = tool_call.get("id")
return ToolCall(name=name, args=args, id=id)


def _convert_to_tool_calling(tool_call: ToolCall) -> Dict[str, Any]:
"""
Convert a ToolCall object to a tool calling request for server.
Args:
tool_call:
Returns:
"""
return {
"type": "function",
"function": {
"arguments": json.dumps(tool_call["args"]),
"name": tool_call["name"],
},
"id": tool_call.get("id"),
}


def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage:
role = _dict["role"]
if role == "user":
return HumanMessage(content=_dict["content"])
elif role == "assistant":
# Fix for azure
# Also OpenAI returns None for tool invocations
content = _dict.get("content", "") or ""
if _dict.get("function_call"):
additional_kwargs = {"function_call": dict(_dict["function_call"])}
else:
additional_kwargs = {}
return AIMessage(content=content, additional_kwargs=additional_kwargs)
tool_calls_content = _dict.get("tool_calls", []) or []
tool_calls = [
_parse_tool_calling(tool_call) for tool_call in tool_calls_content
]
return AIMessage(content=content, tool_calls=tool_calls)
elif role == "system":
return SystemMessage(content=_dict["content"])
elif role == "function":
Expand All @@ -104,15 +141,14 @@ def _convert_delta_to_message_chunk(
) -> BaseMessageChunk:
role = _dict.get("role")
content = _dict.get("content") or ""
if _dict.get("function_call"):
additional_kwargs = {"function_call": dict(_dict["function_call"])}
else:
additional_kwargs = {}

if role == "user" or default_class == HumanMessageChunk:
return HumanMessageChunk(content=content)
elif role == "assistant" or default_class == AIMessageChunk:
return AIMessageChunk(content=content, additional_kwargs=additional_kwargs)
tool_calls = [
_parse_tool_calling(tool_call) for tool_call in _dict.get("tool_calls", [])
]
return AIMessageChunk(content=content, tool_calls=tool_calls)
elif role == "system" or default_class == SystemMessageChunk:
return SystemMessageChunk(content=content)
elif role == "function" or default_class == FunctionMessageChunk:
Expand All @@ -129,9 +165,14 @@ def _convert_message_to_dict(message: BaseMessage) -> dict:
elif isinstance(message, HumanMessage):
message_dict = {"role": "user", "content": message.content}
elif isinstance(message, AIMessage):
message_dict = {"role": "assistant", "content": message.content}
if "function_call" in message.additional_kwargs:
message_dict["function_call"] = message.additional_kwargs["function_call"]
tool_calls = [
_convert_to_tool_calling(tool_call) for tool_call in message.tool_calls
]
message_dict = {
"role": "assistant",
"content": message.content,
"tool_calls": tool_calls, # type: ignore[dict-item]
}
elif isinstance(message, SystemMessage):
message_dict = {"role": "system", "content": message.content}
elif isinstance(message, FunctionMessage):
Expand Down Expand Up @@ -417,6 +458,27 @@ def _headers(self) -> Dict:
def _body(self, kwargs: Any) -> Dict:
return kwargs

def bind_tools(
self,
tools: Sequence[Union[Dict[str, Any], Type[BaseModel], Callable, BaseTool]],
**kwargs: Any,
) -> Runnable[LanguageModelInput, BaseMessage]:
"""Bind tool-like objects to this chat model.
Assumes model is compatible with OpenAI tool-calling API.
Args:
tools: A list of tool definitions to bind to this chat model.
Can be a dictionary, pydantic model, callable, or BaseTool. Pydantic
models, callables, and BaseTools will be automatically converted to
their schema dictionary representation.
**kwargs: Any additional parameters to pass to the
:class:`~langchain.runnable.Runnable` constructor.
"""

formatted_tools = [convert_to_openai_tool(tool) for tool in tools]
return super().bind(tools=formatted_tools, **kwargs)


def _parse_stream(rbody: Iterator[bytes]) -> Iterator[str]:
for line in rbody:
Expand Down
60 changes: 38 additions & 22 deletions libs/community/langchain_community/embeddings/baichuan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import requests
from langchain_core.embeddings import Embeddings
from langchain_core.pydantic_v1 import BaseModel, SecretStr, root_validator
from langchain_core.pydantic_v1 import BaseModel, Field, SecretStr, root_validator
from langchain_core.utils import convert_to_secret_str, get_from_dict_or_env
from requests import RequestException

Expand Down Expand Up @@ -37,9 +37,16 @@ class BaichuanTextEmbeddings(BaseModel, Embeddings):
"""

session: Any #: :meta private:
model_name: str = "Baichuan-Text-Embedding"
baichuan_api_key: Optional[SecretStr] = None
model_name: str = Field(default="Baichuan-Text-Embedding", alias="model")
baichuan_api_key: Optional[SecretStr] = Field(default=None, alias="api_key")
"""Automatically inferred from env var `BAICHUAN_API_KEY` if not provided."""
chunk_size: int = 16
"""Chunk size when multiple texts are input"""

class Config:
"""Configuration for this pydantic object."""

allow_population_by_field_name = True

@root_validator(allow_reuse=True)
def validate_environment(cls, values: Dict) -> Dict:
Expand Down Expand Up @@ -78,26 +85,35 @@ def _embed(self, texts: List[str]) -> Optional[List[List[float]]]:
A list of list of floats representing the embeddings, or None if an
error occurs.
"""
response = self.session.post(
BAICHUAN_API_URL, json={"input": texts, "model": self.model_name}
)
# Raise exception if response status code from 400 to 600
response.raise_for_status()
# Check if the response status code indicates success
if response.status_code == 200:
resp = response.json()
embeddings = resp.get("data", [])
# Sort resulting embeddings by index
sorted_embeddings = sorted(embeddings, key=lambda e: e.get("index", 0))
# Return just the embeddings
return [result.get("embedding", []) for result in sorted_embeddings]
else:
# Log error or handle unsuccessful response appropriately
# Handle 100 <= status_code < 400, not include 200
raise RequestException(
f"Error: Received status code {response.status_code} from "
"`BaichuanEmbedding` API"
chunk_texts = [
texts[i : i + self.chunk_size]
for i in range(0, len(texts), self.chunk_size)
]
embed_results = []
for chunk in chunk_texts:
response = self.session.post(
BAICHUAN_API_URL, json={"input": chunk, "model": self.model_name}
)
# Raise exception if response status code from 400 to 600
response.raise_for_status()
# Check if the response status code indicates success
if response.status_code == 200:
resp = response.json()
embeddings = resp.get("data", [])
# Sort resulting embeddings by index
sorted_embeddings = sorted(embeddings, key=lambda e: e.get("index", 0))
# Return just the embeddings
embed_results.extend(
[result.get("embedding", []) for result in sorted_embeddings]
)
else:
# Log error or handle unsuccessful response appropriately
# Handle 100 <= status_code < 400, not include 200
raise RequestException(
f"Error: Received status code {response.status_code} from "
"`BaichuanEmbedding` API"
)
return embed_results

def embed_documents(self, texts: List[str]) -> Optional[List[List[float]]]: # type: ignore[override]
"""Public method to get embeddings for a list of documents.
Expand Down
Loading

0 comments on commit 13e522b

Please sign in to comment.