上一篇分享了三個用 API 呼叫的 LLMs,那除了單純的跟 AI 溝通,如果要串接其他 AI 工具或功能,像是 Memory、Retrieve 甚至是更進階的 Agents,自己一點一點慢慢刻當然是做得到。但是有個框架可以幫助你將
這些工具、元件、語言模型都整合起來,何樂而不為呢?那這就是我們今天要分享的主角 - LangChain
簡單來說,LangChain 整合了大多數的語言模型,像是 OpenAI、Gemini、Ollama 等。但如果沒有的話,像是台智雲,也可以自己將模型整合至 LangChain 架構裡面,後面會實戰程式!其實跟同事實作的時候,LangChain 的評價很兩極,有些人覺得都整合起來很方便,有些人覺得還要多學一個框架很麻煩。我自己是覺得都挺有道理,畢竟剛開始用 LangChain 確實花了一段時間跟他混熟。而且從我知道 LangChain 到現在,不到一年的時間從 0.1.6 版到今天 (2024/08/16) 0.2.14 版,之前還曾經一個禮拜更新兩次,所以搞不好一年後或幾個月後這篇文章的程式全部都不能用了 🤣
要安裝
langchain-google-genai
這個套件,套件說明連結:langchain-google-genai
# 匯入需要的套件
from dotenv import load_dotenv
from langchain_google_genai import ChatGoogleGenerativeAI
load_dotenv()
# 選擇模型和想要調整的參數
llm = ChatGoogleGenerativeAI(model="gemini-pro", temperature=0.2)
response = llm.invoke("Aespa的成員有誰?")
print(response.content)
程式碼結果探討 🧐:
要安裝
langchain-openai
這個套件,套件說明連結:langchain-openai
# 匯入需要的套件
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
load_dotenv()
# 選擇模型和想要調整的參數
llm = ChatOpenAI(model="gpt-4o", temperature=0.2)
response = llm.invoke("Aespa的成員有誰?")
print(response.content)
程式碼結果探討 🧐:
要安裝
langchain-core
&Requests
這兩個套件,套件說明連結:LangChain Custom LLM
import os
import json
import requests
from pydantic.v1 import Field
from typing import Any, Dict, List, Mapping, Optional, Tuple
from langchain_core.language_models.chat_models import BaseChatModel
from langchain_core.language_models.base import BaseLanguageModel
from langchain_core.messages.base import BaseMessage
from langchain_core.outputs.chat_generation import ChatGeneration
from langchain_core.outputs.chat_result import ChatResult
from langchain_core.messages.chat import ChatMessage
from langchain_core.messages.ai import AIMessage
from langchain_core.messages.human import HumanMessage
from langchain_core.messages.system import SystemMessage
from langchain_core.callbacks.manager import CallbackManagerForLLMRun
from dotenv import load_dotenv
load_dotenv()
class _ChatFormosaFoundationCommon(BaseLanguageModel):
base_url: str = "https://api-ams.twcc.ai/api"
"""Base url the model is hosted under."""
model: str = "ffm-llama3-70b-chat"
"""Model name to use."""
temperature: Optional[float] = 0.01
"""The temperature of the model. Increasing the temperature will
make the model answer more creatively."""
stop: Optional[List[str]]
"""Sets the stop tokens to use."""
top_k: int = 10
"""Reduces the probability of generating nonsense. A higher value (e.g. 100)
will give more diverse answers, while a lower value (e.g. 10)
will be more conservative. (Default: 50)"""
top_p: float = 1
"""Works together with top-k. A higher value (e.g., 0.95) will lead
to more diverse text, while a lower value (e.g., 0.5) will
generate more focused and conservative text. (Default: 1)"""
max_new_tokens: int = 1024
"""The maximum number of tokens to generate in the completion.
-1 returns as many tokens as possible given the prompt and
the models maximal context size."""
frequence_penalty: float = 1.03
"""Penalizes repeated tokens according to frequency."""
model_kwargs: Dict[str, Any] = Field(default_factory=dict)
"""Holds any model parameters valid for `create` call not explicitly specified."""
try:
ffm_api_key: Optional[str] = os.environ['FFM_API_KEY']
except:
ffm_api_key: Optional[str] = None
@property
def _default_params(self) -> Dict[str, Any]:
"""Get the default parameters for calling FFM API."""
normal_params = {
"temperature": self.temperature,
"max_new_tokens": self.max_new_tokens,
"top_p": self.top_p,
"frequence_penalty": self.frequence_penalty,
"top_k": self.top_k,
}
return {**normal_params, **self.model_kwargs}
def _call(
self,
prompt,
stop: Optional[List[str]] = None,
**kwargs: Any,
) -> str:
if self.stop is not None and stop is not None:
raise ValueError("`stop` found in both the input and default params.")
elif self.stop is not None:
stop = self.stop
elif stop is None:
stop = []
params = {**self._default_params, "stop": stop, **kwargs}
parameter_payload = {"parameters": params, "messages": prompt, "model": self.model}
# HTTP headers for authorization
headers = {
"X-API-KEY": self.ffm_api_key,
"X-API-HOST": "afs-inference",
"Content-Type": "application/json",
}
endpoint_url = f"{self.base_url}/models/conversation"
# send request
try:
response = requests.post(
url=endpoint_url,
headers=headers,
data=json.dumps(parameter_payload, ensure_ascii=False).encode("utf8"),
stream=False,
)
response.encoding = "utf-8"
generated_text = response.json()
if response.status_code != 200:
detail = generated_text.get("detail")
raise ValueError(
f"FormosaFoundationModel endpoint_url: {endpoint_url}\n"
f"error raised with status code {response.status_code}\n"
f"Details: {detail}\n"
)
except requests.exceptions.RequestException as e: # This is the correct syntax
raise ValueError(f"FormosaFoundationModel error raised by inference endpoint: {e}\n")
if generated_text.get("detail") is not None:
detail = generated_text["detail"]
raise ValueError(
f"FormosaFoundationModel endpoint_url: {endpoint_url}\n"
f"error raised by inference API: {detail}\n"
)
if generated_text.get("generated_text") is None:
raise ValueError(
f"FormosaFoundationModel endpoint_url: {endpoint_url}\n"
f"Response format error: {generated_text}\n"
)
return generated_text
class ChatFormosaFoundationModel(BaseChatModel, _ChatFormosaFoundationCommon):
"""`FormosaFoundation` Chat large language models API.
The environment variable ``OPENAI_API_KEY`` set with your API key.
Example:
.. code-block:: python
ffm = ChatFormosaFoundationModel(model_name="llama2-7b-chat-meta")
"""
@property
def _llm_type(self) -> str:
return "ChatFormosaFoundationModel"
@property
def lc_serializable(self) -> bool:
return True
def _convert_message_to_dict(self, message: BaseMessage) -> dict:
if isinstance(message, ChatMessage):
message_dict = {"role": message.role, "content": message.content}
elif isinstance(message, HumanMessage):
message_dict = {"role": "human", "content": message.content}
elif isinstance(message, AIMessage):
message_dict = {"role": "assistant", "content": message.content}
elif isinstance(message, SystemMessage):
message_dict = {"role": "system", "content": message.content}
else:
raise ValueError(f"Got unknown type {message}")
return message_dict
def _create_conversation_messages(
self,
messages: List[BaseMessage],
stop: Optional[List[str]]
) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
params: Dict[str, Any] = {**self._default_params}
if stop is not None:
if "stop" in params:
raise ValueError("`stop` found in both the input and default params.")
params["stop"] = stop
message_dicts = [self._convert_message_to_dict(m) for m in messages]
return message_dicts, params
def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult:
chat_generation = ChatGeneration(
message = AIMessage(content=response.get("generated_text")),
generation_info = {
"token_usage": response.get("generated_tokens"),
"model": self.model
}
)
return ChatResult(generations=[chat_generation])
def _generate(
self,
messages: List[BaseMessage],
stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any,
) -> ChatResult:
message_dicts, params = self._create_message_dicts(messages, stop)
params = {**params, **kwargs}
response = self._call(prompt=message_dicts)
if type(response) is str: # response is not the format of dictionary
return response
return self._create_chat_result(response)
async def _agenerate(
self, messages: List[BaseMessage], stop: Optional[List[str]] = None
) -> ChatResult:
pass
def _create_message_dicts(
self,
messages: List[BaseMessage],
stop: Optional[List[str]]
) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
params = self._default_params
if stop is not None:
if "stop" in params:
raise ValueError("`stop` found in both the input and default params.")
params["stop"] = stop
message_dicts = [self._convert_message_to_dict(m) for m in messages]
return message_dicts, params
from langchain_ffm import ChatFormosaFoundationModel
llm = ChatFormosaFoundationModel(model="ffm-llama3-70b-chat", temperature=0.01)
response = llm.invoke("Aespa的成員有誰?")
print(response.content)
程式碼結果探討 🧐:
今天簡單了解了 LangChain 框架是什麼,然後如何透過 LangChain 框架使用 LLMs。那後面會繼續來分享更多的 LangChain 框架的應用,希望可以讓原本覺得 LangChain 很麻煩的人或者覺得 LangChain 框架很複雜的人知道 LangChain 的好,因為目前主流的服務 LangChain 都有整合進框架之中。
悠閒的 Happy Friday,雖然沒有放假,但可以做自己的事。我就 focus 在鐵人賽文章,這樣晚上可以跟女朋友去約會了🥰。啊我同事玩了一整個下午的 LOL 和全明星街球派對哈哈哈,難得有這麼清閒的下午,還有薪水可以拿,但是他一直輸是蠻好笑的哈哈哈