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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
2.7k

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

Avatar for Matsumoto Kazutaka

Matsumoto Kazutaka

May 18, 2023
Tweet

More Decks by Matsumoto Kazutaka

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