packages/blueprints/gen-ai-chatbot/static-assets/chatbot-genai-components/backend/python/app/agents/langchain.py (125 lines of code) (raw):

"""LangChain adaptor stuffs. """ import logging import os from typing import Any, Iterator, Optional from anthropic.types import ContentBlockDeltaEvent, MessageDeltaEvent, MessageStopEvent from app.bedrock import ( calculate_price, compose_args, get_bedrock_response, get_model_id, ) from app.config import DEFAULT_GENERATION_CONFIG as DEFAULT_CLAUDE_GENERATION_CONFIG from app.config import DEFAULT_MISTRAL_GENERATION_CONFIG from app.repositories.models.conversation import ContentModel, MessageModel from app.repositories.models.custom_bot import GenerationParamsModel from app.routes.schemas.conversation import type_model_name from app.stream import BaseStreamHandler, OnStopInput, get_stream_handler_type from app.utils import get_anthropic_client, is_anthropic_model from langchain_core.callbacks.manager import CallbackManagerForLLMRun from langchain_core.language_models import LLM from langchain_core.outputs import GenerationChunk logger = logging.getLogger(__name__) ENABLE_MISTRAL = os.environ.get("ENABLE_MISTRAL", "") == "true" DEFAULT_GENERATION_CONFIG = ( DEFAULT_MISTRAL_GENERATION_CONFIG if ENABLE_MISTRAL else DEFAULT_CLAUDE_GENERATION_CONFIG ) class BedrockLLM(LLM): """A wrapper class for the LangChain's interface. Note that this class only handle simple prompt template and can not handle multi-tern conversation. Reason is that LangChain's interface and Bedrock Claude Chat interface are not fully compatible. """ model: type_model_name generation_params: GenerationParamsModel stream_handler: BaseStreamHandler @classmethod def from_model( cls, model: type_model_name, generation_params: Optional[GenerationParamsModel] = None, ): generation_params = generation_params or GenerationParamsModel( **DEFAULT_GENERATION_CONFIG ) stream_handler = get_stream_handler_type(model).from_model(model) return cls( model=model, generation_params=generation_params, stream_handler=stream_handler, ) def __prepare_args_from_prompt(self, prompt: str, stream: bool) -> dict: """Prepare arguments from the given prompt.""" message = MessageModel( role="user", content=[ ContentModel( content_type="text", media_type=None, body=prompt, ) ], model=self.model, children=[], parent=None, create_time=0, feedback=None, used_chunks=None, thinking_log=None, ) args = compose_args( [message], self.model, instruction=None, stream=stream, generation_params=self.generation_params, ) return args def _call( self, prompt: str, stop: Optional[list[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> str: args = self.__prepare_args_from_prompt(prompt, stream=False) if self.is_anthropic_model: client = get_anthropic_client() response = client.messages.create(**args) reply_txt = response.content[0].text else: response = get_bedrock_response(args) # type: ignore reply_txt = response["outputs"][0]["text"] # type: ignore return reply_txt def _stream( self, prompt: str, stop: Optional[list[str]] = None, run_manager: Optional[CallbackManagerForLLMRun] = None, **kwargs: Any, ) -> Iterator[GenerationChunk]: args = self.__prepare_args_from_prompt(prompt, stream=True) def _on_stream(token: str, **kwargs) -> GenerationChunk: if run_manager: run_manager.on_llm_new_token(token) chunk = GenerationChunk(text=token) return chunk def _on_stop(arg: OnStopInput, **kwargs) -> GenerationChunk: chunk = GenerationChunk( text="", generation_info={ "stop_reason": arg.stop_reason, "input_token_count": arg.input_token_count, "output_token_count": arg.output_token_count, "price": arg.price, }, ) return chunk self.stream_handler.bind(on_stream=_on_stream, on_stop=_on_stop) yield from self.stream_handler.run(args) @property def is_anthropic_model(self) -> bool: return is_anthropic_model(get_model_id(self.model)) @property def _identifying_params(self) -> dict[str, Any]: """Return a dictionary of identifying parameters.""" return { # The model name allows users to specify custom token counting # rules in LLM monitoring applications (e.g., in LangSmith users # can provide per token pricing for their model and monitor # costs for the given LLM.) "model_name": "BedrockClaudeChatModel", } @property def _llm_type(self) -> str: """Get the type of language model used by this chat model. Used for logging purposes only.""" return "bedrock-claude-chat"