Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ChatGPTを使ったSlackbotの実装いろいろ紹介

2.5k

 ChatGPTを使ったSlackbotの実装いろいろ紹介

Matsumoto Kazutaka

May 18, 2023
Tweet

Transcript

  1. ストリーミング機能の実装: コード 一部抜粋 class StreamingAsyncSlackCallbackHandler(AsyncCallbackHandler): ... async def on_llm_start( self,

    serialized: Dict[str, Any], prompts: List[str], **kwargs: Any ) -> None: # Slack にメッセージを送信 self.initial_message = loop = asyncio.get_event_loop() self.task = loop.create_task(self._update_chat()) async def _update_chat(self): while True: await asyncio.sleep(0.5) # Slack のメッセージを更新 ...
  2. 発言者がだれかを認識させる実装: 実装 LangChainで、Modelにチャット形式でテキストを渡すには、一般に MessagePromptTemplate を使う。 ただし、ユーザ名に system や human といったものしか指定できない

    なので、 MessagePromptTemplate を使わず、 PromptTemplate をつかった 今ドキュメント見てると ChatMessagePromptTemplate があり、ここでroleを設定でき そうであった
  3. チャンネルごとにプロンプトの設定できる機能: コード 1/2 Toolの例 class ChannelConfigurationThreadModeTool(BaseTool): name = "ChannelConfigurationThreadModeTool" description

    = f"""A wrapper Channel Configuration Tool that can be used to set up thread mode. Input should be "True" or "False" string with one keys: "thread_mode". The value of "thread_mode" should be a "True" if the user wants to use thread, otherwise "False". Call only if Thread mode is specified. """ def _run( self, thread_mode: str, run_manager: Optional[CallbackManagerForToolRun] = None ) -> str: # DB に保存 return "Successfully set up thread mode"
  4. チャンネルごとにプロンプトの設定できる機能: コード 2/2 Agent実行する部分 llm = ChatOpenAI( model_name="gpt-3.5-turbo", temperature=0, )

    executor = initialize_agent( [ self.channel_configuration_thread_mode_tool, self.channel_configuration_prompt_tool, ], llm, agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION, ) result = await executor.arun(message)
  5. うまくいかないケース1: 原因 LLMからの出力は以下のどちらかが前提で書かれている Final Answer: が出力に含まれている テキストが```の文字列ででスプリットできる # 実際のコード抜粋 FINAL_ANSWER_ACTION

    = "Final Answer:" if FINAL_ANSWER_ACTION in text: return AgentFinish(...) try: action = text.split("```")[1] response = json.loads(action.strip()) return AgentAction(response["action"], response["action_input"], text) except Exception: raise OutputParserException(f"Could not parse LLM output: {text}") 今回はレスポンスはその両方を満たしていない
  6. うまくいかないケース1: なぜこんな実装に? Agentのプロンプトを見るとわかる Agentのプロンプトがレスポンスの形式を指定している 次のActionがあれば```で囲まれたActionのInput 最後ならFinal Answerの文字列を返すよう指定 # 一部抜粋 ALWAYS

    use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: ''' $JSON_BLOB ''' Observation: the result of the action ... (this Thought/Action/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question"""
  7. うまくいかないケース2: 解決方法 わからん(2度目) プロンプトをいじったけど変わらず 結局: json_loadの判定をmultiに対応できるようにした while actions: response, idx

    = decoder.raw_decode(actions.strip()) responses.append(response) next_idx = actions.find('{', idx) if next_idx == -1: break actions = actions[next_idx:] agent_actions = [ AgentAction(response["action"], response["action_input"], text) for response in responses ] return agent_actions[0] if len(agent_actions) == 1 else agent_actions
  8. うまくいかないケース2: 注意点 OutputParserの抽象クラスは下記のようになっている class AgentOutputParser(BaseOutputParser): @abstractmethod def parse(self, text: str)

    -> Union[AgentAction, AgentFinish]: """Parse text into agent action/finish.""" 前ページの変更をすると、出力の型が抽象クラスと異なることに注意する 変更後: Union[Union[AgentAction, List[AgentAction], AgentFinish]] ただしOutputParserを呼び出す部分は、実は List[AgentAction] に対応している ので動く 参考: https://github.com/hwchase17/langchain/pull/2362