From 9937ac666b8f758853098fe6d71c8b6deabb3df9 Mon Sep 17 00:00:00 2001 From: vishnu r kumar Date: Wed, 11 Sep 2024 13:32:48 +0530 Subject: [PATCH 1/9] use llm node for custom routing rules --- flo_ai/router/flo_custom_router.py | 121 ++++++++++++++++++++++++++++ flo_ai/router/flo_router_factory.py | 3 + flo_ai/yaml/flo_team_builder.py | 11 ++- 3 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 flo_ai/router/flo_custom_router.py diff --git a/flo_ai/router/flo_custom_router.py b/flo_ai/router/flo_custom_router.py new file mode 100644 index 00000000..c56a749a --- /dev/null +++ b/flo_ai/router/flo_custom_router.py @@ -0,0 +1,121 @@ +from flo_ai.yaml.flo_team_builder import RouterConfig +from flo_ai.router.flo_router import FloRouter +from langgraph.graph import StateGraph, END, START +from flo_ai.state.flo_state import TeamFloAgentState +from flo_ai.models.flo_routed_team import FloRoutedTeam +from flo_ai.models.flo_team import FloTeam +from flo_ai.state.flo_session import FloSession +from flo_ai.helpers.utils import agent_name_from_randomized_name, randomize_name +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.output_parsers.openai_functions import JsonOutputFunctionsParser + +class FloCustomRouter(FloRouter): + + def __init__(self, session: FloSession, flo_team: FloTeam, config: RouterConfig): + self.llm = session.llm + super().__init__(session=session, name=randomize_name(config.name), + flo_team=flo_team, executor=None, config=config) + + def build_router_fn(self, members, rule): + def router_fn(state: TeamFloAgentState): + conditional_map = {k: k for k in members} + + prompt = ChatPromptTemplate.from_messages( + [ + MessagesPlaceholder(variable_name="messages"), + ( + "system", + "Given the conversation above, who should act next? Select one of: {members}. The rule is given below:" + ), + ('system', rule) + ] + ).partial(members=", ".join(members)) + + + function_def = { + "name": "route", + "description": "Select the next role.", + "parameters": { + "title": "routeSchema", + "type": "object", + "properties": { + "next": { + "title": "Next", + "anyOf": [ + {"enum": members}, + ], + } + }, + "required": ["next"], + } + } + + chain = prompt | self.llm.bind_functions(functions=[function_def], function_call="route") | JsonOutputFunctionsParser() + output = chain.invoke(state) + + next = output['next'] + state['next'] = next + + return conditional_map[next] + + return router_fn + + def build_agent_graph(self): + flo_agent_nodes = [self.build_node(flo_agent) for flo_agent in self.members] + workflow = StateGraph(TeamFloAgentState) + + for flo_agent_node in flo_agent_nodes: + agent_name = agent_name_from_randomized_name(flo_agent_node.name) + workflow.add_node(agent_name, flo_agent_node.func) + + config = self.config + workflow.add_edge(START, config.start_node) + for edge_config in config.edges: + edge = edge_config.edge + if len(edge) > 2: + if edge_config.type == 'conditional_llm': + members = edge[1:] + router = self.build_router_fn(members, edge_config.rule) + workflow.add_conditional_edges(edge[0], router, {item: item for item in members}) + else: + workflow.add_edge(edge[0], edge[1]) + + if isinstance(config.end_node, list): + for node in config.end_node: + workflow.add_edge(node, END) + else: + workflow.add_edge(config.end_node, END) + + workflow_graph = workflow.compile() + + return FloRoutedTeam(self.flo_team.name, workflow_graph) + + def build_team_graph(self): + flo_team_entry_chains = [self.build_node_for_teams(flo_agent) for flo_agent in self.members] + + super_graph = StateGraph(TeamFloAgentState) + + for flo_team_chain in flo_team_entry_chains: + agent_name = agent_name_from_randomized_name(flo_team_chain.name) + super_graph.add_node(agent_name, flo_team_chain.func) + + config = self.config + super_graph.add_edge(START, config.start_node) + for edge_config in config.edges: + edge = edge_config.edge + if len(edge) > 2: + teams = edge[1:] + router = self.build_router_fn(teams, edge_config.rule) + super_graph.add_conditional_edges(edge[0], router, {item: item for item in teams}) + else: + super_graph.add_edge(edge[0], edge[1]) + + if isinstance(config.end_node, list): + for node in config.end_node: + super_graph.add_edge(node, END) + else: + super_graph.add_edge(config.end_node, END) + + workflow_graph = super_graph.compile() + + return FloRoutedTeam(self.flo_team.name, workflow_graph) \ No newline at end of file diff --git a/flo_ai/router/flo_router_factory.py b/flo_ai/router/flo_router_factory.py index f66613cf..95957be3 100644 --- a/flo_ai/router/flo_router_factory.py +++ b/flo_ai/router/flo_router_factory.py @@ -5,6 +5,7 @@ from flo_ai.yaml.flo_team_builder import TeamConfig from flo_ai.models.flo_team import FloTeam from flo_ai.router.flo_router import FloRouter +from flo_ai.router.flo_custom_router import FloCustomRouter class FloRouterFactory: @@ -16,5 +17,7 @@ def create(session: FloSession, team_config: TeamConfig, flo_team: FloTeam) -> F return FloLinear.Builder(session, flo_team, team_config.router).build() elif team_config.router.kind == 'llm': return FloLLMRouter.Builder(session, team_config.router.name, flo_team).build() + elif team_config.router.kind == 'custom': + return FloCustomRouter(session, flo_team, team_config.router) else: raise Exception("Unknown router type") diff --git a/flo_ai/yaml/flo_team_builder.py b/flo_ai/yaml/flo_team_builder.py index 66a75b2e..195b9fc1 100644 --- a/flo_ai/yaml/flo_team_builder.py +++ b/flo_ai/yaml/flo_team_builder.py @@ -1,5 +1,5 @@ from pydantic import BaseModel -from typing import List +from typing import List, Union import yaml import re from typing import Optional @@ -36,13 +36,18 @@ class AgentConfig(BaseModel): job: Optional[str] = None tools: List[ToolConfig] = [] +class EdgeConfig(BaseModel): + edge: List[str] + type: Optional[str] = None + rule: Optional[str] = None + class RouterConfig(BaseModel): name: str kind: str job: Optional[str] = None start_node: Optional[str] = None - end_node: Optional[str] = None - edges: Optional[List[List[str]]] = None + end_node: Union[Optional[str], List[str]] = None + edges: Optional[List[EdgeConfig]] = None class PlannerConfig(BaseModel): name: str From bcdc2928f5e83018d2553385f695ba6f701bf07c Mon Sep 17 00:00:00 2001 From: vishnu r kumar Date: Thu, 12 Sep 2024 19:15:47 +0530 Subject: [PATCH 2/9] add basic reflection for agents in linear routing --- flo_ai/router/flo_custom_router.py | 29 ++++++------ flo_ai/router/flo_linear.py | 41 ++++++++++------- flo_ai/router/flo_router.py | 69 +++++++++++++++++++++++++++-- flo_ai/router/flo_router_factory.py | 2 +- flo_ai/yaml/flo_team_builder.py | 6 +++ 5 files changed, 111 insertions(+), 36 deletions(-) diff --git a/flo_ai/router/flo_custom_router.py b/flo_ai/router/flo_custom_router.py index c56a749a..4b205046 100644 --- a/flo_ai/router/flo_custom_router.py +++ b/flo_ai/router/flo_custom_router.py @@ -1,4 +1,4 @@ -from flo_ai.yaml.flo_team_builder import RouterConfig +from flo_ai.yaml.flo_team_builder import TeamConfig from flo_ai.router.flo_router import FloRouter from langgraph.graph import StateGraph, END, START from flo_ai.state.flo_state import TeamFloAgentState @@ -11,10 +11,11 @@ class FloCustomRouter(FloRouter): - def __init__(self, session: FloSession, flo_team: FloTeam, config: RouterConfig): + def __init__(self, session: FloSession, flo_team: FloTeam, config: TeamConfig): self.llm = session.llm super().__init__(session=session, name=randomize_name(config.name), flo_team=flo_team, executor=None, config=config) + self.router_config = config.router def build_router_fn(self, members, rule): def router_fn(state: TeamFloAgentState): @@ -68,9 +69,9 @@ def build_agent_graph(self): agent_name = agent_name_from_randomized_name(flo_agent_node.name) workflow.add_node(agent_name, flo_agent_node.func) - config = self.config - workflow.add_edge(START, config.start_node) - for edge_config in config.edges: + router_config = self.router_config + workflow.add_edge(START, router_config.start_node) + for edge_config in router_config.edges: edge = edge_config.edge if len(edge) > 2: if edge_config.type == 'conditional_llm': @@ -80,11 +81,11 @@ def build_agent_graph(self): else: workflow.add_edge(edge[0], edge[1]) - if isinstance(config.end_node, list): - for node in config.end_node: + if isinstance(router_config.end_node, list): + for node in router_config.end_node: workflow.add_edge(node, END) else: - workflow.add_edge(config.end_node, END) + workflow.add_edge(router_config.end_node, END) workflow_graph = workflow.compile() @@ -99,9 +100,9 @@ def build_team_graph(self): agent_name = agent_name_from_randomized_name(flo_team_chain.name) super_graph.add_node(agent_name, flo_team_chain.func) - config = self.config - super_graph.add_edge(START, config.start_node) - for edge_config in config.edges: + router_config = self.router_config + super_graph.add_edge(START, router_config.start_node) + for edge_config in router_config.edges: edge = edge_config.edge if len(edge) > 2: teams = edge[1:] @@ -110,11 +111,11 @@ def build_team_graph(self): else: super_graph.add_edge(edge[0], edge[1]) - if isinstance(config.end_node, list): - for node in config.end_node: + if isinstance(router_config.end_node, list): + for node in router_config.end_node: super_graph.add_edge(node, END) else: - super_graph.add_edge(config.end_node, END) + super_graph.add_edge(router_config.end_node, END) workflow_graph = super_graph.compile() diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 1a165e0d..3c6b46c3 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -1,4 +1,4 @@ -from flo_ai.yaml.flo_team_builder import RouterConfig +from flo_ai.yaml.flo_team_builder import RouterConfig, TeamConfig, AgentConfig from flo_ai.router.flo_router import FloRouter from langgraph.graph import StateGraph, END, START from flo_ai.state.flo_state import TeamFloAgentState @@ -6,21 +6,30 @@ from flo_ai.models.flo_team import FloTeam from flo_ai.state.flo_session import FloSession from flo_ai.helpers.utils import agent_name_from_randomized_name, randomize_name - +from typing import List, Tuple +from flo_ai.models.flo_node import FloNode class FloLinear(FloRouter): - def __init__(self, session: FloSession, flo_team: FloTeam, config: RouterConfig): + def __init__(self, session: FloSession, flo_team: FloTeam, config: TeamConfig): super().__init__(session=session, name=randomize_name(config.name), flo_team=flo_team, executor=None, config=config) + self.router_config = config.router def build_agent_graph(self): - flo_agent_nodes = [self.build_node(flo_agent) for flo_agent in self.members] + flo_nodes = [self.build_node(flo_agent) for flo_agent in self.members] + flo_agent_nodes: List[FloNode] + flo_reflection_nodes: List[FloNode] + flo_agent_nodes, flo_reflection_nodes = self.differentiate_nodes(flo_nodes, self.config.agents) + workflow = StateGraph(TeamFloAgentState) - for flo_agent_node in flo_agent_nodes: - agent_name = agent_name_from_randomized_name(flo_agent_node.name) - workflow.add_node(agent_name, flo_agent_node.func) - if self.config.edges is None: + for flo_node in flo_nodes: + agent_name = agent_name_from_randomized_name(flo_node.name) + workflow.add_node(agent_name, flo_node.func) + + self.build_reflection_routes(workflow, self.config.agents, flo_reflection_nodes) + + if self.router_config.edges is None: start_node_name = agent_name_from_randomized_name(flo_agent_nodes[0].name) end_node_name = agent_name_from_randomized_name(flo_agent_nodes[-1].name) workflow.add_edge(START, start_node_name) @@ -30,11 +39,10 @@ def build_agent_graph(self): workflow.add_edge(agent1_name, agent2_name) workflow.add_edge(end_node_name, END) else: - config = self.config - workflow.add_edge(START, config.start_node) - for edge in config.edges: + workflow.add_edge(START, self.router_config.start_node) + for edge in self.router_config.edges: workflow.add_edge(edge[0], edge[1]) - workflow.add_edge(config.end_node, END) + workflow.add_edge(self.router_config.end_node, END) workflow_graph = workflow.compile() @@ -49,7 +57,7 @@ def build_team_graph(self): agent_name = agent_name_from_randomized_name(flo_team_chain.name) super_graph.add_node(agent_name, flo_team_chain.func) - if self.config.edges is None: + if self.router_config.edges is None: start_node_name = agent_name_from_randomized_name(flo_team_entry_chains[0].name) end_node_name = agent_name_from_randomized_name(flo_team_entry_chains[-1].name) super_graph.add_edge(START, start_node_name) @@ -59,11 +67,10 @@ def build_team_graph(self): super_graph.add_edge(agent1_name, agent2_name) super_graph.add_edge(end_node_name, END) else: - config = self.config - super_graph.add_edge(START, config.start_node) - for edge in config.edges: + super_graph.add_edge(START, self.router_config.start_node) + for edge in self.router_config.edges: super_graph.add_edge(edge[0], edge[1]) - super_graph.add_edge(config.end_node, END) + super_graph.add_edge(self.router_config.end_node, END) super_graph = super_graph.compile() return FloRoutedTeam(self.flo_team.name, super_graph) diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index 71333c2a..9511f30c 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -3,17 +3,19 @@ from abc import ABC, abstractmethod from flo_ai.state.flo_session import FloSession from flo_ai.models.flo_team import FloTeam -from flo_ai.yaml.flo_team_builder import RouterConfig +from flo_ai.yaml.flo_team_builder import RouterConfig, TeamConfig, AgentConfig from flo_ai.models.flo_routed_team import FloRoutedTeam from flo_ai.models.flo_agent import FloAgent from flo_ai.state.flo_state import TeamFloAgentState from flo_ai.models.flo_node import FloNode from flo_ai.constants.prompt_constants import FLO_FINISH -from langgraph.graph import END - +from langgraph.graph import END,StateGraph +from typing import List, Tuple +from flo_ai.models.flo_node import FloNode +from flo_ai.helpers.utils import agent_name_from_randomized_name class FloRouter(ABC): - def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, config: RouterConfig = None): + def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, config: TeamConfig = None): self.router_name = name self.session: FloSession = session self.flo_team: FloTeam = flo_team @@ -57,5 +59,64 @@ def router_fn(self, state: TeamFloAgentState): def build_node_for_teams(self, flo_team: FloRoutedTeam): node_builder = FloNode.Builder() return node_builder.build_from_team(flo_team) + + def differentiate_nodes(self, flo_nodes: List[FloNode], agents: List[AgentConfig] | None) -> Tuple[List[FloNode], List[FloNode]]: + node_dict = {} + agent_nodes = [] + reflection_nodes = [] + + for agent in agents: + if agent.reflection: + node_dict['reflection_nodes'] = agent.name + else: + node_dict['agent_nodes'] = agent.name + + for node in flo_nodes: + node_name = agent_name_from_randomized_name(node.name) + if node_name in node_dict['reflection_nodes']: + reflection_nodes.append(node) + elif node_name in node_dict['agent_nodes']: + agent_nodes.append(node) + + return agent_nodes, reflection_nodes + + + def build_reflection_routes(self, workflow: StateGraph, agents: List[AgentConfig] | None, reflection_nodes: List[FloNode]): + if len(reflection_nodes) == 0: + return + + reflection_routes = self.__get_reflection_routes(agents) + for reflection in reflection_routes: + parent_node = reflection['nodes'][0] + reflection_node = reflection['nodes'][1] + next = END if reflection['next'] == 'END' else reflection['next'] + + workflow.add_conditional_edges(parent_node, self.__get_refelection_routing_fn(reflection['retries'], parent_node, reflection_node, next), {reflection_node: reflection_node, next: next}) + workflow.add_edge(reflection_node, parent_node) + + @staticmethod + def __get_refelection_routing_fn(retries, parent_node, reflection_node, next): + def reflection_routing_fn(state: TeamFloAgentState): + if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == parent_node: + return next + return reflection_node + + return reflection_routing_fn + + + @staticmethod + def __get_reflection_routes(agents: List[AgentConfig] | None): + reflection_routes = [] + + for agent in agents: + if agent.reflection: + route = {} + route['nodes'] = [agent.reflection.node, agent.name] + route['retries'] = agent.reflection.retries + route['next'] = agent.reflection.next + reflection_routes.append(route) + + return reflection_routes + \ No newline at end of file diff --git a/flo_ai/router/flo_router_factory.py b/flo_ai/router/flo_router_factory.py index 95957be3..86de7b93 100644 --- a/flo_ai/router/flo_router_factory.py +++ b/flo_ai/router/flo_router_factory.py @@ -18,6 +18,6 @@ def create(session: FloSession, team_config: TeamConfig, flo_team: FloTeam) -> F elif team_config.router.kind == 'llm': return FloLLMRouter.Builder(session, team_config.router.name, flo_team).build() elif team_config.router.kind == 'custom': - return FloCustomRouter(session, flo_team, team_config.router) + return FloCustomRouter(session, flo_team, team_config) else: raise Exception("Unknown router type") diff --git a/flo_ai/yaml/flo_team_builder.py b/flo_ai/yaml/flo_team_builder.py index 195b9fc1..3985ffd7 100644 --- a/flo_ai/yaml/flo_team_builder.py +++ b/flo_ai/yaml/flo_team_builder.py @@ -29,12 +29,18 @@ class ToolConfig(BaseModel): properties: Optional[List[KeyValueArgs]] = None filters: Optional[List[FilterArgs]] = None +class ReflectionConfig(BaseModel): + node: str + retries: int + next: str + class AgentConfig(BaseModel): name: str role: Optional[str] = None kind: Optional[str] = None job: Optional[str] = None tools: List[ToolConfig] = [] + reflection: Optional[ReflectionConfig] = None class EdgeConfig(BaseModel): edge: List[str] From 1edce1760b0be08c3578b7939182488417429c0b Mon Sep 17 00:00:00 2001 From: vishnu r kumar Date: Thu, 19 Sep 2024 20:42:10 +0530 Subject: [PATCH 3/9] make config part of session and bring consistency to build functions --- flo_ai/builders/yaml_builder.py | 35 ++++++------------- flo_ai/core.py | 15 ++++---- flo_ai/factory/agent_factory.py | 5 +-- flo_ai/models/flo_team.py | 23 ++++-------- flo_ai/router/flo_custom_router.py | 16 +++++++-- flo_ai/router/flo_linear.py | 8 ++--- flo_ai/router/flo_llm_router.py | 5 +-- flo_ai/router/flo_router.py | 3 +- flo_ai/router/flo_router_factory.py | 19 +++++----- flo_ai/router/flo_supervisor.py | 8 ++--- flo_ai/state/flo_session.py | 7 ++-- .../yaml/{flo_team_builder.py => config.py} | 0 12 files changed, 65 insertions(+), 79 deletions(-) rename flo_ai/yaml/{flo_team_builder.py => config.py} (100%) diff --git a/flo_ai/builders/yaml_builder.py b/flo_ai/builders/yaml_builder.py index 2772de37..428667af 100644 --- a/flo_ai/builders/yaml_builder.py +++ b/flo_ai/builders/yaml_builder.py @@ -1,53 +1,38 @@ from flo_ai.models.flo_team import FloTeam from flo_ai.models.flo_agent import FloAgent -from flo_ai.yaml.flo_team_builder import (FloRoutedTeamConfig, TeamConfig, - AgentConfig, FloAgentConfig) +from flo_ai.yaml.config import (FloRoutedTeamConfig, TeamConfig, AgentConfig, FloAgentConfig) from flo_ai.models.flo_executable import ExecutableFlo -from flo_ai.models.flo_planner import FloPlannerBuilder from flo_ai.state.flo_session import FloSession from flo_ai.router.flo_router_factory import FloRouterFactory from flo_ai.factory.agent_factory import AgentFactory -from typing import Union -def build_supervised_team( - session: FloSession, - flo_config: Union[FloRoutedTeamConfig, FloAgentConfig]) -> ExecutableFlo: +def build_supervised_team(session: FloSession) -> ExecutableFlo: + flo_config = session.config if isinstance(flo_config, FloRoutedTeamConfig): team_config: TeamConfig = flo_config.team - team = parse_and_build_subteams(session, team_config, session.tools) + team = parse_and_build_subteams(session, team_config) return team elif isinstance(flo_config, FloAgentConfig): agent_config: AgentConfig = flo_config.agent - agent = AgentFactory.create(session, agent_config, session.tools) + agent = AgentFactory.create(session, agent_config) return agent -def parse_and_build_subteams( - session: FloSession, - team_config: TeamConfig, - tool_map) -> ExecutableFlo: +def parse_and_build_subteams(session: FloSession, team_config: TeamConfig) -> ExecutableFlo: flo_team = None if team_config.agents: agents = [] for agent in team_config.agents: - flo_agent: FloAgent = AgentFactory.create(session, agent, tool_map) + flo_agent: FloAgent = AgentFactory.create(session, agent) agents.append(flo_agent) - flo_team = FloTeam.Builder( - session=session, - name=team_config.name, - members=agents - ).build() + flo_team = FloTeam.Builder(team_config, members=agents).build() router = FloRouterFactory.create(session, team_config, flo_team) flo_routed_team = router.build_routed_team() else: flo_teams = [] for subteam in team_config.subteams: - flo_subteam = parse_and_build_subteams(session, subteam, tool_map) + flo_subteam = parse_and_build_subteams(session, subteam) flo_teams.append(flo_subteam) - flo_team = FloTeam.Builder( - session=session, - name=team_config.name, - members=flo_teams - ).build() + flo_team = FloTeam.Builder(team_config, members=flo_teams).build() router = FloRouterFactory.create(session, team_config, flo_team) flo_routed_team = router.build_routed_team() return flo_routed_team diff --git a/flo_ai/core.py b/flo_ai/core.py index 30e4a6fc..40d3f7dc 100644 --- a/flo_ai/core.py +++ b/flo_ai/core.py @@ -1,4 +1,4 @@ -from flo_ai.yaml.flo_team_builder import to_supervised_team +from flo_ai.yaml.config import to_supervised_team from flo_ai.builders.yaml_builder import build_supervised_team, FloRoutedTeamConfig from typing import ( Any, @@ -10,11 +10,9 @@ class Flo: - def __init__(self, - session: FloSession, - config: FloRoutedTeamConfig) -> None: - self.config = config - self.runnable: ExecutableFlo = build_supervised_team(session, config) + def __init__(self, session: FloSession) -> None: + self.runnable: ExecutableFlo = build_supervised_team(session) + self.session = session def stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: return self.runnable.stream(query, config) @@ -22,9 +20,8 @@ def stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: def invoke(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: return self.runnable.invoke(query, config) - @staticmethod - def build(session: FloSession, yaml: str): - return Flo(session, to_supervised_team(yaml)) + def build(self): + return Flo(self.session) def draw(self, xray=True): return self.runnable.draw(xray) diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 949f18b3..3a56888c 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -1,5 +1,5 @@ from flo_ai.state.flo_session import FloSession -from flo_ai.yaml.flo_team_builder import (AgentConfig) +from flo_ai.yaml.config import (AgentConfig) from flo_ai.models.flo_agent import FloAgent from flo_ai.models.flo_llm_agent import FloLLMAgent from flo_ai.models.flo_executable import ExecutableFlo @@ -14,8 +14,9 @@ class AgentKinds(Enum): class AgentFactory(): @staticmethod - def create(session: FloSession, agent: AgentConfig, tool_map): + def create(session: FloSession, agent: AgentConfig): kind = agent.kind + tool_map = session.tools if kind is not None: agent_kind = getattr(AgentKinds, kind, None) if agent_kind is None: diff --git a/flo_ai/models/flo_team.py b/flo_ai/models/flo_team.py index db21a709..e2a7313a 100644 --- a/flo_ai/models/flo_team.py +++ b/flo_ai/models/flo_team.py @@ -1,30 +1,21 @@ from flo_ai.models.flo_member import FloMember -from flo_ai.state.flo_session import FloSession from flo_ai.helpers.utils import randomize_name - +from flo_ai.yaml.config import TeamConfig class FloTeam(): - def __init__(self, - session: FloSession, - name: str, - members: list[FloMember]) -> None: - self.name = name - self.session = session + def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: + self.name = randomize_name(team_config.name) + self.config = team_config self.members = members class Builder: - def __init__(self, - session: FloSession, - name: str, - members: list[FloMember]) -> None: - self.name = randomize_name(name) - self.session = session + def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: + self.team_config = team_config self.members = members self.member_names= list(map(lambda x: x.name, self.members)) def build(self): return FloTeam( - name = self.name, - session=self.session, + team_config=self.team_config, members=self.members ) \ No newline at end of file diff --git a/flo_ai/router/flo_custom_router.py b/flo_ai/router/flo_custom_router.py index 4b205046..6fdab3b5 100644 --- a/flo_ai/router/flo_custom_router.py +++ b/flo_ai/router/flo_custom_router.py @@ -1,4 +1,4 @@ -from flo_ai.yaml.flo_team_builder import TeamConfig +from flo_ai.yaml.config import TeamConfig from flo_ai.router.flo_router import FloRouter from langgraph.graph import StateGraph, END, START from flo_ai.state.flo_state import TeamFloAgentState @@ -11,7 +11,7 @@ class FloCustomRouter(FloRouter): - def __init__(self, session: FloSession, flo_team: FloTeam, config: TeamConfig): + def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam): self.llm = session.llm super().__init__(session=session, name=randomize_name(config.name), flo_team=flo_team, executor=None, config=config) @@ -119,4 +119,14 @@ def build_team_graph(self): workflow_graph = super_graph.compile() - return FloRoutedTeam(self.flo_team.name, workflow_graph) \ No newline at end of file + return FloRoutedTeam(self.flo_team.name, workflow_graph) + + class Builder(): + + def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam,) -> None: + self.session = session + self.config = config + self.team = flo_team + + def build(self): + return FloCustomRouter(self.session, self.config, self.team, ) \ No newline at end of file diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 3c6b46c3..4f5ec5a6 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -1,4 +1,4 @@ -from flo_ai.yaml.flo_team_builder import RouterConfig, TeamConfig, AgentConfig +from flo_ai.yaml.config import TeamConfig from flo_ai.router.flo_router import FloRouter from langgraph.graph import StateGraph, END, START from flo_ai.state.flo_state import TeamFloAgentState @@ -10,7 +10,7 @@ from flo_ai.models.flo_node import FloNode class FloLinear(FloRouter): - def __init__(self, session: FloSession, flo_team: FloTeam, config: TeamConfig): + def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam, ): super().__init__(session=session, name=randomize_name(config.name), flo_team=flo_team, executor=None, config=config) self.router_config = config.router @@ -77,11 +77,11 @@ def build_team_graph(self): class Builder(): - def __init__(self, session: FloSession, flo_team: FloTeam, config: RouterConfig) -> None: + def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam,) -> None: self.config = config self.session = session self.team = flo_team def build(self): - return FloLinear(self.session, self.team, self.config) + return FloLinear(self.session, self.config, self.team) diff --git a/flo_ai/router/flo_llm_router.py b/flo_ai/router/flo_llm_router.py index 388dd6dc..7b6900df 100644 --- a/flo_ai/router/flo_llm_router.py +++ b/flo_ai/router/flo_llm_router.py @@ -11,6 +11,7 @@ from flo_ai.models.flo_routed_team import FloRoutedTeam from langgraph.graph import StateGraph from flo_ai.state.flo_state import TeamFloAgentState +from flo_ai.yaml.config import TeamConfig class StateUpdateComponent: def __init__(self, name: str, session: FloSession) -> None: @@ -69,12 +70,12 @@ def build_team_graph(self): class Builder: def __init__(self, session: FloSession, - name: str, + team_config: TeamConfig, flo_team: FloTeam, router_prompt: ChatPromptTemplate = None, llm: Union[BaseLanguageModel, None] = None) -> None: - self.name = randomize_name(name) + self.name = randomize_name(team_config.router.name) self.session = session self.llm = llm if llm is not None else session.llm self.flo_team = flo_team diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index 9511f30c..e1eb6e9c 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -1,9 +1,8 @@ -import functools from abc import ABC, abstractmethod from flo_ai.state.flo_session import FloSession from flo_ai.models.flo_team import FloTeam -from flo_ai.yaml.flo_team_builder import RouterConfig, TeamConfig, AgentConfig +from flo_ai.yaml.config import TeamConfig, AgentConfig from flo_ai.models.flo_routed_team import FloRoutedTeam from flo_ai.models.flo_agent import FloAgent from flo_ai.state.flo_state import TeamFloAgentState diff --git a/flo_ai/router/flo_router_factory.py b/flo_ai/router/flo_router_factory.py index 86de7b93..279f84a4 100644 --- a/flo_ai/router/flo_router_factory.py +++ b/flo_ai/router/flo_router_factory.py @@ -2,7 +2,7 @@ from flo_ai.router.flo_supervisor import FloSupervisor from flo_ai.router.flo_llm_router import FloLLMRouter from flo_ai.router.flo_linear import FloLinear -from flo_ai.yaml.flo_team_builder import TeamConfig +from flo_ai.yaml.config import TeamConfig from flo_ai.models.flo_team import FloTeam from flo_ai.router.flo_router import FloRouter from flo_ai.router.flo_custom_router import FloCustomRouter @@ -11,13 +11,14 @@ class FloRouterFactory: @staticmethod def create(session: FloSession, team_config: TeamConfig, flo_team: FloTeam) -> FloRouter: - if team_config.router.kind == 'supervisor': - return FloSupervisor.Builder(session, team_config.router.name, flo_team).build() - elif team_config.router.kind == 'linear': - return FloLinear.Builder(session, flo_team, team_config.router).build() - elif team_config.router.kind == 'llm': - return FloLLMRouter.Builder(session, team_config.router.name, flo_team).build() - elif team_config.router.kind == 'custom': - return FloCustomRouter(session, flo_team, team_config) + router_kind = team_config.router.kind + if router_kind == 'supervisor': + return FloSupervisor.Builder(session, team_config, flo_team).build() + elif router_kind == 'linear': + return FloLinear.Builder(session, team_config, flo_team).build() + elif router_kind == 'llm': + return FloLLMRouter.Builder(session, team_config, flo_team).build() + elif router_kind == 'custom': + return FloCustomRouter.Builder(session, team_config, flo_team).build() else: raise Exception("Unknown router type") diff --git a/flo_ai/router/flo_supervisor.py b/flo_ai/router/flo_supervisor.py index d340e3b4..f1fc5577 100644 --- a/flo_ai/router/flo_supervisor.py +++ b/flo_ai/router/flo_supervisor.py @@ -8,9 +8,7 @@ from flo_ai.helpers.utils import randomize_name from flo_ai.router.flo_llm_router import FloLLMRouter, StateUpdateComponent from flo_ai.models.flo_team import FloTeam -from flo_ai.models.flo_routed_team import FloRoutedTeam -from langgraph.graph import StateGraph -from flo_ai.state.flo_state import TeamFloAgentState +from flo_ai.yaml.config import TeamConfig # TODO, maybe add description about what team members can do supervisor_system_message = ( @@ -38,11 +36,11 @@ def __init__(self, class Builder: def __init__(self, session: FloSession, - name: str, + team_config: TeamConfig, flo_team: FloTeam, llm: Union[BaseLanguageModel, None] = None) -> None: # TODO add validation for reporteess - self.name = randomize_name(name) + self.name = randomize_name(team_config.router.name) self.session = session self.llm = llm if llm is not None else session.llm self.flo_team = flo_team diff --git a/flo_ai/state/flo_session.py b/flo_ai/state/flo_session.py index a177ce79..0630bc78 100644 --- a/flo_ai/state/flo_session.py +++ b/flo_ai/state/flo_session.py @@ -1,8 +1,10 @@ from langchain_core.language_models import BaseLanguageModel from langchain_core.tools import BaseTool - +from flo_ai.yaml.config import to_supervised_team +from typing import Union +from flo_ai.yaml.config import (FloRoutedTeamConfig, FloAgentConfig) class FloSession: - def __init__(self, llm: BaseLanguageModel, loop_size: int = 2, max_loop: int = 3) -> None: + def __init__(self, llm: BaseLanguageModel, yaml_config: str | None, loop_size: int = 2, max_loop: int = 3) -> None: self.llm = llm self.tools = dict() self.counter = dict() @@ -10,6 +12,7 @@ def __init__(self, llm: BaseLanguageModel, loop_size: int = 2, max_loop: int = 3 self.pattern_series = dict() self.loop_size: int = loop_size self.max_loop: int = max_loop + self.config: Union[FloRoutedTeamConfig, FloAgentConfig] = to_supervised_team(yaml_str=yaml_config) if isinstance(yaml_config, str) else None def register_tool(self, name: str, tool: BaseTool): self.tools[name] = tool diff --git a/flo_ai/yaml/flo_team_builder.py b/flo_ai/yaml/config.py similarity index 100% rename from flo_ai/yaml/flo_team_builder.py rename to flo_ai/yaml/config.py From b9bf81898c7041729a63512af6f2559968038eeb Mon Sep 17 00:00:00 2001 From: vishnu r kumar Date: Fri, 20 Sep 2024 00:56:29 +0530 Subject: [PATCH 4/9] make reflection nodes a part of flo team --- flo_ai/builders/yaml_builder.py | 10 ++++- flo_ai/factory/agent_factory.py | 5 +-- flo_ai/models/flo_agent.py | 16 +++++--- flo_ai/models/flo_llm_agent.py | 23 +++++------ flo_ai/models/flo_node.py | 5 ++- flo_ai/models/flo_team.py | 9 +++-- flo_ai/router/flo_linear.py | 8 ++-- flo_ai/router/flo_router.py | 68 +++++++++++++++------------------ flo_ai/state/flo_state.py | 1 + flo_ai/yaml/config.py | 10 +++-- 10 files changed, 81 insertions(+), 74 deletions(-) diff --git a/flo_ai/builders/yaml_builder.py b/flo_ai/builders/yaml_builder.py index 428667af..92e1b2e1 100644 --- a/flo_ai/builders/yaml_builder.py +++ b/flo_ai/builders/yaml_builder.py @@ -21,10 +21,16 @@ def parse_and_build_subteams(session: FloSession, team_config: TeamConfig) -> Ex flo_team = None if team_config.agents: agents = [] + reflection_agents = [] + for agent in team_config.agents: flo_agent: FloAgent = AgentFactory.create(session, agent) - agents.append(flo_agent) - flo_team = FloTeam.Builder(team_config, members=agents).build() + if agent.use == 'reflection': + reflection_agents.append(flo_agent) + else: + agents.append(flo_agent) + + flo_team = FloTeam.Builder(team_config, members=agents, reflection_agents=reflection_agents).build() router = FloRouterFactory.create(session, team_config, flo_team) flo_routed_team = router.build_routed_team() else: diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 3a56888c..4bdc90eb 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -33,15 +33,14 @@ def __create_agentic_agent(session: FloSession, agent: AgentConfig, tool_map) -> tools = [tool_map[tool.name] for tool in agent.tools] flo_agent: FloAgent = FloAgent.Builder( session, - agent.name, - agent.job, + agent, tools ).build() return flo_agent @staticmethod def __create_llm_agent(session: FloSession, agent: AgentConfig) -> FloLLMAgent: - builder = FloLLMAgent.Builder(session, agent.name, agent.job, agent.role) + builder = FloLLMAgent.Builder(session, agent) llm_agent: FloLLMAgent = builder.build() return llm_agent diff --git a/flo_ai/models/flo_agent.py b/flo_ai/models/flo_agent.py index b82083a3..fe5360cf 100644 --- a/flo_ai/models/flo_agent.py +++ b/flo_ai/models/flo_agent.py @@ -8,29 +8,33 @@ from flo_ai.models.flo_executable import ExecutableFlo from flo_ai.state.flo_session import FloSession from typing import Union, Optional +from flo_ai.yaml.config import AgentConfig class FloAgent(ExecutableFlo): def __init__(self, agent: Runnable, executor: AgentExecutor, - name: str) -> None: - super().__init__(name, executor, "agent") + config: AgentConfig) -> None: + super().__init__(config.name, executor, "agent") self.agent: Runnable = agent, self.executor: AgentExecutor = executor + self.config: AgentConfig = config class Builder: def __init__(self, session: FloSession, - name: str, - prompt: Union[ChatPromptTemplate, str], + config: AgentConfig, tools: list[BaseTool], verbose: bool = True, role: Optional[str] = None, llm: Union[BaseLanguageModel, None] = None, return_intermediate_steps: bool = False, handle_parsing_errors: bool = True) -> None: - self.name: str = randomize_name(name) + prompt: Union[ChatPromptTemplate, str] = config.job + + self.name: str = randomize_name(config.name) self.llm = llm if llm is not None else session.llm + self.config = config # TODO improve to add more context of what other agents are available system_prompts = [("system", "You are a {}".format(role)), ("system", prompt)] if role is not None else [("system", prompt)] system_prompts.append(MessagesPlaceholder(variable_name="messages")) @@ -51,4 +55,4 @@ def build(self) -> AgentExecutor: verbose=self.verbose, return_intermediate_steps=self.return_intermediate_steps, handle_parsing_errors=self.handle_parsing_errors) - return FloAgent(agent, executor, self.name) + return FloAgent(agent, executor, self.config) diff --git a/flo_ai/models/flo_llm_agent.py b/flo_ai/models/flo_llm_agent.py index 5b3b9111..f033efd4 100644 --- a/flo_ai/models/flo_llm_agent.py +++ b/flo_ai/models/flo_llm_agent.py @@ -1,40 +1,41 @@ -from langchain_core.tools import BaseTool from langchain_core.runnables import Runnable -from langchain.agents import create_tool_calling_agent from langchain_core.runnables import Runnable from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from flo_ai.helpers.utils import randomize_name from flo_ai.models.flo_executable import ExecutableFlo from flo_ai.state.flo_session import FloSession -from typing import Union, Optional +from typing import Union from langchain_core.output_parsers import StrOutputParser +from flo_ai.yaml.config import AgentConfig class FloLLMAgent(ExecutableFlo): def __init__(self, executor: Runnable, - name: str) -> None: - super().__init__(name, executor, "agent") + config: AgentConfig) -> None: + super().__init__(config.name, executor, "agent") self.executor: Runnable = executor + self.config: AgentConfig = config class Builder: def __init__(self, session: FloSession, - name: str, - prompt: Union[ChatPromptTemplate, str], - role: Optional[str] = None, + config: AgentConfig, llm: Union[BaseLanguageModel, None] = None) -> None: - self.name: str = randomize_name(name) + prompt: Union[ChatPromptTemplate, str] = config.job + + self.name: str = randomize_name(config.name) self.llm = llm if llm is not None else session.llm # TODO improve to add more context of what other agents are available - system_prompts = [("system", "You are a {}".format(role)), ("system", prompt)] if role is not None else [("system", prompt)] + system_prompts = [("system", "You are a {}".format(config.role)), ("system", prompt)] if config.role is not None else [("system", prompt)] system_prompts.append(MessagesPlaceholder(variable_name="messages")) self.prompt: ChatPromptTemplate = ChatPromptTemplate.from_messages( system_prompts ) if isinstance(prompt, str) else prompt + self.config = config def build(self) -> Runnable: executor = self.prompt | self.llm | StrOutputParser() - return FloLLMAgent(executor, self.name) + return FloLLMAgent(executor, self.config) diff --git a/flo_ai/models/flo_node.py b/flo_ai/models/flo_node.py index 8c9f752e..98a27acb 100644 --- a/flo_ai/models/flo_node.py +++ b/flo_ai/models/flo_node.py @@ -4,6 +4,7 @@ from langchain.agents import AgentExecutor from flo_ai.state.flo_state import TeamFloAgentState from langchain_core.messages import HumanMessage +from flo_ai.yaml.config import AgentConfig class FloNode(): @@ -16,7 +17,7 @@ def __init__(self, class Builder(): @staticmethod - def teamflo_agent_node(state: TeamFloAgentState, agent: AgentExecutor, name: str): + def teamflo_agent_node(state: TeamFloAgentState, agent: AgentExecutor, name: str, agent_config: AgentConfig): result = agent.invoke(state) # TODO see how to fix this output = result if isinstance(result, str) else result["output"] @@ -39,7 +40,7 @@ def teamflo_team_node(message: str, members: list[str]): return results def build_from_agent(self, flo_agent: FloAgent): - agent_func = functools.partial(FloNode.Builder.teamflo_agent_node, agent=flo_agent.executor, name=flo_agent.name) + agent_func = functools.partial(FloNode.Builder.teamflo_agent_node, agent=flo_agent.executor, name=flo_agent.name, agent_config=flo_agent.config) return FloNode(agent_func, flo_agent.name) def build_from_team(self, flo_team: FloRoutedTeam): diff --git a/flo_ai/models/flo_team.py b/flo_ai/models/flo_team.py index e2a7313a..b4745e48 100644 --- a/flo_ai/models/flo_team.py +++ b/flo_ai/models/flo_team.py @@ -3,19 +3,22 @@ from flo_ai.yaml.config import TeamConfig class FloTeam(): - def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: + def __init__(self, team_config: TeamConfig, members: list[FloMember], reflection_agents: list[FloMember] = None) -> None: self.name = randomize_name(team_config.name) self.config = team_config self.members = members + self.reflection_agents = reflection_agents class Builder: - def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: + def __init__(self, team_config: TeamConfig, members: list[FloMember], reflection_agents: list[FloMember] = None) -> None: self.team_config = team_config self.members = members + self.reflection_agents = reflection_agents self.member_names= list(map(lambda x: x.name, self.members)) def build(self): return FloTeam( team_config=self.team_config, - members=self.members + members=self.members, + reflection_agents=self.reflection_agents ) \ No newline at end of file diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 4f5ec5a6..57c689ca 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -16,14 +16,12 @@ def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam, ) self.router_config = config.router def build_agent_graph(self): - flo_nodes = [self.build_node(flo_agent) for flo_agent in self.members] - flo_agent_nodes: List[FloNode] - flo_reflection_nodes: List[FloNode] - flo_agent_nodes, flo_reflection_nodes = self.differentiate_nodes(flo_nodes, self.config.agents) + flo_agent_nodes = [self.build_node(member) for member in self.members] + flo_reflection_nodes = [self.build_node(reflection_agent) for reflection_agent in self.reflection_agents] workflow = StateGraph(TeamFloAgentState) - for flo_node in flo_nodes: + for flo_node in (flo_agent_nodes + flo_reflection_nodes): agent_name = agent_name_from_randomized_name(flo_node.name) workflow.add_node(agent_name, flo_node.func) diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index e1eb6e9c..9fbd66fd 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -9,9 +9,17 @@ from flo_ai.models.flo_node import FloNode from flo_ai.constants.prompt_constants import FLO_FINISH from langgraph.graph import END,StateGraph -from typing import List, Tuple +from typing import List from flo_ai.models.flo_node import FloNode from flo_ai.helpers.utils import agent_name_from_randomized_name + +class ReflectionRoute: + + def __init__(self, agent_name, reflection_agent_name, retries, next = None): + self.agent_name = agent_name + self.reflection_agent_name = reflection_agent_name + self.retries = retries + self.next = next class FloRouter(ABC): def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, config: TeamConfig = None): @@ -19,6 +27,7 @@ def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, self.session: FloSession = session self.flo_team: FloTeam = flo_team self.members = flo_team.members + self.reflection_agents = flo_team.reflection_agents self.member_names = [x.name for x in flo_team.members] self.type = flo_team.members[0].type self.executor = executor @@ -53,33 +62,11 @@ def router_fn(self, state: TeamFloAgentState): if self.session.is_looping(node=next): return conditional_map[FLO_FINISH] return conditional_map[next] - def build_node_for_teams(self, flo_team: FloRoutedTeam): node_builder = FloNode.Builder() return node_builder.build_from_team(flo_team) - def differentiate_nodes(self, flo_nodes: List[FloNode], agents: List[AgentConfig] | None) -> Tuple[List[FloNode], List[FloNode]]: - node_dict = {} - agent_nodes = [] - reflection_nodes = [] - - for agent in agents: - if agent.reflection: - node_dict['reflection_nodes'] = agent.name - else: - node_dict['agent_nodes'] = agent.name - - for node in flo_nodes: - node_name = agent_name_from_randomized_name(node.name) - if node_name in node_dict['reflection_nodes']: - reflection_nodes.append(node) - elif node_name in node_dict['agent_nodes']: - agent_nodes.append(node) - - return agent_nodes, reflection_nodes - - def build_reflection_routes(self, workflow: StateGraph, agents: List[AgentConfig] | None, reflection_nodes: List[FloNode]): if len(reflection_nodes) == 0: return @@ -87,35 +74,40 @@ def build_reflection_routes(self, workflow: StateGraph, agents: List[AgentConfig reflection_routes = self.__get_reflection_routes(agents) for reflection in reflection_routes: - parent_node = reflection['nodes'][0] - reflection_node = reflection['nodes'][1] - next = END if reflection['next'] == 'END' else reflection['next'] + agent_name = reflection.agent_name + reflection_agent_name = reflection.reflection_agent_name + next = END if reflection.next == 'END' else reflection.next - workflow.add_conditional_edges(parent_node, self.__get_refelection_routing_fn(reflection['retries'], parent_node, reflection_node, next), {reflection_node: reflection_node, next: next}) - workflow.add_edge(reflection_node, parent_node) + workflow.add_conditional_edges(agent_name, + self.__get_refelection_routing_fn(reflection.retries, agent_name, reflection_agent_name, next), + {reflection_agent_name: reflection_agent_name, next: next} + ) + workflow.add_edge(reflection_agent_name, agent_name) @staticmethod - def __get_refelection_routing_fn(retries, parent_node, reflection_node, next): + def __get_refelection_routing_fn(retries, agent_name, reflection_agent_name, next): def reflection_routing_fn(state: TeamFloAgentState): - if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == parent_node: + if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == agent_name: return next - return reflection_node + return reflection_agent_name return reflection_routing_fn @staticmethod - def __get_reflection_routes(agents: List[AgentConfig] | None): + def __get_reflection_routes(agents: List[AgentConfig] | None) -> List[ReflectionRoute]: reflection_routes = [] for agent in agents: - if agent.reflection: - route = {} - route['nodes'] = [agent.reflection.node, agent.name] - route['retries'] = agent.reflection.retries - route['next'] = agent.reflection.next - reflection_routes.append(route) + if agent.prompt_strategy and agent.prompt_strategy.kind == 'reflection': + reflection_routes.append( + ReflectionRoute(agent.name, + agent.prompt_strategy.agent_name, + agent.prompt_strategy.retries, + agent.prompt_strategy.next) + ) return reflection_routes + \ No newline at end of file diff --git a/flo_ai/state/flo_state.py b/flo_ai/state/flo_state.py index 2d609c1d..1629ccc5 100644 --- a/flo_ai/state/flo_state.py +++ b/flo_ai/state/flo_state.py @@ -11,6 +11,7 @@ class TeamFloAgentState(TypedDict): messages: Annotated[Sequence[BaseMessage], operator.add] # The 'next' field indicates where to route to next next: str + reflection_messages: Annotated[Sequence[BaseMessage], operator.add] class TeamFloAgentStateWithPlan(TypedDict): input: str diff --git a/flo_ai/yaml/config.py b/flo_ai/yaml/config.py index 3985ffd7..a988de00 100644 --- a/flo_ai/yaml/config.py +++ b/flo_ai/yaml/config.py @@ -29,10 +29,11 @@ class ToolConfig(BaseModel): properties: Optional[List[KeyValueArgs]] = None filters: Optional[List[FilterArgs]] = None -class ReflectionConfig(BaseModel): - node: str +class PromptStrategy(BaseModel): + kind: str + agent_name: str retries: int - next: str + next: str | None = None class AgentConfig(BaseModel): name: str @@ -40,7 +41,8 @@ class AgentConfig(BaseModel): kind: Optional[str] = None job: Optional[str] = None tools: List[ToolConfig] = [] - reflection: Optional[ReflectionConfig] = None + use: Optional[str] = None + prompt_strategy: Optional[PromptStrategy] = None class EdgeConfig(BaseModel): edge: List[str] From a583fce4912108c9a9b0440c7cfae15ce1810fb9 Mon Sep 17 00:00:00 2001 From: vizsatiz Date: Fri, 20 Sep 2024 16:19:42 +0530 Subject: [PATCH 5/9] Implement reflection version 1 --- examples/linear_router_team.py | 16 ++++---- examples/reflection_flo.py | 48 ++++++++++++++++++++++ flo_ai/builders/yaml_builder.py | 13 +----- flo_ai/core.py | 13 ++++-- flo_ai/factory/agent_factory.py | 12 +++++- flo_ai/models/flo_agent.py | 3 +- flo_ai/models/flo_executable.py | 18 +++++++++ flo_ai/models/flo_llm_agent.py | 4 +- flo_ai/models/flo_node.py | 38 ++++++++++-------- flo_ai/models/flo_reflection_agent.py | 39 ++++++++++++++++++ flo_ai/models/flo_routed_team.py | 4 +- flo_ai/models/flo_team.py | 9 ++--- flo_ai/router/flo_custom_router.py | 4 +- flo_ai/router/flo_linear.py | 26 +++++++----- flo_ai/router/flo_llm_router.py | 4 +- flo_ai/router/flo_router.py | 58 +++++++++------------------ flo_ai/state/flo_session.py | 6 ++- flo_ai/yaml/config.py | 4 +- 18 files changed, 210 insertions(+), 109 deletions(-) create mode 100644 examples/reflection_flo.py create mode 100644 flo_ai/models/flo_reflection_agent.py diff --git a/examples/linear_router_team.py b/examples/linear_router_team.py index 27d0af67..b5b183d3 100644 --- a/examples/linear_router_team.py +++ b/examples/linear_router_team.py @@ -42,14 +42,14 @@ async def _arun( name: data-processing-pipline kind: linear agents: - - name: Reasercher - job: Do a research on the internet and find articles of relevent to the topic asked by the user, always try to find the latest information on the same - tools: - - name: TavilySearchResults - - name: Blogger - job: From the documents provider by the researcher write a blog of 300 words with can be readily published, make in engaging and add reference links to original blogs - tools: - - name: TavilySearchResults + - name: Reasercher + job: Do a research on the internet and find articles of relevent to the topic asked by the user, always try to find the latest information on the same + tools: + - name: TavilySearchResults + - name: Blogger + job: From the documents provider by the researcher write a blog of 300 words with can be readily published, make in engaging and add reference links to original blogs + tools: + - name: TavilySearchResults """ input_prompt = """ diff --git a/examples/reflection_flo.py b/examples/reflection_flo.py new file mode 100644 index 00000000..5c623620 --- /dev/null +++ b/examples/reflection_flo.py @@ -0,0 +1,48 @@ +from flo_ai.core import Flo +from flo_ai import FloSession +from langchain_openai import ChatOpenAI +from langchain_community.tools.tavily_search.tool import TavilySearchResults +from dotenv import load_dotenv +load_dotenv() + +yaml_data = """ +apiVersion: flo/alpha-v1 +kind: FloRoutedTeam +name: adding-team +team: + name: EssayTeam + agents: + - name: EssayWriter + kind: llm + job: > + You are an essay assistant tasked with writing excellent 300-words essays. Generate the best essay possible for the user's request. + If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words + - name: ReflectionAgent + kind: reflection + to: EssayWriter + job: > + You are a teacher grading an essay submission. Generate critique and recommendations for the user's submission. + Provide detailed recommendations, including requests for length, depth, style, etc. + - name: FinalEssayProducer + kind: llm + job: > + Generate the final assay to be returned to the user + router: + name: router + kind: linear +""" + +input_prompt = """ +Question: Write me an interesting blog about latest advancements in agentic AI by reasearching the internet +""" + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') +session = FloSession(llm).register_tool( + name="TavilySearchResults", + tool=TavilySearchResults() +) + +flo: Flo = Flo.build(session, yaml=yaml_data) +# flo.draw_to_file("event.png", xray=True) +data = flo.invoke(input_prompt) +print((data['messages'][-1]).content) \ No newline at end of file diff --git a/flo_ai/builders/yaml_builder.py b/flo_ai/builders/yaml_builder.py index 92e1b2e1..86ad74b2 100644 --- a/flo_ai/builders/yaml_builder.py +++ b/flo_ai/builders/yaml_builder.py @@ -20,17 +20,8 @@ def build_supervised_team(session: FloSession) -> ExecutableFlo: def parse_and_build_subteams(session: FloSession, team_config: TeamConfig) -> ExecutableFlo: flo_team = None if team_config.agents: - agents = [] - reflection_agents = [] - - for agent in team_config.agents: - flo_agent: FloAgent = AgentFactory.create(session, agent) - if agent.use == 'reflection': - reflection_agents.append(flo_agent) - else: - agents.append(flo_agent) - - flo_team = FloTeam.Builder(team_config, members=agents, reflection_agents=reflection_agents).build() + members = [AgentFactory.create(session, agent) for agent in team_config.agents] + flo_team = FloTeam.Builder(team_config, members=members).build() router = FloRouterFactory.create(session, team_config, flo_team) flo_routed_team = router.build_routed_team() else: diff --git a/flo_ai/core.py b/flo_ai/core.py index 40d3f7dc..a259fad5 100644 --- a/flo_ai/core.py +++ b/flo_ai/core.py @@ -10,9 +10,12 @@ class Flo: - def __init__(self, session: FloSession) -> None: + def __init__(self, + session: FloSession, + config: FloRoutedTeamConfig) -> None: + self.config = config + session.config = config self.runnable: ExecutableFlo = build_supervised_team(session) - self.session = session def stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: return self.runnable.stream(query, config) @@ -20,8 +23,10 @@ def stream(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: def invoke(self, query, config = None) -> Iterator[Union[dict[str, Any], Any]]: return self.runnable.invoke(query, config) - def build(self): - return Flo(self.session) + @staticmethod + def build(session: FloSession, yaml: str): + session.config = to_supervised_team(yaml) + return Flo(session, to_supervised_team(yaml)) def draw(self, xray=True): return self.runnable.draw(xray) diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 4bdc90eb..915c47fe 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -2,7 +2,8 @@ from flo_ai.yaml.config import (AgentConfig) from flo_ai.models.flo_agent import FloAgent from flo_ai.models.flo_llm_agent import FloLLMAgent -from flo_ai.models.flo_executable import ExecutableFlo +from flo_ai.models.flo_reflection_agent import FloReflectionAgent +from flo_ai.models.flo_executable import ExecutableFlo, ExecutableType from enum import Enum class AgentKinds(Enum): @@ -10,6 +11,7 @@ class AgentKinds(Enum): llm = "llm" tool = "tool" function = "function" + reflection = "reflection" class AgentFactory(): @@ -26,6 +28,8 @@ def create(session: FloSession, agent: AgentConfig): return AgentFactory.__create_llm_agent(session, agent) case AgentKinds.tool: return AgentFactory.__create_runnable_agent(session, agent) + case AgentKinds.reflection: + return AgentFactory.__create_reflection_agent(session, agent) return AgentFactory.__create_agentic_agent(session, agent, tool_map) @staticmethod @@ -47,4 +51,8 @@ def __create_llm_agent(session: FloSession, agent: AgentConfig) -> FloLLMAgent: @staticmethod def __create_runnable_agent(session: FloSession, agent: AgentConfig) -> FloLLMAgent: runnable = session.tools[agent.tools[0].name] - return ExecutableFlo(agent.name, runnable, "agent") \ No newline at end of file + return ExecutableFlo(agent.name, runnable, ExecutableType.tool) + + @staticmethod + def __create_reflection_agent(session: FloSession, agent: AgentConfig) -> FloReflectionAgent: + return FloReflectionAgent.Builder(session, agent).build() \ No newline at end of file diff --git a/flo_ai/models/flo_agent.py b/flo_ai/models/flo_agent.py index fe5360cf..abdd47d4 100644 --- a/flo_ai/models/flo_agent.py +++ b/flo_ai/models/flo_agent.py @@ -9,13 +9,14 @@ from flo_ai.state.flo_session import FloSession from typing import Union, Optional from flo_ai.yaml.config import AgentConfig +from flo_ai.models.flo_executable import ExecutableType class FloAgent(ExecutableFlo): def __init__(self, agent: Runnable, executor: AgentExecutor, config: AgentConfig) -> None: - super().__init__(config.name, executor, "agent") + super().__init__(config.name, executor, ExecutableType.agentic) self.agent: Runnable = agent, self.executor: AgentExecutor = executor self.config: AgentConfig = config diff --git a/flo_ai/models/flo_executable.py b/flo_ai/models/flo_executable.py index 32fb101a..e1381e42 100644 --- a/flo_ai/models/flo_executable.py +++ b/flo_ai/models/flo_executable.py @@ -1,6 +1,24 @@ from flo_ai.models.flo_member import FloMember from langchain_core.runnables import Runnable from langchain_core.messages import HumanMessage +from enum import Enum + +class ExecutableType(Enum): + agentic = "agentic" + llm = "llm" + tool = "tool" + reflection = "reflection" + + @staticmethod + def isAgent(type: 'ExecutableType'): + match(type): + case ExecutableType.agentic: + return True + case ExecutableType.llm: + return True + case ExecutableType.tool: + return True + return False class ExecutableFlo(FloMember): def __init__(self, diff --git a/flo_ai/models/flo_llm_agent.py b/flo_ai/models/flo_llm_agent.py index f033efd4..d753dd47 100644 --- a/flo_ai/models/flo_llm_agent.py +++ b/flo_ai/models/flo_llm_agent.py @@ -1,5 +1,4 @@ from langchain_core.runnables import Runnable -from langchain_core.runnables import Runnable from langchain_core.language_models import BaseLanguageModel from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from flo_ai.helpers.utils import randomize_name @@ -8,13 +7,14 @@ from typing import Union from langchain_core.output_parsers import StrOutputParser from flo_ai.yaml.config import AgentConfig +from flo_ai.models.flo_executable import ExecutableType class FloLLMAgent(ExecutableFlo): def __init__(self, executor: Runnable, config: AgentConfig) -> None: - super().__init__(config.name, executor, "agent") + super().__init__(config.name, executor, ExecutableType.llm) self.executor: Runnable = executor self.config: AgentConfig = config diff --git a/flo_ai/models/flo_node.py b/flo_ai/models/flo_node.py index 98a27acb..5410ff22 100644 --- a/flo_ai/models/flo_node.py +++ b/flo_ai/models/flo_node.py @@ -4,47 +4,53 @@ from langchain.agents import AgentExecutor from flo_ai.state.flo_state import TeamFloAgentState from langchain_core.messages import HumanMessage -from flo_ai.yaml.config import AgentConfig +from flo_ai.yaml.config import AgentConfig, TeamConfig +from flo_ai.models.flo_executable import ExecutableType +from typing import Union class FloNode(): def __init__(self, func: functools.partial, - name: str) -> None: + name: str, + kind: ExecutableType, + config: Union[AgentConfig | TeamConfig]) -> None: self.name = name self.func = func + self.kind: ExecutableType = kind + self.config: Union[AgentConfig | TeamConfig] = config class Builder(): + def build_from_agent(self, flo_agent: FloAgent) -> 'FloNode': + agent_func = functools.partial(FloNode.Builder.__teamflo_agent_node, agent=flo_agent.runnable, name=flo_agent.name, agent_config=flo_agent.config) + return FloNode(agent_func, flo_agent.name, flo_agent.type, flo_agent.config) + + def build_from_team(self, flo_team: FloRoutedTeam) -> 'FloNode': + team_chain = (functools.partial(FloNode.Builder.__teamflo_team_node, members=flo_team.runnable.nodes) | flo_team.runnable) + return FloNode(( + FloNode.Builder.__get_last_message | team_chain | FloNode.Builder.__join_graph + ), flo_team.name, flo_team.type, flo_team.config) + @staticmethod - def teamflo_agent_node(state: TeamFloAgentState, agent: AgentExecutor, name: str, agent_config: AgentConfig): + def __teamflo_agent_node(state: TeamFloAgentState, agent: AgentExecutor, name: str, agent_config: AgentConfig): result = agent.invoke(state) # TODO see how to fix this output = result if isinstance(result, str) else result["output"] return { "messages": [HumanMessage(content=output, name=name)] } @staticmethod - def get_last_message(state: TeamFloAgentState) -> str: + def __get_last_message(state: TeamFloAgentState) -> str: return state["messages"][-1].content @staticmethod - def join_graph(response: dict): + def __join_graph(response: dict): return { "messages": [ response["messages"][-1] ] } @staticmethod - def teamflo_team_node(message: str, members: list[str]): + def __teamflo_team_node(message: str, members: list[str]): results = { "messages": [HumanMessage(content=message)], "team_members": ", ".join(members), } return results - - def build_from_agent(self, flo_agent: FloAgent): - agent_func = functools.partial(FloNode.Builder.teamflo_agent_node, agent=flo_agent.executor, name=flo_agent.name, agent_config=flo_agent.config) - return FloNode(agent_func, flo_agent.name) - - def build_from_team(self, flo_team: FloRoutedTeam): - team_chain = (functools.partial(FloNode.Builder.teamflo_team_node, members=flo_team.runnable.nodes) | flo_team.runnable) - return FloNode(( - FloNode.Builder.get_last_message | team_chain | FloNode.Builder.join_graph - ), flo_team.name) \ No newline at end of file diff --git a/flo_ai/models/flo_reflection_agent.py b/flo_ai/models/flo_reflection_agent.py new file mode 100644 index 00000000..c8a45ee8 --- /dev/null +++ b/flo_ai/models/flo_reflection_agent.py @@ -0,0 +1,39 @@ +from typing import Union +from langchain_core.runnables import Runnable +from flo_ai.yaml.config import AgentConfig +from flo_ai.state.flo_session import FloSession +from flo_ai.models.flo_executable import ExecutableFlo +from langchain_core.language_models import BaseLanguageModel +from flo_ai.helpers.utils import randomize_name +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from flo_ai.models.flo_executable import ExecutableType +from langchain_core.output_parsers import StrOutputParser + +# TODO not ready, lot of fixes to do +# can be merged into LLM Agent or made into a child +class FloReflectionAgent(ExecutableFlo): + + def __init__(self, executor: Runnable, config: AgentConfig) -> None: + super().__init__(config.name, executor, ExecutableType.reflection) + self.config = config + + class Builder(): + def __init__(self, + session: FloSession, + config: AgentConfig, + llm: Union[BaseLanguageModel, None] = None) -> None: + + prompt_message: Union[ChatPromptTemplate, str] = config.job + self.name: str = randomize_name(config.name) + self.llm = llm if llm is not None else session.llm + self.config = config + + system_prompts = [("system", "You are a {}".format(config.role)), ("system", prompt_message)] if config.role is not None else [("system", prompt_message)] + system_prompts.append(MessagesPlaceholder(variable_name="messages")) + self.prompt: ChatPromptTemplate = ChatPromptTemplate.from_messages( + system_prompts + ) if isinstance(prompt_message, str) else prompt_message + + def build(self): + executor = self.prompt | self.llm | StrOutputParser() + return FloReflectionAgent(executor, self.config) \ No newline at end of file diff --git a/flo_ai/models/flo_routed_team.py b/flo_ai/models/flo_routed_team.py index 0eb4270e..6531162f 100644 --- a/flo_ai/models/flo_routed_team.py +++ b/flo_ai/models/flo_routed_team.py @@ -1,10 +1,12 @@ from flo_ai.models.flo_executable import ExecutableFlo from langgraph.graph.graph import CompiledGraph +from flo_ai.yaml.config import TeamConfig class FloRoutedTeam(ExecutableFlo): - def __init__(self, name: str, graph: CompiledGraph) -> None: + def __init__(self, name: str, graph: CompiledGraph, config: TeamConfig) -> None: super().__init__(name, graph) + self.config = config def draw(self, xray=True): return self.runnable.get_graph(xray=xray).draw_mermaid_png() \ No newline at end of file diff --git a/flo_ai/models/flo_team.py b/flo_ai/models/flo_team.py index b4745e48..e2a7313a 100644 --- a/flo_ai/models/flo_team.py +++ b/flo_ai/models/flo_team.py @@ -3,22 +3,19 @@ from flo_ai.yaml.config import TeamConfig class FloTeam(): - def __init__(self, team_config: TeamConfig, members: list[FloMember], reflection_agents: list[FloMember] = None) -> None: + def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: self.name = randomize_name(team_config.name) self.config = team_config self.members = members - self.reflection_agents = reflection_agents class Builder: - def __init__(self, team_config: TeamConfig, members: list[FloMember], reflection_agents: list[FloMember] = None) -> None: + def __init__(self, team_config: TeamConfig, members: list[FloMember]) -> None: self.team_config = team_config self.members = members - self.reflection_agents = reflection_agents self.member_names= list(map(lambda x: x.name, self.members)) def build(self): return FloTeam( team_config=self.team_config, - members=self.members, - reflection_agents=self.reflection_agents + members=self.members ) \ No newline at end of file diff --git a/flo_ai/router/flo_custom_router.py b/flo_ai/router/flo_custom_router.py index 6fdab3b5..bea0fffa 100644 --- a/flo_ai/router/flo_custom_router.py +++ b/flo_ai/router/flo_custom_router.py @@ -89,7 +89,7 @@ def build_agent_graph(self): workflow_graph = workflow.compile() - return FloRoutedTeam(self.flo_team.name, workflow_graph) + return FloRoutedTeam(self.flo_team.name, workflow_graph, self.flo_team.config) def build_team_graph(self): flo_team_entry_chains = [self.build_node_for_teams(flo_agent) for flo_agent in self.members] @@ -119,7 +119,7 @@ def build_team_graph(self): workflow_graph = super_graph.compile() - return FloRoutedTeam(self.flo_team.name, workflow_graph) + return FloRoutedTeam(self.flo_team.name, workflow_graph, self.flo_team.config) class Builder(): diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 57c689ca..1724b5d0 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -5,27 +5,24 @@ from flo_ai.models.flo_routed_team import FloRoutedTeam from flo_ai.models.flo_team import FloTeam from flo_ai.state.flo_session import FloSession +from flo_ai.models.flo_executable import ExecutableType from flo_ai.helpers.utils import agent_name_from_randomized_name, randomize_name -from typing import List, Tuple -from flo_ai.models.flo_node import FloNode + class FloLinear(FloRouter): - def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam, ): + def __init__(self, session: FloSession, config: TeamConfig, flo_team: FloTeam): super().__init__(session=session, name=randomize_name(config.name), flo_team=flo_team, executor=None, config=config) self.router_config = config.router def build_agent_graph(self): flo_agent_nodes = [self.build_node(member) for member in self.members] - flo_reflection_nodes = [self.build_node(reflection_agent) for reflection_agent in self.reflection_agents] workflow = StateGraph(TeamFloAgentState) - for flo_node in (flo_agent_nodes + flo_reflection_nodes): + for flo_node in flo_agent_nodes: agent_name = agent_name_from_randomized_name(flo_node.name) workflow.add_node(agent_name, flo_node.func) - - self.build_reflection_routes(workflow, self.config.agents, flo_reflection_nodes) if self.router_config.edges is None: start_node_name = agent_name_from_randomized_name(flo_agent_nodes[0].name) @@ -34,8 +31,15 @@ def build_agent_graph(self): for i in range(len(flo_agent_nodes) - 1): agent1_name = agent_name_from_randomized_name(flo_agent_nodes[i].name) agent2_name = agent_name_from_randomized_name(flo_agent_nodes[i+1].name) - workflow.add_edge(agent1_name, agent2_name) - workflow.add_edge(end_node_name, END) + print(flo_agent_nodes[i].kind) + if (flo_agent_nodes[i].kind == ExecutableType.reflection): + self.add_reflection_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1]) + else: + workflow.add_edge(agent1_name, agent2_name) + if (flo_agent_nodes[-1].kind == ExecutableType.reflection): + self.add_reflection_edge(workflow, flo_agent_nodes[-1], END) + else: + workflow.add_edge(end_node_name, END) else: workflow.add_edge(START, self.router_config.start_node) for edge in self.router_config.edges: @@ -44,7 +48,7 @@ def build_agent_graph(self): workflow_graph = workflow.compile() - return FloRoutedTeam(self.flo_team.name, workflow_graph) + return FloRoutedTeam(self.flo_team.name, workflow_graph, self.flo_team.config) def build_team_graph(self): flo_team_entry_chains = [self.build_node_for_teams(flo_agent) for flo_agent in self.members] @@ -71,7 +75,7 @@ def build_team_graph(self): super_graph.add_edge(self.router_config.end_node, END) super_graph = super_graph.compile() - return FloRoutedTeam(self.flo_team.name, super_graph) + return FloRoutedTeam(self.flo_team.name, super_graph, self.flo_team.config) class Builder(): diff --git a/flo_ai/router/flo_llm_router.py b/flo_ai/router/flo_llm_router.py index 7b6900df..1d1c06a1 100644 --- a/flo_ai/router/flo_llm_router.py +++ b/flo_ai/router/flo_llm_router.py @@ -47,7 +47,7 @@ def build_agent_graph(self): workflow.add_conditional_edges(self.router_name, self.router_fn) workflow.set_entry_point(self.router_name) workflow_graph = workflow.compile() - return FloRoutedTeam(self.flo_team.name, workflow_graph) + return FloRoutedTeam(self.flo_team.name, workflow_graph, self.flo_team.config) def build_team_graph(self): flo_team_entry_chains = [self.build_node_for_teams(flo_agent) for flo_agent in self.members] @@ -65,7 +65,7 @@ def build_team_graph(self): super_graph.set_entry_point(self.router_name) super_graph = super_graph.compile() - return FloRoutedTeam(self.flo_team.name, super_graph) + return FloRoutedTeam(self.flo_team.name, super_graph, self.flo_team.config) class Builder: def __init__(self, diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index 9fbd66fd..fc5e0ffe 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -9,9 +9,9 @@ from flo_ai.models.flo_node import FloNode from flo_ai.constants.prompt_constants import FLO_FINISH from langgraph.graph import END,StateGraph -from typing import List from flo_ai.models.flo_node import FloNode from flo_ai.helpers.utils import agent_name_from_randomized_name +from flo_ai.models.flo_executable import ExecutableType class ReflectionRoute: @@ -20,6 +20,7 @@ def __init__(self, agent_name, reflection_agent_name, retries, next = None): self.reflection_agent_name = reflection_agent_name self.retries = retries self.next = next + class FloRouter(ABC): def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, config: TeamConfig = None): @@ -27,14 +28,13 @@ def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, self.session: FloSession = session self.flo_team: FloTeam = flo_team self.members = flo_team.members - self.reflection_agents = flo_team.reflection_agents self.member_names = [x.name for x in flo_team.members] - self.type = flo_team.members[0].type + self.type: ExecutableType = flo_team.members[0].type self.executor = executor self.config = config def is_agent_supervisor(self): - return self.type == "agent" + return ExecutableType.isAgent(self.type) def build_routed_team(self) -> FloRoutedTeam: if self.is_agent_supervisor(): @@ -50,7 +50,7 @@ def build_agent_graph(): def build_team_graph(): pass - def build_node(self, flo_agent: FloAgent): + def build_node(self, flo_agent: FloAgent) -> FloNode: node_builder = FloNode.Builder() return node_builder.build_from_agent(flo_agent) @@ -67,47 +67,27 @@ def build_node_for_teams(self, flo_team: FloRoutedTeam): node_builder = FloNode.Builder() return node_builder.build_from_team(flo_team) - def build_reflection_routes(self, workflow: StateGraph, agents: List[AgentConfig] | None, reflection_nodes: List[FloNode]): - if len(reflection_nodes) == 0: - return - - reflection_routes = self.__get_reflection_routes(agents) - - for reflection in reflection_routes: - agent_name = reflection.agent_name - reflection_agent_name = reflection.reflection_agent_name - next = END if reflection.next == 'END' else reflection.next - - workflow.add_conditional_edges(agent_name, - self.__get_refelection_routing_fn(reflection.retries, agent_name, reflection_agent_name, next), - {reflection_agent_name: reflection_agent_name, next: next} - ) - workflow.add_edge(reflection_agent_name, agent_name) + def add_reflection_edge(self, workflow: StateGraph, reflection_node: FloNode, nextNode: FloNode): + to_agent_name = reflection_node.config.to + retry = reflection_node.config.retry or 1 + reflection_agent_name = reflection_node.name + next = nextNode.name + print(f"Setting router between: {reflection_agent_name} -> {to_agent_name} -> next -> {next}") + workflow.add_conditional_edges( + reflection_agent_name, + self.__get_refelection_routing_fn(retry, reflection_agent_name, to_agent_name, next), + { to_agent_name: to_agent_name, next: next } + ) @staticmethod - def __get_refelection_routing_fn(retries, agent_name, reflection_agent_name, next): + def __get_refelection_routing_fn(retries, reflection_agent_name, to_agent_name, next): def reflection_routing_fn(state: TeamFloAgentState): - if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == agent_name: + if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == reflection_agent_name: return next - return reflection_agent_name + return to_agent_name return reflection_routing_fn - - @staticmethod - def __get_reflection_routes(agents: List[AgentConfig] | None) -> List[ReflectionRoute]: - reflection_routes = [] - - for agent in agents: - if agent.prompt_strategy and agent.prompt_strategy.kind == 'reflection': - reflection_routes.append( - ReflectionRoute(agent.name, - agent.prompt_strategy.agent_name, - agent.prompt_strategy.retries, - agent.prompt_strategy.next) - ) - - return reflection_routes \ No newline at end of file diff --git a/flo_ai/state/flo_session.py b/flo_ai/state/flo_session.py index 0630bc78..3ab4ce38 100644 --- a/flo_ai/state/flo_session.py +++ b/flo_ai/state/flo_session.py @@ -3,8 +3,10 @@ from flo_ai.yaml.config import to_supervised_team from typing import Union from flo_ai.yaml.config import (FloRoutedTeamConfig, FloAgentConfig) + class FloSession: - def __init__(self, llm: BaseLanguageModel, yaml_config: str | None, loop_size: int = 2, max_loop: int = 3) -> None: + + def __init__(self, llm: BaseLanguageModel, loop_size: int = 2, max_loop: int = 3) -> None: self.llm = llm self.tools = dict() self.counter = dict() @@ -12,7 +14,7 @@ def __init__(self, llm: BaseLanguageModel, yaml_config: str | None, loop_size: i self.pattern_series = dict() self.loop_size: int = loop_size self.max_loop: int = max_loop - self.config: Union[FloRoutedTeamConfig, FloAgentConfig] = to_supervised_team(yaml_str=yaml_config) if isinstance(yaml_config, str) else None + self.config: Union[FloRoutedTeamConfig, FloAgentConfig] = None def register_tool(self, name: str, tool: BaseTool): self.tools[name] = tool diff --git a/flo_ai/yaml/config.py b/flo_ai/yaml/config.py index a988de00..159bd47e 100644 --- a/flo_ai/yaml/config.py +++ b/flo_ai/yaml/config.py @@ -41,8 +41,8 @@ class AgentConfig(BaseModel): kind: Optional[str] = None job: Optional[str] = None tools: List[ToolConfig] = [] - use: Optional[str] = None - prompt_strategy: Optional[PromptStrategy] = None + to: Optional[str] = None + retry: Optional[int] = 1 class EdgeConfig(BaseModel): edge: List[str] From 98b00f831dd8df25c671c9be8596be37a5c8e91a Mon Sep 17 00:00:00 2001 From: vizsatiz Date: Fri, 20 Sep 2024 22:27:43 +0530 Subject: [PATCH 6/9] Reflection implementation --- examples/reflection_flo.py | 6 +++--- flo_ai/router/flo_linear.py | 1 - flo_ai/router/flo_router.py | 30 +++++++++++++++++++++++++----- flo_ai/state/flo_state.py | 5 +++-- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/examples/reflection_flo.py b/examples/reflection_flo.py index 5c623620..ed345914 100644 --- a/examples/reflection_flo.py +++ b/examples/reflection_flo.py @@ -43,6 +43,6 @@ ) flo: Flo = Flo.build(session, yaml=yaml_data) -# flo.draw_to_file("event.png", xray=True) -data = flo.invoke(input_prompt) -print((data['messages'][-1]).content) \ No newline at end of file +flo.draw_to_file("event.png", xray=True) +# data = flo.invoke(input_prompt) +# print((data['messages'][-1]).content) \ No newline at end of file diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 1724b5d0..bd1dfe53 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -31,7 +31,6 @@ def build_agent_graph(self): for i in range(len(flo_agent_nodes) - 1): agent1_name = agent_name_from_randomized_name(flo_agent_nodes[i].name) agent2_name = agent_name_from_randomized_name(flo_agent_nodes[i+1].name) - print(flo_agent_nodes[i].kind) if (flo_agent_nodes[i].kind == ExecutableType.reflection): self.add_reflection_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1]) else: diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index fc5e0ffe..37c05e78 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -10,8 +10,8 @@ from flo_ai.constants.prompt_constants import FLO_FINISH from langgraph.graph import END,StateGraph from flo_ai.models.flo_node import FloNode -from flo_ai.helpers.utils import agent_name_from_randomized_name from flo_ai.models.flo_executable import ExecutableType +import functools class ReflectionRoute: @@ -67,26 +67,46 @@ def build_node_for_teams(self, flo_team: FloRoutedTeam): node_builder = FloNode.Builder() return node_builder.build_from_team(flo_team) + def update_reflection_state(self, state: TeamFloAgentState, reflection_agent_name: str): + tracker = None + if "reflection_tracker" not in state or state["reflection_tracker"] is None: + tracker = dict() + else: + tracker = state["reflection_tracker"] + + if reflection_agent_name in tracker: + tracker[reflection_agent_name] += 1 + else: + tracker[reflection_agent_name] = 1 + + return { + "reflection_tracker": tracker + } + def add_reflection_edge(self, workflow: StateGraph, reflection_node: FloNode, nextNode: FloNode): to_agent_name = reflection_node.config.to retry = reflection_node.config.retry or 1 reflection_agent_name = reflection_node.name next = nextNode.name - print(f"Setting router between: {reflection_agent_name} -> {to_agent_name} -> next -> {next}") + + workflow.add_node("reflection_counter", functools.partial(self.update_reflection_state, reflection_agent_name=reflection_agent_name)) + workflow.add_edge(reflection_agent_name, "reflection_counter") workflow.add_conditional_edges( - reflection_agent_name, + "reflection_counter", self.__get_refelection_routing_fn(retry, reflection_agent_name, to_agent_name, next), { to_agent_name: to_agent_name, next: next } ) @staticmethod - def __get_refelection_routing_fn(retries, reflection_agent_name, to_agent_name, next): + def __get_refelection_routing_fn(retries: int, reflection_agent_name, to_agent_name, next): def reflection_routing_fn(state: TeamFloAgentState): - if len(state['messages']) > (int(retries)*2)+1 and agent_name_from_randomized_name(state['messages'][-((int(retries)*2)+1)].name) == reflection_agent_name: + tracker = state["reflection_tracker"] + if tracker is not None and reflection_agent_name in tracker and tracker[reflection_agent_name] >= retries: return next return to_agent_name return reflection_routing_fn + diff --git a/flo_ai/state/flo_state.py b/flo_ai/state/flo_state.py index 1629ccc5..74f611a0 100644 --- a/flo_ai/state/flo_state.py +++ b/flo_ai/state/flo_state.py @@ -1,6 +1,6 @@ from typing import Annotated, List, Sequence, TypedDict from langchain_core.messages import BaseMessage -from typing import List, Tuple, Annotated, TypedDict, Dict +from typing import List, Tuple, Annotated, TypedDict import operator @@ -11,7 +11,8 @@ class TeamFloAgentState(TypedDict): messages: Annotated[Sequence[BaseMessage], operator.add] # The 'next' field indicates where to route to next next: str - reflection_messages: Annotated[Sequence[BaseMessage], operator.add] + # used for reflection agents + reflection_tracker: dict class TeamFloAgentStateWithPlan(TypedDict): input: str From d694cc61da36be5fb590b3cc6ba3957f865e5304 Mon Sep 17 00:00:00 2001 From: vizsatiz Date: Sat, 21 Sep 2024 11:37:58 +0530 Subject: [PATCH 7/9] Renaming to reflexion --- examples/{reflection_flo.py => reflexion_example.py} | 5 +++-- flo_ai/factory/agent_factory.py | 4 ++-- flo_ai/models/flo_executable.py | 2 +- flo_ai/models/flo_reflection_agent.py | 2 +- flo_ai/router/flo_linear.py | 4 ++-- flo_ai/router/flo_router.py | 8 -------- 6 files changed, 9 insertions(+), 16 deletions(-) rename examples/{reflection_flo.py => reflexion_example.py} (95%) diff --git a/examples/reflection_flo.py b/examples/reflexion_example.py similarity index 95% rename from examples/reflection_flo.py rename to examples/reflexion_example.py index ed345914..a65e8949 100644 --- a/examples/reflection_flo.py +++ b/examples/reflexion_example.py @@ -17,8 +17,9 @@ job: > You are an essay assistant tasked with writing excellent 300-words essays. Generate the best essay possible for the user's request. If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words - - name: ReflectionAgent - kind: reflection + - name: ReflexionAgent + kind: reflexion + retry: 1 to: EssayWriter job: > You are a teacher grading an essay submission. Generate critique and recommendations for the user's submission. diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 915c47fe..c3b640fb 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -11,7 +11,7 @@ class AgentKinds(Enum): llm = "llm" tool = "tool" function = "function" - reflection = "reflection" + reflexion = "reflexion" class AgentFactory(): @@ -28,7 +28,7 @@ def create(session: FloSession, agent: AgentConfig): return AgentFactory.__create_llm_agent(session, agent) case AgentKinds.tool: return AgentFactory.__create_runnable_agent(session, agent) - case AgentKinds.reflection: + case AgentKinds.reflexion: return AgentFactory.__create_reflection_agent(session, agent) return AgentFactory.__create_agentic_agent(session, agent, tool_map) diff --git a/flo_ai/models/flo_executable.py b/flo_ai/models/flo_executable.py index e1381e42..4bcc2299 100644 --- a/flo_ai/models/flo_executable.py +++ b/flo_ai/models/flo_executable.py @@ -7,7 +7,7 @@ class ExecutableType(Enum): agentic = "agentic" llm = "llm" tool = "tool" - reflection = "reflection" + reflexion = "reflexion" @staticmethod def isAgent(type: 'ExecutableType'): diff --git a/flo_ai/models/flo_reflection_agent.py b/flo_ai/models/flo_reflection_agent.py index c8a45ee8..41dd123b 100644 --- a/flo_ai/models/flo_reflection_agent.py +++ b/flo_ai/models/flo_reflection_agent.py @@ -14,7 +14,7 @@ class FloReflectionAgent(ExecutableFlo): def __init__(self, executor: Runnable, config: AgentConfig) -> None: - super().__init__(config.name, executor, ExecutableType.reflection) + super().__init__(config.name, executor, ExecutableType.reflexion) self.config = config class Builder(): diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index bd1dfe53..382b4440 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -31,11 +31,11 @@ def build_agent_graph(self): for i in range(len(flo_agent_nodes) - 1): agent1_name = agent_name_from_randomized_name(flo_agent_nodes[i].name) agent2_name = agent_name_from_randomized_name(flo_agent_nodes[i+1].name) - if (flo_agent_nodes[i].kind == ExecutableType.reflection): + if (flo_agent_nodes[i].kind == ExecutableType.reflexion): self.add_reflection_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1]) else: workflow.add_edge(agent1_name, agent2_name) - if (flo_agent_nodes[-1].kind == ExecutableType.reflection): + if (flo_agent_nodes[-1].kind == ExecutableType.reflexion): self.add_reflection_edge(workflow, flo_agent_nodes[-1], END) else: workflow.add_edge(end_node_name, END) diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index 37c05e78..56ec68b0 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -13,14 +13,6 @@ from flo_ai.models.flo_executable import ExecutableType import functools -class ReflectionRoute: - - def __init__(self, agent_name, reflection_agent_name, retries, next = None): - self.agent_name = agent_name - self.reflection_agent_name = reflection_agent_name - self.retries = retries - self.next = next - class FloRouter(ABC): def __init__(self, session: FloSession, name: str, flo_team: FloTeam, executor, config: TeamConfig = None): From d7c8bf1611bff92867429792ff2c8f24fe803ab9 Mon Sep 17 00:00:00 2001 From: vizsatiz Date: Sat, 21 Sep 2024 16:27:59 +0530 Subject: [PATCH 8/9] Using reflection manager before reflection node --- ROADMAP.md | 2 +- examples/reflexion_example.py | 8 ++++---- flo_ai/core.py | 1 + flo_ai/factory/agent_factory.py | 4 ++-- flo_ai/models/flo_executable.py | 2 +- flo_ai/models/flo_reflection_agent.py | 2 +- flo_ai/router/flo_linear.py | 7 ++++--- flo_ai/router/flo_router.py | 22 ++++++++++++---------- flo_ai/state/flo_session.py | 1 - 9 files changed, 26 insertions(+), 23 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d81a0374..2f971ced 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -9,7 +9,7 @@ Core features improve the library itself to cater wider range of functionalities | Name | Description | Status | Release version | |------|-------------|--------|-----------------| |Linear Router|A router that lets you build agents or teams that execute linearly or sequentially. The current router supervisor works in a hierarchical way where all the children report to one parent| In progress | 0.0.3| -|Reflexion| Reflection lets you build a component that can make the AI retrospectively look at the current output and retry or work again on the task at hand| Yet to start| 0.0.3| +|Reflection| Reflection lets you build a component that can make the AI retrospectively look at the current output and retry or work again on the task at hand| Yet to start| 0.0.3| |Output formatter| Ability to templatize output format using pydantic| Yet to start| 0.0.4| |LLM Extensibilty| Ability to different LLMs across different agents and teams| Yet to start| 0.0.4| |Auto-Query RAG| Ability to make metadata query within the agentic, which can automatically add metadata while rag query runs, like timestamp or other data|Yet to start|TBD| diff --git a/examples/reflexion_example.py b/examples/reflexion_example.py index a65e8949..57a075ed 100644 --- a/examples/reflexion_example.py +++ b/examples/reflexion_example.py @@ -17,8 +17,8 @@ job: > You are an essay assistant tasked with writing excellent 300-words essays. Generate the best essay possible for the user's request. If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words - - name: ReflexionAgent - kind: reflexion + - name: ReflectionAgent + kind: reflection retry: 1 to: EssayWriter job: > @@ -45,5 +45,5 @@ flo: Flo = Flo.build(session, yaml=yaml_data) flo.draw_to_file("event.png", xray=True) -# data = flo.invoke(input_prompt) -# print((data['messages'][-1]).content) \ No newline at end of file +data = flo.invoke(input_prompt) +print((data['messages'][-1]).content) \ No newline at end of file diff --git a/flo_ai/core.py b/flo_ai/core.py index 628ac997..088f2d37 100644 --- a/flo_ai/core.py +++ b/flo_ai/core.py @@ -11,6 +11,7 @@ def __init__(self, session: FloSession, config: FloRoutedTeamConfig, log_level: str = "INFO") -> None: + self.session = session self.config = config session.config = config self.runnable: ExecutableFlo = build_supervised_team(session) diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index c3b640fb..915c47fe 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -11,7 +11,7 @@ class AgentKinds(Enum): llm = "llm" tool = "tool" function = "function" - reflexion = "reflexion" + reflection = "reflection" class AgentFactory(): @@ -28,7 +28,7 @@ def create(session: FloSession, agent: AgentConfig): return AgentFactory.__create_llm_agent(session, agent) case AgentKinds.tool: return AgentFactory.__create_runnable_agent(session, agent) - case AgentKinds.reflexion: + case AgentKinds.reflection: return AgentFactory.__create_reflection_agent(session, agent) return AgentFactory.__create_agentic_agent(session, agent, tool_map) diff --git a/flo_ai/models/flo_executable.py b/flo_ai/models/flo_executable.py index 4bcc2299..e1381e42 100644 --- a/flo_ai/models/flo_executable.py +++ b/flo_ai/models/flo_executable.py @@ -7,7 +7,7 @@ class ExecutableType(Enum): agentic = "agentic" llm = "llm" tool = "tool" - reflexion = "reflexion" + reflection = "reflection" @staticmethod def isAgent(type: 'ExecutableType'): diff --git a/flo_ai/models/flo_reflection_agent.py b/flo_ai/models/flo_reflection_agent.py index 41dd123b..c8a45ee8 100644 --- a/flo_ai/models/flo_reflection_agent.py +++ b/flo_ai/models/flo_reflection_agent.py @@ -14,7 +14,7 @@ class FloReflectionAgent(ExecutableFlo): def __init__(self, executor: Runnable, config: AgentConfig) -> None: - super().__init__(config.name, executor, ExecutableType.reflexion) + super().__init__(config.name, executor, ExecutableType.reflection) self.config = config class Builder(): diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 382b4440..957805f4 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -31,11 +31,12 @@ def build_agent_graph(self): for i in range(len(flo_agent_nodes) - 1): agent1_name = agent_name_from_randomized_name(flo_agent_nodes[i].name) agent2_name = agent_name_from_randomized_name(flo_agent_nodes[i+1].name) - if (flo_agent_nodes[i].kind == ExecutableType.reflexion): + if (flo_agent_nodes[i].kind == ExecutableType.reflection): self.add_reflection_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1]) else: - workflow.add_edge(agent1_name, agent2_name) - if (flo_agent_nodes[-1].kind == ExecutableType.reflexion): + if (flo_agent_nodes[i+1].kind != ExecutableType.reflection): + workflow.add_edge(agent1_name, agent2_name) + if (flo_agent_nodes[-1].kind == ExecutableType.reflection): self.add_reflection_edge(workflow, flo_agent_nodes[-1], END) else: workflow.add_edge(end_node_name, END) diff --git a/flo_ai/router/flo_router.py b/flo_ai/router/flo_router.py index 56ec68b0..b4bef01d 100644 --- a/flo_ai/router/flo_router.py +++ b/flo_ai/router/flo_router.py @@ -12,6 +12,7 @@ from flo_ai.models.flo_node import FloNode from flo_ai.models.flo_executable import ExecutableType import functools +from typing import Union class FloRouter(ABC): @@ -75,27 +76,28 @@ def update_reflection_state(self, state: TeamFloAgentState, reflection_agent_nam "reflection_tracker": tracker } - def add_reflection_edge(self, workflow: StateGraph, reflection_node: FloNode, nextNode: FloNode): + def add_reflection_edge(self, workflow: StateGraph, reflection_node: FloNode, nextNode: Union[FloNode | str]): to_agent_name = reflection_node.config.to retry = reflection_node.config.retry or 1 reflection_agent_name = reflection_node.name - next = nextNode.name + next = nextNode if isinstance(nextNode, str) else nextNode.name - workflow.add_node("reflection_counter", functools.partial(self.update_reflection_state, reflection_agent_name=reflection_agent_name)) - workflow.add_edge(reflection_agent_name, "reflection_counter") + workflow.add_node("rf/ReflectionManager", functools.partial(self.update_reflection_state, reflection_agent_name=reflection_agent_name)) + workflow.add_edge(to_agent_name, "rf/ReflectionManager") workflow.add_conditional_edges( - "reflection_counter", - self.__get_refelection_routing_fn(retry, reflection_agent_name, to_agent_name, next), - { to_agent_name: to_agent_name, next: next } + "rf/ReflectionManager", + self.__get_refelection_routing_fn(retry, reflection_agent_name, next), + { reflection_agent_name: reflection_agent_name, next: next } ) + workflow.add_edge(reflection_agent_name, to_agent_name) @staticmethod - def __get_refelection_routing_fn(retries: int, reflection_agent_name, to_agent_name, next): + def __get_refelection_routing_fn(retries: int, reflection_agent_name, next): def reflection_routing_fn(state: TeamFloAgentState): tracker = state["reflection_tracker"] - if tracker is not None and reflection_agent_name in tracker and tracker[reflection_agent_name] >= retries: + if tracker is not None and reflection_agent_name in tracker and tracker[reflection_agent_name] > retries: return next - return to_agent_name + return reflection_agent_name return reflection_routing_fn diff --git a/flo_ai/state/flo_session.py b/flo_ai/state/flo_session.py index 49b87272..17c98dc0 100644 --- a/flo_ai/state/flo_session.py +++ b/flo_ai/state/flo_session.py @@ -30,7 +30,6 @@ def __init__(self, self.config: Union[FloRoutedTeamConfig, FloAgentConfig] = None self.logger.info(f"New FloSession created with ID: {self.session_id}") self.langchain_logger = custom_langchainlog_handler or FloLangchainLogger(self.session_id, log_level=log_level, logger_name=f"FloLangChainLogger-{self.session_id}") - self.langchain_logger.set_session_id(self.session_id) def init_logger(self, log_level: str): FloLogger.set_log_level("SESSION", log_level) From 48d161f6a41c2c14547f07b1cdf79457e71640f9 Mon Sep 17 00:00:00 2001 From: Vishnu Satis Date: Mon, 23 Sep 2024 15:12:42 +0530 Subject: [PATCH 9/9] Delegator implementation with GraphRAG (#27) * Delegator agent impl * Fix for delegator at the end * Finished implementing delegator agent * Settin retry count for delegator * Implementing agentic RAG * Fix for composable RAG with messages from chain * Adding a tool agent example * Added graph rag example * Finished Graph RAG --- examples/agent_of_flo_ai.ipynb | 752 ++++++------------ examples/agentic_rag.ipynb | 251 ++++++ examples/delegator_example.py | 51 ++ examples/rag_tool.py | 70 ++ examples/rag_with_reranking.py | 7 +- ...exion_example.py => reflection_example.py} | 3 +- examples/tool_agent.py | 54 ++ flo_ai/common/flo_langchain_logger.py | 18 +- flo_ai/constants/flo_node_contants.py | 2 + flo_ai/core.py | 3 +- flo_ai/factory/agent_factory.py | 13 +- flo_ai/models/flo_delegation_agent.py | 78 ++ flo_ai/models/flo_executable.py | 14 +- flo_ai/models/flo_node.py | 10 +- flo_ai/models/flo_reflection_agent.py | 3 +- flo_ai/models/flo_routed_team.py | 1 + flo_ai/models/flo_tool_agent.py | 28 + flo_ai/retrievers/flo_retriever.py | 88 +- flo_ai/router/flo_linear.py | 10 +- flo_ai/router/flo_router.py | 68 +- flo_ai/state/flo_state.py | 6 +- flo_ai/tools/flo_pdf_rag_tool.py | 17 - flo_ai/tools/flo_rag_retriver_tool.py | 57 -- flo_ai/yaml/config.py | 5 +- 24 files changed, 997 insertions(+), 612 deletions(-) create mode 100644 examples/agentic_rag.ipynb create mode 100644 examples/delegator_example.py create mode 100644 examples/rag_tool.py rename examples/{reflexion_example.py => reflection_example.py} (97%) create mode 100644 examples/tool_agent.py create mode 100644 flo_ai/constants/flo_node_contants.py create mode 100644 flo_ai/models/flo_delegation_agent.py create mode 100644 flo_ai/models/flo_tool_agent.py delete mode 100644 flo_ai/tools/flo_pdf_rag_tool.py delete mode 100644 flo_ai/tools/flo_rag_retriver_tool.py diff --git a/examples/agent_of_flo_ai.ipynb b/examples/agent_of_flo_ai.ipynb index ede50487..78979e23 100644 --- a/examples/agent_of_flo_ai.ipynb +++ b/examples/agent_of_flo_ai.ipynb @@ -14,30 +14,25 @@ "\n", "2. LLM Agents (`kind: llm`): These agents are simply an LLM which can answer any questions asked to it. The agents dont except tools. If tool is passed to an agent of type llm, they are ignored.\n", "\n", - "3. Tool LLM (`kind: tool`): These agents are just tools or functions that can be executed on the current state. Within the tool will be given the current state as the input, meaning the history of what happened in the flo until now" + "3. Tool Agents (`kind: tool`): These agents are just tools or functions that can be executed on the current state. Within the tool will be given the current state as the input, meaning the history of what happened in the flo until now\n", + "\n", + "4. Reflection Agents (`kind: reflection`): These agents can help in reflecting on the current answer and retrying an exisitng flow. This is useful when you want to re-evaluate an answer with better model for example.\n", + "\n", + "5. Delegator Agent (`kind: delegator`): These agents can delegate to any other agent with the workflow based on a prompt. For example you want to delegate to different agents based on the user question" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 6, "metadata": {}, "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Log level -> INFO\n", - "Log level -> INFO\n", - "Log level -> INFO\n" - ] - }, { "data": { "text/plain": [ "True" ] }, - "execution_count": 1, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -63,31 +58,16 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-09-21 12:58:25,784 - SESSION - INFO - New FloSession created with ID: 9b56b8e0-bc8f-4c4c-b8a7-4a9695a7c97a\n", - "2024-09-21 12:58:25,785 - SESSION - INFO - Tool 'InternetSearchTool' registered for session 9b56b8e0-bc8f-4c4c-b8a7-4a9695a7c97a\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Log level -> ERROR\n" - ] - }, { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -98,12 +78,8 @@ "\n", "llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini')\n", "\n", - "# Create custom FloLangchainLogger\n", - "# custom_langchainlog_handler = FloLangchainLogger(\"CustomFloCallback\", \"DEBUG\")\n", - "\n", "session = FloSession(\n", " llm, \n", - " # custom_langchainlog_handler=custom_langchainlog_handler,\n", " log_level=\"ERROR\"\n", ")\n", "\n", @@ -126,7 +102,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -146,90 +122,45 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-09-21 12:58:31,259 - BUILDER - INFO - Building Flo instance from YAML\n", - "2024-09-21 12:58:31,265 - COMMON - INFO - Flo instance created for session 9b56b8e0-bc8f-4c4c-b8a7-4a9695a7c97a\n", - "2024-09-21 12:58:31,266 - COMMON - INFO - Invoking query for session 9b56b8e0-bc8f-4c4c-b8a7-4a9695a7c97a: Whats the whether in california\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", - "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\n", - "\n", - "\n", - "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'California City', 'region': 'California', 'country': 'United States of America', 'lat': 35.13, 'lon': -117.99, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1726903691, 'localtime': '2024-09-21 00:28'}, 'current': {'last_updated_epoch': 1726902900, 'last_updated': '2024-09-21 00:15', 'temp_c': 16.8, 'temp_f': 62.2, 'is_day': 0, 'condition': {'text': 'Clear', 'icon': '//cdn.weatherapi.com/weather/64x64/night/113.png', 'code': 1000}, 'wind_mph': 4.5, 'wind_kph': 7.2, 'wind_degree': 264, 'wind_dir': 'W', 'pressure_mb': 1011.0, 'pressure_in': 29.86, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 64, 'cloud': 0, 'feelslike_c': 16.8, 'feelslike_f': 62.2, 'windchill_c': 15.6, 'windchill_f': 60.0, 'heatindex_c': 15.8, 'heatindex_f': 60.5, 'dewpoint_c': 10.1, 'dewpoint_f': 50.2, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 1.0, 'gust_mph': 7.8, 'gust_kph': 12.5}}\"}, {'url': 'https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023', 'content': 'Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...'}, {'url': 'https://www.meteoprog.com/weather/California-california/month/october/', 'content': 'California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \"near me\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG'}, {'url': 'https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States', 'content': 'October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim'}, {'url': 'https://world-weather.info/forecast/usa/los_angeles/october-2023/', 'content': 'Extended weather forecast in Los Angeles. Hourly Week 10 days 14 days 30 days Year. Detailed ⚡ Los Angeles Weather Forecast for October 2023 - day/night 🌡️ temperatures, precipitations - World-Weather.info.'}]\u001b[0m\u001b[32;1m\u001b[1;3mThe current weather in California is as follows:\n", - "\n", - "- **Location**: California City\n", - "- **Temperature**: 16.8°C (62.2°F)\n", - "- **Condition**: Clear\n", - "- **Wind**: 4.5 mph (7.2 kph) from the west\n", - "- **Humidity**: 64%\n", - "- **Visibility**: 16 km (9 miles)\n", - "\n", - "For more detailed weather information, you can check the following resources:\n", - "- [Weather API](https://www.weatherapi.com/)\n", - "- [Time and Date - Los Angeles Weather](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\n", - "- [Meteoprog - California Weather](https://www.meteoprog.com/weather/California-california/month/october/)\n", - "- [Weather Spark - Anaheim Weather History](https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States)\n", - "\n", - "If you need specific information about a particular city in California, let me know!\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" + "2024-09-22 13:22:31,840 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-22 13:22:31,846 - COMMON - INFO - Flo instance created for session e7f22b4d-7380-4d00-97c9-5267a468468a\n" ] }, { "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCADqAJYDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwMECAIBCf/EAEcQAAEDBAADAgkIBgkFAQEAAAECAwQABQYRBxIhEzEUFRYiQVFWlNMIFyMyVFVh0TZxdIGVsjM0QlJydZOhtCQnkbHBJfD/xAAbAQEBAAMBAQEAAAAAAAAAAAAAAQIDBAUHBv/EADQRAAIAAwUEBwgDAQAAAAAAAAABAgMRBBIUIVExYZHRQVJicZKh8AUTIzNTgbHBIjLx4f/aAAwDAQACEQMRAD8A/qnSlKAUpUNe7xIZktW22Noeur6SsF0EsxmwdF13WjrfRKAQVnoCAFLRlDC43RAlnHUMoK3FpQhPepR0BUerKLMhRCrtBBHeDJR+dRzeBWuQ4H7uhV/l7J7a5AOJTvppDeuRA106J36yTs1304pZEgAWe3gDoAIqPyrdSStrb9etC5H75VWX74ge8o/OnlVZfviB7yj86eStl+54HuyPyp5K2X7nge7I/Knwd/kXIeVVl++IHvKPzp5VWX74ge8o/OnkrZfueB7sj8qeStl+54HuyPyp8Hf5DIeVVl++IHvKPzp5VWX74ge8o/OnkrZfueB7sj8qeStl+54HuyPyp8Hf5DIeVVl++IHvKPzr7ayO0vrCWrpCcUfQiQgn/wB18eStl+54HuyPyr5cxGxPI5HLLblp7+VUVsj/ANU+Dv8AImRLUqreRKbJ9NjL3ihaevgGyYTo/ulrub/xN8pHTYUBymYsl5ReYy1di5FksrLUiK9rnZWPQddCCCCCOhBBHfWMUCpegdV5imhI0pStJBSlKAUpSgFVfBNXCLPva9KfuUt1QV6mG1qbZT+A5U82h05lq797NoqscOB4PizcFWw7AkPw1gjXVDqgD+op5VD8FCuiHKVE1quGf7SL0EXxn4oJ4TYSq7tW9d5usqUxbbXa21hCpkx9YQy1zH6oJOyfQEnv7qr0e+cWcVxXKb3mJw15uBZpE2IzZGpYUiQ2grCHO0XpaOh2U8hPqG+n18pbC77lmE2e4YzETc79i99hZFEtq1hAmmOslbIUegKkKXo+vXd31FXPitN4qYRmFngcPc0tCl49OKn73ajET25aKUR20qVzOrUVHq2FJ83v6iuchL8GvlFYrxVgWCC1eI6sqm2lqe/CbjPMtLXyILwYW4nldShaiDyLWU66noalcV+UDgOa5KrH7Rfu3u3I443HehyGBIS3/SFlTjaUvcvp7Mq6dazKNit/ju/JlcjWWWJFmtkhqb2sZaUQlmzKQlD519EC6Ep0rXnaHfWe4Vbssu3EHg/kF/tHEKdkluukgZNKu0d5NuhOvxH2QmMyD2fZc6wO1aSUhABWvqKA3HF/lG40vD3skyHILexa5WQybNbno0CawoqRzqbYdbebDgkcrawocoSVAJTskA8HEH5RVvjcJbvlmEyI9zl226wbZIjXOI+wphb0thpaXWV9m4hQbe5k7A9B6joc1gYTkKUWMKsNzHZ8a7hdV7huebDUqZyyT5vRo86NOfVPMOvUVxcYsIyK5TOOZhWC5ykXK8Ym/D8HhuLEpLLkbtlt6T54QEHmKd8oSd61QG35R8orh7hl7utou9/Ma4Wlbabg03BkvCGFtocQt5SG1JbbKXEntFEI3sb2kgcmW/KAwTB747Z7teH03FqE3cVsw7bKl8sZZUEvEstLARtCtq3odN62N5xd8Tu8id8qH/8AGnOIvNvZbtx8FWROIsyWylnp9IefaNJ353Tvqn4bnT3C3i0fGGMZNeph4dY+y5DstqclvtvIVJ2h1A6tknY2rQBB2RQHpO2cTsXvd1sdut94ZnSb3b3LrbvB0qW3IioKAtxLgHKAC6joSCd9B0Os/wCI3yo8ZwhFhciIlXlqdkzuNy1xoMpZiOM8wkEJQyouKSpISEJ+vzEpKghVZZw2wzJOCbnCO+X3GrrJjx7Te4dwiWOIue7bHZkxuWw0ptoFRSlILZUkFKSnqQCDXVh2LJ2cBiZI9h1/bXbuLcvJZNnEJSp4grdfT2iGk7Luu2SfMKgQCUkjrQHru2XFm722JPjdp4PKZQ+32zS2l8qkhQ5kLAUk6PVKgCO4gGoK46tGd2mSjSW7s05BfHXz3G0l1pXq6JS+Px5h6qmbJdUX20Q7g3GlRESmkuhicwph9sEb5Vtq6pUPSD1FQ2RJ8Ny/FIqdlUd6RcF6HQISwtnqfR50hP69GuiT/Zrc/wAP9lRZ6UpXOQUpSgFKUoBVbuLDuOXaReYrC5EKUE+MYzCFLd5kgJS+2kbKiEjlUkDakpTy9Ucq7JStkEdx7ntKjrwLhFukVEmHIalR1/VdZWFJP7xXYqAuGEWubMcmNJfts1wkuSbdIXHU4da2sIICzrXVYPcPUK63kQ+O7Kb8kegdu0f/AG3utl2U9kVO9cv+DItFKq/kTI9qr9/rM/Cp5EyPaq/f6zPwqe7l9fyYotS0Uqr+RMj2qv3+sz8KqnYrfdbjxGyuyPZTePAbZFgPRyl1ntOZ4Pc/N9H3fRp10Hp76e7l9fyYotTVKhY2HWiJl07J2onJfJsRmDIldqs87DSlqbRyE8o0pxZ2ACd9SdCuj5EyPaq/f6zPwqeRMj2qv3+sz8Knu5fX8mKLUtFKq/kTI9qr9/rM/Cr9GEOkacya+up3vXhLaf8AdLYP+9Pdy+v5MUWpLXq/wrAyhct3TjquRiOgczr6/wC42gdVH9XcOp0ATXUsFqkIlyrtcUIRcpiUo7JCuZMdlJJQ2D6T5xKiO8k+gCuSy4na7A84/FjlUtxPK5MkurffWPUXFkqI/DevwqYqOKGFOGX07WO4UpStBBSlKAUpSgFKUoBSlKAUpSgFZ7ihHz08QBs83gNp2P3Sfx/+VoVZ7im/no4gd2vAbT6Bvuk/v/8ANAaFSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBWeYmP+9fEE8wJ8AtPTXUdJVaHWeYnr56+IPr8AtPo/CV6aA0OlKUApSlAKUpQClKUApSlAKV+KUEJKlEJSBsknoBVKOYXu7ASLLbIJtq+rMi4SVtuPJ9Cw2ls8qT3jZ2R3gVulyoptbvItKl2pVI8e5h9gsfvb3w6ePcw+wWP3t74dbsLHquKFC70qkePcw+wWP3t74dPHuYfYLH7298OmFj1XFChd6VSPHuYfYLH7298Onj3MPsFj97e+HTCx6rihQtV7lS4NmnybfCFxnsx3HI8NTvZB9wJJS3z6PLzEAc2jre9GvDPBP5dkviF8ol6yxOG0lidkj0O3vIVdApUBEcu9s6odgCrlStSuXY+prfXdevPHuYfYLH7298OsgwXgA9gPG7LOJlvt9mN1vzYT4KqQ72cVSiC+tB7Pe3FBJPdrzh3KphY9VxQoelqVSPHuYfYLH7298Onj3MPsFj97e+HTCx6rihQu9KpHj3MPsFj97e+HTx7mH2Cx+9vfDphY9VxQoXelUjx7mH2Cx+9vfDp49zD7BY/e3vh0wseq4oULvSqR49zD7BY/e3vh13bZlk9mfGiXyDHi+FL7JiVDfU60XNbCFhSUlBOjo9QSNbBKQcXZpiVcn90KFqpSlcpCLyglOM3cg6IhvEEf4DVexkAY3agAABEa0B/gFWHKv0YvH7G9/Iar2Nfo5av2Rr+QV6Mn5L7/wBF6CSpSo1OSWxWSLsAmtm8oiJnKh788MFZQHP1cySP3VSElSlKoFKV0XL5b2r0xaFzWE3R9hcpuGXB2q2kKSlTgT38oK0gnu2oUB3qVld5+U/w5sV9udnlXeeq4W2QqLLbi2OfIS06kAlBW2wpJIBHcT3itRYeTIZbdQSULSFJJBB0RsdD1FSqYPulKVQKUroyL5b4t3iWt6aw3cpbbjseIpwB11COXnUlPeQnmTs+jmHroDvUpSgFQGZHlg2wjv8AHFuG9euW0D/san6gM0/qFs/zi2/8xmtsn5kPeVbUaFSlK8chF5V+jF4/Y3v5DVexr9HLV+yNfyCrDlX6MXj9je/kNV7Gv0ctX7I1/IK9GT8l9/6L0EkSACSdAV4stPFu0vcWLZxPSbn2tyyR2xuurtUtERNicSmNHX4SW+xI8Iaaf+uf6ZQ7+leyrnbmLxbZcCUlS4splbDqUOKbUUKSUqAUkhSTonqCCPQar03hdi9w4dowR+1IViiIjcFNvDriQGW+XkAWFBexypPNzc2xve6kSb2EPMec3nIciy/LbaMiytjiBHyuLFtOO22RIZgOWguMEOLDWkcimS8tbpUFJUNbHcZB5XFHi/k3EObj856C/ZL5Ks1sLeVO29mD2ASG1uwkxHEPhew4S4o8wXyjlAqwZ58nPLr/AJtd7hj0m0423OlokNX6He7s3NjkBAUtURLng7rhCdbJAPTYPp1XJeAWBZdkrt/ulgS9dXw2JDzMp9hMrk+p2zba0od1oAc6VdAB3VhdbBkFzgZJleU8Xm7rlt9tczHbLbpUWPY7m7HjMTFQVrcWkDRUnnbHmK807JKSTuutjtrHErjVwoyC7XK7R59z4eC6PeL7o/FQp7tYaykJbWkchLiipH1VaTzA8o16J8gLD4xySd4B/wBVkTLce6Ods5/1Dbbam0DXNpOkrUNp0evXrUHeuBuE5Bacbt02zKMfHGBFtamJkhl6MyEJR2YdQ4laklKEghSiDyjezWV1gy3ghCzV7iHxQes91skbGUZ3J8NhzIDrst0+Dxefs3UupSjaeUDaFaIJ670IriRkl1tXHxrh5GzSZCxzK5EWZPk+Ev8AhFnc0vUJh8dGPC+xHInmBTyu8o89NafefkwcOb7fbneJVonpuFykKlS3It8nx0uuqABWUNvpSCQB3AdwqelcFsMnWjIbbJswkRcglJmXIuyXlOvPJ5QhYdK+dHJyI5AhSQjXm6pddKAwS4K4lcW824i+IpkiEceuq7PbUs5W7bEw+RltSHnIqYrqZHOpZXzOqIUPNATy7NnsFvyXi1xMyfH8syu7WN3FLbamjCxa4LgokSn43avyStIC3EdptCEnzQEHadmtHyn5P+A5ne1Xe72Hwm4uNIYffRMkM+FIQNJS+EOJD+h0+kCuldrN+CWFcRLqxc77ZfCLkyyYyZcaU/FdUzvfZrUytBWjZJ5VbHU9OtLrBUsUu1wi8XuLFqfu86TBtlksy4iJUlSg0S1L7RxI7kqUUJKlJA2UjfcKx7AbArP8q+T5cr1fL+7cJ2FS3n5TN6ksuOuNqiq2VJWCSrnPP/fATzb5Rr0Xf+BmD5NLiSbhYw47GhJtqexlPMpcip+qw6lC0h5sbOkuBQ6n11x3PgLgt3xvHbFJsh8XY832VrDM2Q09FQUhJSl5DgcIKQAQVEK0N71RwsGI5Nc7/fsR4y8QV5pe7Jd8Putwi2i3w5pagsohoSppDsf6rxfPUlwE6cSE60KteCIuvEbjrlkm536+wLda7fYLgxY4lwdYjofdbccXzpSRtO0cqm/qr2eYK0NaFkHAHAcpyRV9umOty7i4ppx/ch5LMlTeuzU8yFht4p0NFxKj0Hqq027EbTackvF/iROyu13Qw3Nkdos9qlkKDQ5SeVPKFq+qBvfXfSihYJioDNP6hbP84tv/ADGan6gM0/qFs/zi2/8AMZrpk/Mh7yrajQqUpXjkIvKv0YvH7G9/Iar2Nfo5av2Rr+QVcZDDcphxl1PO04koUk+kEaIqhsxb/jMdm3Jsj18jx0JaZmQ5DKVLQBpPaJdWjS9DrokHv6b5R6FnacDgrR1rm6fkyWaoTtKhPG1+9jLr71C+PTxtfvYy6+9Qvj1vudpeJcxQm6VCeNr97GXX3qF8enja/exl196hfHpc7S8S5ihN0qE8bX72MuvvUL49PG1+9jLr71C+PS52l4lzFCbpUJ42v3sZdfeoXx6jombz598uNnYxS6ruNvbZdks9vEHZpd5+zOy9o77NfcTrXXXSlztLxLmKFspUJ42v3sZdfeoXx6eNr97GXX3qF8elztLxLmKE3SoTxtfvYy6+9Qvj08bX72MuvvUL49LnaXiXMUJulQnja/exl196hfHp42v3sZdfeoXx6XO0vEuYoTdQGaf1C2f5xbf+YzXJ42v3sZdfeoXx654tpuuRzoarhb1We3xXkSS288hx55xBCkJ+jUpKUhXUnZJ5QNdd1lDSXEo4mqLen+GEqOpd6UpXjGIpSlAKUpQClKUApSlAKz/FR/3lz4674Nq66/CT6df/AE/u9OgVnuKJ1xp4gK0esG09ddO6T6aA0KlKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQCs8xMj56+IPXr4BadjX4SvTWh1n2KBXzz5/sr5fAbToEeb3Se7/+9VAaDSlKAUpSgFKUoBSlKAUpSgFKUoBSlcUmSzDYW/IdQwygbU44oJSkesk91NoOWlVhzihh7SiFZTZtgkHU5o6I7x9bvr4+dXDfaqz++t/nXRh53UfBlo9C1Uqq/OrhvtVZ/fW/zp86uG+1Vn99b/OmGn9R8GW69Cxzp0a2QpEyZIaiRI7annpD6whtpCRtSlKPQAAEknoAKxvC+KuETONeZJj5hYH3J8a0x4gaubCjJd/6hPZt6X56tqSOUDe1D11ep/EbBLpBkQpeSWSTEkNqZeZcmNlLiFAhSSN9QQSK/n98nz5N2O4P8rm8XK63u2+RmMveH2aW7LR2ctxZ3HSlWxtTXUq13KQn10w0/qPgxdeh/TKlVX51cN9qrP763+dPnVw32qs/vrf50w0/qPgxdehaqVVfnVw32qs/vrf50+dXDfaqz++t/nTDT+o+DF16FqpVci8SMTnPIZYya0OvL+q2mc1zK/UObZqxAggEHYPprXHLjl5RprvJSh+0pStZBSlKAUpSgK9m+ZxsKtAlOoMiU8rso0VKtF1ff3+hIGyVegDoCSAfP16mzMpmeF3uR4xfB5kNrGmGfwbbJIT6t9VEd6jVj4q3Rd14iTGVKJatbDcVtB7krWkOuKH6wpoH/AKo+S31jF8dul5lBSo1viuy3QnvKUIKiB+OhX732ZZILNIU6L+zVa6L/A3TJEiAEjQGh6hSszwGDneQx7Pkt4ypuJHmoTLXj8a3NFltpY5ktdqfpCoAjat9++lQ+EcblIafZyGLdXkG/SrWm8phoTDZPhK22G1KBB7uRPMEkbI5lb3Xo4iHK8mq6+t5ibJSqRkHFeHjF7MK42S9x4CX2oyr0Yg8BStwpCNr5ubRUpKeYJ0CdE1FXTiqzjmU5ku5G5ottgtseUuEYjPKpKluAvNOBzmXza1yqCdcmx31m58uHa/WfIGmUrP3OM9sjovImWq62t+32p28oauDCWjKjN/WW3pStdeUFKglQ5htNU6ffOIuO4A3xCl5BGmNojt3KXjIgNoZRHVpSm23h9JzpQd8xJBI7tVhFaYIdmetOgG40qqRuJVqloylbTUpSMebQ7JVyJ06lUdMgdn53XzFAedy9fw61V43GGdcOI9mtMLHrhKstxsjVzS62hkOI7VxADiyXhptCVaUACrm7goVnFPlw0z2/wCENTpWbzeOlqgOXhx2yX02yzzlwJ91RGbVHjrRrmUdOc5QOYEkJOh3gVPo4i2x1WWBDchYxtCVylpSkpdCo6XwWjzdfMUB111/DrVU6W9j9Z8mCzutIeQUuIS4k96VDYqXxLKrhg8hBgLW7btjtbYpX0RT6ezB6Nq9WtA+kekYvG4wzrhxHs1phY9cJVluNkauaXW0MhxHauIAcWS8NNoSrSgAVc3cFCtTrB+6tUEUESqiptHpix3qJkVpjXKA720WQjmQrWiPQQR6CCCCPQQRXerIuBF0cRLv1oUrbCeymsp/ulfMlYHqG20q/Wo1rtfPbZZ8LPildC/DzM2KUpXEQUpSgPPfE6C5buJF1K98k5piY0T3aCA0oD9RaBP+IeuqVlFhZynGrtZZCihi4xHYjik96UuIKSR+I3XoziLgqc1trJYcRHusMqXFeWPNO9c7a9deVWh1HcQk6OtHBXg5DnOwJjK4Vwa/pIj+gtP4+pSfUoEg+g19A9m2mC02dS3thVGt2wPPMy7Bp/ESxQrNjdyxOLIRB7OI9f03NsMOsI0ntEtaLnOUj6pAG/SBUPI4d5CvhFeLKm37ub+SKntMds35zHjNL/Pzc2h9GCrRO/RrfStqpXbh04bsUTeTXRsf2MTzpxA4W5NkEjKwvFkX67SbimXbb7IuDaUMRULbWiO0hR5kL0lSO4JJUSVVYeJ3D7IcllcQ3bfbS6LvjkWDDCn2klx9DjylI6q6aC09TodehNbVSsMHBnm8+7fu3sGa5tw9lZfnEUvMEWR/HLhaZUlK07bW8pkJATvZOkrOwNdO/uqqTbBxGyLh+zw7nWGNCZUy3bpeTpntraXGRoKW2z/SdopCdaUAASetbrSs4rNDE26tV28vWYMZvuKZPYrjxCi2XHhd4WSxGkxJCZrTSIy0xBHKHAshX9gEFIO96JHfXLbMYyfEb9hl4jWI3cMYy1Y50VqW005FdSptfPtagladpUDyknpsbrYaUw0NapvyyzroDzpDbynILRxRxex48mUxd7/PiLvD8xtDMVLiEIWVNk86iEnY5Qdkju1U/cMMyjF3M6t1msJvkTIIDDMSZ4Y0yllbcMRil0LIV/YCgUgg70SO+tjhWyHbTIMSIxF8IdU+92LYR2ritcy1aHVR0Nk9eldmtcNkVP5ROv237t4MetmMZPiN+wy8RrEbuGMZasc6K1LaaciupU2vn2tQStO0qB5ST02N1sNK7dgs83K7mbfamw86hQD75G2owP8AaWfX6kb5j+A2RvUMFnhcTeW/oCVS88CIK3rvkNx19A2hiEhXrWOZxY/cFt/+a2KonFcai4lYo1ricym2QSp1f13Vk7UtX4kkn1DuGgAKlq+e220K02iKatj2dyyM2KUpXCQUpSgFRl9xm05PGSxdrdGuDaCSgPthRQT6UnvSfxGjUnSsoYnA70LowUNfBDEFd0KY2Nk6bucpI/2cr5+Y3Efss/8Ai0v4tX6ldeOtX1YuLLVlB+Y3Efss/wDi0v4tPmNxH7LP/i0v4tX6lXHWr6sXFirKD8xuI/ZZ/wDFpfxafMbiP2Wf/Fpfxav1KY61fVi4sVZQfmNxH7LP/i0v4tPmNxH7LP8A4tL+LV+pTHWr6sXFirKD8xuI/ZZ/8Wl/Fp8xuI/ZZ/8AFpfxav1KY61fVi4sVZRWeCeHtLSpVtfkcv8AZk3CQ6g/rSpwg/vFXG3W2HaIbcSBFYhRWxpDEdsNoSPwSBoV2aVomT5075kbfe2xVsUpStBBSlKA/9k=", "text/plain": [ - "{'messages': [HumanMessage(content='Whats the whether in california')],\n", - " 'output': 'The current weather in California is as follows:\\n\\n- **Location**: California City\\n- **Temperature**: 16.8°C (62.2°F)\\n- **Condition**: Clear\\n- **Wind**: 4.5 mph (7.2 kph) from the west\\n- **Humidity**: 64%\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following resources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date - Los Angeles Weather](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog - California Weather](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [Weather Spark - Anaheim Weather History](https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States)\\n\\nIf you need specific information about a particular city in California, let me know!'}" + "" ] }, - "execution_count": 8, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ + "from IPython.display import Image, display\n", "flo = Flo.build(session, simple_weather_checking_agent,log_level=\"DEBUG\")\n", "\n", - "flo.invoke(\"Whats the whether in california\")" + "flo.draw()" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-09-20 00:38:07,862 - BUILDER - INFO - Building Flo instance from YAML\n", - "2024-09-20 00:38:07,870 - COMMON - INFO - Flo instance created for session dd5c4a4c-4390-46bc-945d-eb9474e63702\n", - "2024-09-20 00:38:07,872 - COMMON - INFO - Invoking query for session dd5c4a4c-4390-46bc-945d-eb9474e63702: Whats the whether in california\n", - "2024-09-20 00:38:07,876 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'messages': [HumanMessage(content='Whats the whether in california')]}\n", - "2024-09-20 00:38:07,901 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:08,012 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:08,035 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:08,038 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:08,041 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: []\n", - "2024-09-20 00:38:08,042 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: {'agent_scratchpad': []}\n", - "2024-09-20 00:38:08,043 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: {'messages': [HumanMessage(content='Whats the whether in california')], 'intermediate_steps': [], 'agent_scratchpad': []}\n", - "2024-09-20 00:38:08,045 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'messages': [HumanMessage(content='Whats the whether in california')], 'intermediate_steps': [], 'agent_scratchpad': []}\n", - "2024-09-20 00:38:08,047 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: messages=[SystemMessage(content='Given the city name you are capable of answering the latest whether this time of the year by searching the internet\\n'), HumanMessage(content='Whats the whether in california')]\n", - "2024-09-20 00:38:08,048 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMStart: ['System: Given the city name you are capable of answering the latest whether this time of the year by searching the internet\\n\\nHuman: Whats the whether in california']\n" + "2024-09-22 13:22:34,352 - COMMON - INFO - Invoking query for session e7f22b4d-7380-4d00-97c9-5267a468468a: Whats the whether in New Delhi, India ?\n" ] }, { @@ -238,361 +169,40 @@ "text": [ "\n", "\n", - "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-09-20 00:38:09,158 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,160 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,171 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,173 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,196 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,199 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,215 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,218 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,359 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,364 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,367 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,370 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:09,373 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMEnd: [[ChatGenerationChunk(generation_info={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, message=AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]))]]\n", - "2024-09-20 00:38:09,375 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: content='' additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]} response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'} id='run-456fe998-ec19-48fa-ad0b-c22361d37f68' tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}] tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]\n", - "2024-09-20 00:38:09,381 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: [ToolAgentAction(tool='tavily_search_results_json', tool_input={'query': 'California weather October 2023'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}])], tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]\n", - "2024-09-20 00:38:09,383 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: [ToolAgentAction(tool='tavily_search_results_json', tool_input={'query': 'California weather October 2023'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}])], tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]\n", - "2024-09-20 00:38:09,384 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onAgentAction: tavily_search_results_json - {'query': 'California weather October 2023'}\n", - "2024-09-20 00:38:09,388 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onToolStart: {'query': 'California weather October 2023'}\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", "\u001b[32;1m\u001b[1;3m\n", - "Invoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\n", + "Invoking: `tavily_search_results_json` with `{'query': 'current weather in New Delhi, India'}`\n", "\n", "\n", - "\u001b[0m" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-09-20 00:38:13,560 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onToolEnd: [{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'California City', 'region': 'California', 'country': 'United States of America', 'lat': 35.13, 'lon': -117.99, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1726772854, 'localtime': '2024-09-19 12:07'}, 'current': {'last_updated_epoch': 1726772400, 'last_updated': '2024-09-19 12:00', 'temp_c': 22.3, 'temp_f': 72.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 3.4, 'wind_kph': 5.4, 'wind_degree': 153, 'wind_dir': 'SSE', 'pressure_mb': 1013.0, 'pressure_in': 29.91, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 35, 'cloud': 0, 'feelslike_c': 23.9, 'feelslike_f': 75.1, 'windchill_c': 24.3, 'windchill_f': 75.7, 'heatindex_c': 24.5, 'heatindex_f': 76.0, 'dewpoint_c': 7.6, 'dewpoint_f': 45.6, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 6.0, 'gust_mph': 3.9, 'gust_kph': 6.2}}\"}, {'url': 'https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023', 'content': 'Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...'}, {'url': 'https://www.meteoprog.com/weather/California-california/month/october/', 'content': 'California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \"near me\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG'}, {'url': 'https://world-weather.info/forecast/usa/california/october-2023/', 'content': \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world's temperature today Temperature units\"}, {'url': 'https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States', 'content': 'October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim'}]\n", - "2024-09-20 00:38:13,574 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:13,585 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:13,591 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:13,595 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'input': ''}\n", - "2024-09-20 00:38:13,597 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]', additional_kwargs={'name': 'tavily_search_results_json'}, tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]\n", - "2024-09-20 00:38:13,598 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: {'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]', additional_kwargs={'name': 'tavily_search_results_json'}, tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]}\n", - "2024-09-20 00:38:13,600 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: {'messages': [HumanMessage(content='Whats the whether in california')], 'intermediate_steps': [(ToolAgentAction(tool='tavily_search_results_json', tool_input={'query': 'California weather October 2023'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}])], tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk'), [{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'California City', 'region': 'California', 'country': 'United States of America', 'lat': 35.13, 'lon': -117.99, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1726772854, 'localtime': '2024-09-19 12:07'}, 'current': {'last_updated_epoch': 1726772400, 'last_updated': '2024-09-19 12:00', 'temp_c': 22.3, 'temp_f': 72.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 3.4, 'wind_kph': 5.4, 'wind_degree': 153, 'wind_dir': 'SSE', 'pressure_mb': 1013.0, 'pressure_in': 29.91, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 35, 'cloud': 0, 'feelslike_c': 23.9, 'feelslike_f': 75.1, 'windchill_c': 24.3, 'windchill_f': 75.7, 'heatindex_c': 24.5, 'heatindex_f': 76.0, 'dewpoint_c': 7.6, 'dewpoint_f': 45.6, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 6.0, 'gust_mph': 3.9, 'gust_kph': 6.2}}\"}, {'url': 'https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023', 'content': 'Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...'}, {'url': 'https://www.meteoprog.com/weather/California-california/month/october/', 'content': 'California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \"near me\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG'}, {'url': 'https://world-weather.info/forecast/usa/california/october-2023/', 'content': \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world's temperature today Temperature units\"}, {'url': 'https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States', 'content': 'October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim'}])], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]', additional_kwargs={'name': 'tavily_search_results_json'}, tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]}\n", - "2024-09-20 00:38:13,602 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'messages': [HumanMessage(content='Whats the whether in california')], 'intermediate_steps': [(ToolAgentAction(tool='tavily_search_results_json', tool_input={'query': 'California weather October 2023'}, log=\"\\nInvoking: `tavily_search_results_json` with `{'query': 'California weather October 2023'}`\\n\\n\\n\", message_log=[AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}])], tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk'), [{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'California City', 'region': 'California', 'country': 'United States of America', 'lat': 35.13, 'lon': -117.99, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1726772854, 'localtime': '2024-09-19 12:07'}, 'current': {'last_updated_epoch': 1726772400, 'last_updated': '2024-09-19 12:00', 'temp_c': 22.3, 'temp_f': 72.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 3.4, 'wind_kph': 5.4, 'wind_degree': 153, 'wind_dir': 'SSE', 'pressure_mb': 1013.0, 'pressure_in': 29.91, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 35, 'cloud': 0, 'feelslike_c': 23.9, 'feelslike_f': 75.1, 'windchill_c': 24.3, 'windchill_f': 75.7, 'heatindex_c': 24.5, 'heatindex_f': 76.0, 'dewpoint_c': 7.6, 'dewpoint_f': 45.6, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 6.0, 'gust_mph': 3.9, 'gust_kph': 6.2}}\"}, {'url': 'https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023', 'content': 'Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...'}, {'url': 'https://www.meteoprog.com/weather/California-california/month/october/', 'content': 'California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \"near me\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG'}, {'url': 'https://world-weather.info/forecast/usa/california/october-2023/', 'content': \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world's temperature today Temperature units\"}, {'url': 'https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States', 'content': 'October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim'}])], 'agent_scratchpad': [AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]', additional_kwargs={'name': 'tavily_search_results_json'}, tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]}\n", - "2024-09-20 00:38:13,603 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: messages=[SystemMessage(content='Given the city name you are capable of answering the latest whether this time of the year by searching the internet\\n'), HumanMessage(content='Whats the whether in california'), AIMessageChunk(content='', additional_kwargs={'tool_calls': [{'index': 0, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'function': {'arguments': '{\"query\":\"California weather October 2023\"}', 'name': 'tavily_search_results_json'}, 'type': 'function'}]}, response_metadata={'finish_reason': 'tool_calls', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-456fe998-ec19-48fa-ad0b-c22361d37f68', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': 'California weather October 2023'}, 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'type': 'tool_call'}], tool_call_chunks=[{'name': 'tavily_search_results_json', 'args': '{\"query\":\"California weather October 2023\"}', 'id': 'call_BiZ1tUx2CkEuvDao92dT9JNk', 'index': 0, 'type': 'tool_call_chunk'}]), ToolMessage(content='[{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]', additional_kwargs={'name': 'tavily_search_results_json'}, tool_call_id='call_BiZ1tUx2CkEuvDao92dT9JNk')]\n", - "2024-09-20 00:38:13,605 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMStart: ['System: Given the city name you are capable of answering the latest whether this time of the year by searching the internet\\n\\nHuman: Whats the whether in california\\nAI: \\nTool: [{\"url\": \"https://www.weatherapi.com/\", \"content\": \"{\\'location\\': {\\'name\\': \\'California City\\', \\'region\\': \\'California\\', \\'country\\': \\'United States of America\\', \\'lat\\': 35.13, \\'lon\\': -117.99, \\'tz_id\\': \\'America/Los_Angeles\\', \\'localtime_epoch\\': 1726772854, \\'localtime\\': \\'2024-09-19 12:07\\'}, \\'current\\': {\\'last_updated_epoch\\': 1726772400, \\'last_updated\\': \\'2024-09-19 12:00\\', \\'temp_c\\': 22.3, \\'temp_f\\': 72.1, \\'is_day\\': 1, \\'condition\\': {\\'text\\': \\'Sunny\\', \\'icon\\': \\'//cdn.weatherapi.com/weather/64x64/day/113.png\\', \\'code\\': 1000}, \\'wind_mph\\': 3.4, \\'wind_kph\\': 5.4, \\'wind_degree\\': 153, \\'wind_dir\\': \\'SSE\\', \\'pressure_mb\\': 1013.0, \\'pressure_in\\': 29.91, \\'precip_mm\\': 0.0, \\'precip_in\\': 0.0, \\'humidity\\': 35, \\'cloud\\': 0, \\'feelslike_c\\': 23.9, \\'feelslike_f\\': 75.1, \\'windchill_c\\': 24.3, \\'windchill_f\\': 75.7, \\'heatindex_c\\': 24.5, \\'heatindex_f\\': 76.0, \\'dewpoint_c\\': 7.6, \\'dewpoint_f\\': 45.6, \\'vis_km\\': 16.0, \\'vis_miles\\': 9.0, \\'uv\\': 6.0, \\'gust_mph\\': 3.9, \\'gust_kph\\': 6.2}}\"}, {\"url\": \"https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023\", \"content\": \"Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...\"}, {\"url\": \"https://www.meteoprog.com/weather/California-california/month/october/\", \"content\": \"California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \\\\\"near me\\\\\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG\"}, {\"url\": \"https://world-weather.info/forecast/usa/california/october-2023/\", \"content\": \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world\\'s temperature today Temperature units\"}, {\"url\": \"https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States\", \"content\": \"October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim\"}]']\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[36;1m\u001b[1;3m[{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'California City', 'region': 'California', 'country': 'United States of America', 'lat': 35.13, 'lon': -117.99, 'tz_id': 'America/Los_Angeles', 'localtime_epoch': 1726772854, 'localtime': '2024-09-19 12:07'}, 'current': {'last_updated_epoch': 1726772400, 'last_updated': '2024-09-19 12:00', 'temp_c': 22.3, 'temp_f': 72.1, 'is_day': 1, 'condition': {'text': 'Sunny', 'icon': '//cdn.weatherapi.com/weather/64x64/day/113.png', 'code': 1000}, 'wind_mph': 3.4, 'wind_kph': 5.4, 'wind_degree': 153, 'wind_dir': 'SSE', 'pressure_mb': 1013.0, 'pressure_in': 29.91, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 35, 'cloud': 0, 'feelslike_c': 23.9, 'feelslike_f': 75.1, 'windchill_c': 24.3, 'windchill_f': 75.7, 'heatindex_c': 24.5, 'heatindex_f': 76.0, 'dewpoint_c': 7.6, 'dewpoint_f': 45.6, 'vis_km': 16.0, 'vis_miles': 9.0, 'uv': 6.0, 'gust_mph': 3.9, 'gust_kph': 6.2}}\"}, {'url': 'https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023', 'content': 'Weather reports from October 2023 in Los Angeles, California, USA with highs and lows. Sep 17-18. Sign in. News. News Home; Astronomy News; Time Zone News; Calendar & Holiday News; Newsletter; Live events. ... High & Low Weather Summary for October 2023 Temperature Humidity Pressure; High: 92 °F (Oct 5, 11:53 am) 100% (Oct 7, 9:02 am) 30.11 ...'}, {'url': 'https://www.meteoprog.com/weather/California-california/month/october/', 'content': 'California (United States) weather in October 2023 ☀️ Accurate weather forecast for California in October ⛅ Detailed forecast By month Current temperature \"near me\" Weather news ⊳ Widget of weather ⊳ Water temperature | METEOPROG'}, {'url': 'https://world-weather.info/forecast/usa/california/october-2023/', 'content': \"Weather in California in October 2023 (Maryland) - Detailed Weather Forecast for a Month Weather Weather in California Weather in California in October 2023 California Weather Forecast for October 2023 is based on statistical data. 1 +77°+61° 2 +79°+63° 3 +79°+61° 4 +77°+59° 5 +75°+59° 6 +77°+66° 7 +64°+64° 8 +64°+52° 9 +66°+50° 10 +70°+55° 11 +72°+54° 12 +70°+54° 13 +68°+54° 14 +64°+54° 15 +63°+59° 16 +61°+50° 17 +63°+54° 18 +63°+50° 19 +70°+50° Average weather in October 2023 Weather in Washington, D.C.+79° Annapolis+77° Fairfax+77° Fredericksburg+77° Manassas+79° McLean+79° Mechanicsville+77° Vienna+77° Alexandria+79° Waldorf+77° Salisbury+75° Rockville+77° Woodmere+77° Windsor+75° world's temperature today Temperature units\"}, {'url': 'https://weatherspark.com/h/m/1828/2023/10/Historical-Weather-in-October-2023-in-Anaheim-California-United-States', 'content': 'October 2023 Weather History in Anaheim California, United States This report shows the past weather for Anaheim, providing a weather history for October 2023. It features all historical weather data series we have available, including the Anaheim temperature history for October 2023. Anaheim Temperature History October 2023 Hourly Temperature in October 2023 in Anaheim Cloud Cover in October 2023 in Anaheim Daily Precipitation in October 2023 in Anaheim Observed Weather in October 2023 in Anaheim Hours of Daylight and Twilight in October 2023 in Anaheim Solar Elevation and Azimuth in October 2023 in Anaheim Oct 2023 Humidity Comfort Levels in October 2023 in Anaheim Wind Speed in October 2023 in Anaheim Hourly Wind Speed in October 2023 in Anaheim'}]\u001b[0m" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "2024-09-20 00:38:14,560 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:14,563 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: The\n", - "2024-09-20 00:38:14,599 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: current\n", - "2024-09-20 00:38:14,600 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: weather\n", - "2024-09-20 00:38:14,628 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: in\n", - "2024-09-20 00:38:14,630 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: California\n", - "2024-09-20 00:38:14,666 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: is\n", - "2024-09-20 00:38:14,669 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: as\n", - "2024-09-20 00:38:14,744 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: follows\n", - "2024-09-20 00:38:14,747 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "\n", - "\n", - "2024-09-20 00:38:14,779 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:14,782 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:14,785 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Temperature\n", - "2024-09-20 00:38:14,787 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:14,791 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:14,795 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:14,890 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 22\n", - "2024-09-20 00:38:14,891 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .\n", - "2024-09-20 00:38:14,914 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 3\n", - "2024-09-20 00:38:14,915 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: °C\n", - "2024-09-20 00:38:14,926 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: (\n", - "2024-09-20 00:38:14,929 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 72\n", - "2024-09-20 00:38:14,932 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .\n", - "2024-09-20 00:38:14,935 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 1\n", - "2024-09-20 00:38:14,948 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: °F\n", - "2024-09-20 00:38:14,950 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: )\n", - "\n", - "2024-09-20 00:38:14,976 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:14,978 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,019 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Condition\n", - "2024-09-20 00:38:15,021 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,065 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:15,068 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Sunny\n", - "2024-09-20 00:38:15,116 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "\n", - "2024-09-20 00:38:15,117 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:15,134 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,136 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Wind\n", - "2024-09-20 00:38:15,158 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,160 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:15,191 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:15,194 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 3\n", - "2024-09-20 00:38:15,238 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .\n", - "2024-09-20 00:38:15,242 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 4\n", - "2024-09-20 00:38:15,260 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: mph\n", - "2024-09-20 00:38:15,262 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: (\n", - "2024-09-20 00:38:15,321 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 5\n", - "2024-09-20 00:38:15,325 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .\n", - "2024-09-20 00:38:15,346 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 4\n", - "2024-09-20 00:38:15,348 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: k\n", - "2024-09-20 00:38:15,368 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ph\n", - "2024-09-20 00:38:15,370 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: )\n", - "2024-09-20 00:38:15,417 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: from\n", - "2024-09-20 00:38:15,420 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: the\n", - "2024-09-20 00:38:15,443 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: SSE\n", - "2024-09-20 00:38:15,445 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "\n", - "2024-09-20 00:38:15,477 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:15,480 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,528 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Humidity\n", - "2024-09-20 00:38:15,530 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,547 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:15,549 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:15,585 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 35\n", - "2024-09-20 00:38:15,587 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: %\n", - "\n", - "2024-09-20 00:38:15,614 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:15,615 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,656 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Pressure\n", - "2024-09-20 00:38:15,660 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,682 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:15,684 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:15,713 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 101\n", - "2024-09-20 00:38:15,715 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 3\n", - "2024-09-20 00:38:15,742 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: mb\n", - "2024-09-20 00:38:15,744 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", + "\u001b[0m\u001b[36;1m\u001b[1;3m[{'url': 'https://www.weatherapi.com/', 'content': \"{'location': {'name': 'New Delhi', 'region': 'Delhi', 'country': 'India', 'lat': 28.6, 'lon': 77.2, 'tz_id': 'Asia/Kolkata', 'localtime_epoch': 1726991371, 'localtime': '2024-09-22 13:19'}, 'current': {'last_updated_epoch': 1726991100, 'last_updated': '2024-09-22 13:15', 'temp_c': 35.3, 'temp_f': 95.5, 'is_day': 1, 'condition': {'text': 'Mist', 'icon': '//cdn.weatherapi.com/weather/64x64/day/143.png', 'code': 1030}, 'wind_mph': 3.4, 'wind_kph': 5.4, 'wind_degree': 350, 'wind_dir': 'N', 'pressure_mb': 1007.0, 'pressure_in': 29.74, 'precip_mm': 0.0, 'precip_in': 0.0, 'humidity': 56, 'cloud': 50, 'feelslike_c': 38.1, 'feelslike_f': 100.6, 'windchill_c': 36.6, 'windchill_f': 98.0, 'heatindex_c': 40.7, 'heatindex_f': 105.3, 'dewpoint_c': 20.3, 'dewpoint_f': 68.5, 'vis_km': 3.5, 'vis_miles': 2.0, 'uv': 9.0, 'gust_mph': 3.9, 'gust_kph': 6.2}}\"}, {'url': 'https://mausam.imd.gov.in/delhiums/', 'content': 'Urban Meteorological Services for Delhi - NCR India Meteorological Department Ministry of Earth Sciences Government of India. ... Sep 22, 2024. Sep 23, 2024. ... Sep 25, 2024. Interactive Display of weather and Thunderstorm warnings. Current Weather Across Delhi - NCR. Delhi; Gurugram; Faridabad; Gautam Budh Nagar; Ghaziabad; New Delhi. 33.0 ...'}, {'url': 'https://www.msn.com/en-in/news/India/delhi-weather-and-aqi-today-warm-start-at-2705-c-check-weather-forecast-for-september-22-2024/ar-AA1qYihM', 'content': \"The temperature in Delhi today, on September 22, 2024, is 35.08 °C. The day's forecast indicates a minimum and maximum temperature of 27.05 °C and 37.13 °C, respectively. The relative humidity ...\"}, {'url': 'https://weatherspark.com/h/m/109174/2024/9/Historical-Weather-in-September-2024-in-New-Delhi-NCT-India', 'content': '°F September 2024 Weather History in New Delhi NCT, India Latest Report — 3:00 PM °F Dew Pt. 73°F\\xa0\\xa0oppressive No Report Overcast 8,000 ft Mostly Clear 1,500 ft Mostly Clear 3,000 ft This report shows the past weather for New Delhi, providing a weather history for September 2024. It features all historical weather data series we have available, including the New Delhi temperature history for September 2024. New Delhi Temperature History September 2024 Sep 65°F 65°F 70°F 70°F 75°F 75°F 80°F 80°F 85°F 85°F 90°F 90°F 95°F 95°F 100°F 100°F Hourly Temperature in September 2024 in New Delhi Sep'}, {'url': 'https://www.timeanddate.com/weather/india/new-delhi/hourly', 'content': 'Hour-by-Hour Forecast for New Delhi, Delhi, India. Weather Today Weather Hourly 14 Day Forecast Yesterday/Past Weather Climate (Averages) Currently: 84 °F. Passing clouds. (Weather station: New Delhi / Safdarjung, India). See more current weather.'}]\u001b[0m\u001b[32;1m\u001b[1;3mThe current weather in New Delhi, India, is as follows:\n", "\n", - "2024-09-20 00:38:15,787 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:15,792 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,826 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Visibility\n", - "2024-09-20 00:38:15,828 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: **\n", - "2024-09-20 00:38:15,865 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", - "2024-09-20 00:38:15,868 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:15,902 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 16\n", - "2024-09-20 00:38:15,904 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: km\n", - "2024-09-20 00:38:15,927 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: (\n", - "2024-09-20 00:38:15,928 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 9\n", - "2024-09-20 00:38:15,969 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: miles\n", - "2024-09-20 00:38:15,971 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: )\n", + "- **Temperature**: 35.3°C (95.5°F)\n", + "- **Condition**: Mist\n", + "- **Feels Like**: 38.1°C (100.6°F)\n", + "- **Humidity**: 56%\n", + "- **Wind**: 3.4 mph (5.4 kph) from the North\n", + "- **Visibility**: 3.5 km\n", + "- **Pressure**: 1007 mb\n", "\n", + "The forecast for today indicates a minimum temperature of 27.05°C and a maximum of 37.13°C.\n", "\n", - "2024-09-20 00:38:15,991 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: For\n", - "2024-09-20 00:38:15,993 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: more\n", - "2024-09-20 00:38:16,021 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: detailed\n", - "2024-09-20 00:38:16,025 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: weather\n", - "2024-09-20 00:38:16,075 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: information\n", - "2024-09-20 00:38:16,078 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ,\n", - "2024-09-20 00:38:16,108 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: you\n", - "2024-09-20 00:38:16,110 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: can\n", - "2024-09-20 00:38:16,154 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: check\n", - "2024-09-20 00:38:16,158 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: the\n", - "2024-09-20 00:38:16,212 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: following\n", - "2024-09-20 00:38:16,214 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: sources\n", - "2024-09-20 00:38:16,243 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: :\n", + "For more details, you can check the [India Meteorological Department](https://mausam.imd.gov.in/delhiums/) or other weather services.\u001b[0m\n", "\n", - "2024-09-20 00:38:16,245 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:16,275 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: [\n", - "2024-09-20 00:38:16,277 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Weather\n", - "2024-09-20 00:38:16,279 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: API\n", - "2024-09-20 00:38:16,315 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ](\n", - "2024-09-20 00:38:16,316 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: https\n", - "2024-09-20 00:38:16,343 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ://\n", - "2024-09-20 00:38:16,345 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: www\n", - "2024-09-20 00:38:16,368 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .weather\n", - "2024-09-20 00:38:16,369 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: api\n", - "2024-09-20 00:38:16,425 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .com\n", - "2024-09-20 00:38:16,427 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /)\n", - "\n", - "2024-09-20 00:38:16,435 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:16,438 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: [\n", - "2024-09-20 00:38:16,493 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Time\n", - "2024-09-20 00:38:16,496 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: and\n", - "2024-09-20 00:38:16,529 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Date\n", - "2024-09-20 00:38:16,531 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ](\n", - "2024-09-20 00:38:16,620 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: https\n", - "2024-09-20 00:38:16,624 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ://\n", - "2024-09-20 00:38:16,631 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: www\n", - "2024-09-20 00:38:16,633 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .time\n", - "2024-09-20 00:38:16,678 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: and\n", - "2024-09-20 00:38:16,680 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: date\n", - "2024-09-20 00:38:16,699 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .com\n", - "2024-09-20 00:38:16,702 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /weather\n", - "2024-09-20 00:38:16,723 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:16,725 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: usa\n", - "2024-09-20 00:38:16,778 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:16,780 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: los\n", - "2024-09-20 00:38:16,834 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -ang\n", - "2024-09-20 00:38:16,836 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: eles\n", - "2024-09-20 00:38:16,872 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /h\n", - "2024-09-20 00:38:16,874 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: istor\n", - "2024-09-20 00:38:16,877 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ic\n", - "2024-09-20 00:38:16,878 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ?\n", - "2024-09-20 00:38:16,920 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: month\n", - "2024-09-20 00:38:16,923 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: =\n", - "2024-09-20 00:38:16,937 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 10\n", - "2024-09-20 00:38:16,939 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: &\n", - "2024-09-20 00:38:16,972 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: year\n", - "2024-09-20 00:38:16,975 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: =\n", - "2024-09-20 00:38:17,004 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 202\n", - "2024-09-20 00:38:17,007 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 3\n", - "2024-09-20 00:38:17,053 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: )\n", - "\n", - "2024-09-20 00:38:17,057 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:17,087 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: [\n", - "2024-09-20 00:38:17,092 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: M\n", - "2024-09-20 00:38:17,109 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ete\n", - "2024-09-20 00:38:17,111 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: op\n", - "2024-09-20 00:38:17,184 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: rog\n", - "2024-09-20 00:38:17,187 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ](\n", - "2024-09-20 00:38:17,224 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: https\n", - "2024-09-20 00:38:17,227 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ://\n", - "2024-09-20 00:38:17,263 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: www\n", - "2024-09-20 00:38:17,265 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .m\n", - "2024-09-20 00:38:17,348 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ete\n", - "2024-09-20 00:38:17,350 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: op\n", - "2024-09-20 00:38:17,367 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: rog\n", - "2024-09-20 00:38:17,371 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .com\n", - "2024-09-20 00:38:17,397 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /weather\n", - "2024-09-20 00:38:17,400 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:17,435 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: California\n", - "2024-09-20 00:38:17,440 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -cal\n", - "2024-09-20 00:38:17,465 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ifornia\n", - "2024-09-20 00:38:17,467 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /month\n", - "2024-09-20 00:38:17,507 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /oct\n", - "2024-09-20 00:38:17,510 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ober\n", - "2024-09-20 00:38:17,542 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /)\n", - "\n", - "2024-09-20 00:38:17,544 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:17,568 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: [\n", - "2024-09-20 00:38:17,569 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: World\n", - "2024-09-20 00:38:17,608 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: Weather\n", - "2024-09-20 00:38:17,611 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ](\n", - "2024-09-20 00:38:17,644 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: https\n", - "2024-09-20 00:38:17,646 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ://\n", - "2024-09-20 00:38:17,678 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: world\n", - "2024-09-20 00:38:17,680 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -weather\n", - "2024-09-20 00:38:17,713 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: .info\n", - "2024-09-20 00:38:17,715 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:17,746 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: forecast\n", - "2024-09-20 00:38:17,748 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:17,773 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: usa\n", - "2024-09-20 00:38:17,777 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /cal\n", - "2024-09-20 00:38:17,831 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ifornia\n", - "2024-09-20 00:38:17,833 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /oct\n", - "2024-09-20 00:38:17,848 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ober\n", - "2024-09-20 00:38:17,850 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: -\n", - "2024-09-20 00:38:17,880 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 202\n", - "2024-09-20 00:38:17,883 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: 3\n", - "2024-09-20 00:38:17,912 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: /\n", - "2024-09-20 00:38:17,914 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: )\n", - "\n", - "\n", - "2024-09-20 00:38:17,950 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: If\n", - "2024-09-20 00:38:17,952 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: you\n", - "2024-09-20 00:38:17,986 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: need\n", - "2024-09-20 00:38:17,990 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: specific\n", - "2024-09-20 00:38:18,015 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: weather\n", - "2024-09-20 00:38:18,016 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: details\n", - "2024-09-20 00:38:18,080 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: for\n", - "2024-09-20 00:38:18,083 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: a\n", - "2024-09-20 00:38:18,087 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: particular\n", - "2024-09-20 00:38:18,090 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: city\n", - "2024-09-20 00:38:18,121 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: in\n", - "2024-09-20 00:38:18,124 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: California\n", - "2024-09-20 00:38:18,156 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: ,\n", - "2024-09-20 00:38:18,160 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: let\n", - "2024-09-20 00:38:18,193 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: me\n", - "2024-09-20 00:38:18,195 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: know\n", - "2024-09-20 00:38:18,197 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: !\n", - "2024-09-20 00:38:18,198 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - DEBUG - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onNewToken: \n", - "2024-09-20 00:38:18,200 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMEnd: [[ChatGenerationChunk(text='The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!', generation_info={'finish_reason': 'stop', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, message=AIMessageChunk(content='The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!', response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'}, id='run-f2f049b4-55c5-4805-964e-91d2f1731b11'))]]\n", - "2024-09-20 00:38:18,202 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: content='The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!' response_metadata={'finish_reason': 'stop', 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_1bb46167f9'} id='run-f2f049b4-55c5-4805-964e-91d2f1731b11'\n", - "2024-09-20 00:38:18,203 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: return_values={'output': 'The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'} log='The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'\n", - "2024-09-20 00:38:18,204 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: return_values={'output': 'The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'} log='The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'\n", - "2024-09-20 00:38:18,206 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onAgentFinish: {'output': 'The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'}\n", - "2024-09-20 00:38:18,207 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: {'output': 'The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'}\n" + "\u001b[1m> Finished chain.\u001b[0m\n", + "{'messages': [HumanMessage(content='Whats the whether in New Delhi, India ?')], 'output': 'The current weather in New Delhi, India, is as follows:\\n\\n- **Temperature**: 35.3°C (95.5°F)\\n- **Condition**: Mist\\n- **Feels Like**: 38.1°C (100.6°F)\\n- **Humidity**: 56%\\n- **Wind**: 3.4 mph (5.4 kph) from the North\\n- **Visibility**: 3.5 km\\n- **Pressure**: 1007 mb\\n\\nThe forecast for today indicates a minimum temperature of 27.05°C and a maximum of 37.13°C.\\n\\nFor more details, you can check the [India Meteorological Department](https://mausam.imd.gov.in/delhiums/) or other weather services.'}\n" ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[32;1m\u001b[1;3mThe current weather in California is as follows:\n", - "\n", - "- **Temperature**: 22.3°C (72.1°F)\n", - "- **Condition**: Sunny\n", - "- **Wind**: 3.4 mph (5.4 kph) from the SSE\n", - "- **Humidity**: 35%\n", - "- **Pressure**: 1013 mb\n", - "- **Visibility**: 16 km (9 miles)\n", - "\n", - "For more detailed weather information, you can check the following sources:\n", - "- [Weather API](https://www.weatherapi.com/)\n", - "- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\n", - "- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\n", - "- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\n", - "\n", - "If you need specific weather details for a particular city in California, let me know!\u001b[0m\n", - "\n", - "\u001b[1m> Finished chain.\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "{'messages': [HumanMessage(content='Whats the whether in california')],\n", - " 'output': 'The current weather in California is as follows:\\n\\n- **Temperature**: 22.3°C (72.1°F)\\n- **Condition**: Sunny\\n- **Wind**: 3.4 mph (5.4 kph) from the SSE\\n- **Humidity**: 35%\\n- **Pressure**: 1013 mb\\n- **Visibility**: 16 km (9 miles)\\n\\nFor more detailed weather information, you can check the following sources:\\n- [Weather API](https://www.weatherapi.com/)\\n- [Time and Date](https://www.timeanddate.com/weather/usa/los-angeles/historic?month=10&year=2023)\\n- [Meteoprog](https://www.meteoprog.com/weather/California-california/month/october/)\\n- [World Weather](https://world-weather.info/forecast/usa/california/october-2023/)\\n\\nIf you need specific weather details for a particular city in California, let me know!'}" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "# with logging\n", - "\n", - "from flo_ai.common.flo_langchain_logger import FloLangchainLogger\n", - "\n", - "flo = Flo.build(session, simple_weather_checking_agent)\n", - "\n", - "handler = FloLangchainLogger(log_level=\"INFO\")\n", - "\n", - "config = {\n", - " 'callbacks' : [handler]\n", - "}\n", - "\n", - "query=\"Whats the whether in california\"\n", - "\n", - "flo.invoke(query=query, config=config)" + "result = flo.invoke(\"Whats the whether in New Delhi, India ?\")\n", + "print(result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## LLM Agent (agentic)\n", + "## LLM Agent (llm)\n", "\n", "Here we are gonna create a simple llm math assitant flo agent that can check answer any math question\n", "\n", @@ -601,7 +211,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -619,68 +229,68 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-09-20 00:45:06,709 - BUILDER - INFO - Building Flo instance from YAML\n", - "2024-09-20 00:45:06,728 - COMMON - INFO - Flo instance created for session dd5c4a4c-4390-46bc-945d-eb9474e63702\n" + "2024-09-22 13:22:46,658 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-22 13:22:46,661 - COMMON - INFO - Flo instance created for session e7f22b4d-7380-4d00-97c9-5267a468468a\n" ] }, + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAGwAMwDASIAAhEBAxEB/8QAHQABAAMAAwEBAQAAAAAAAAAAAAUGBwIECAMBCf/EAFQQAAEDAwEDBQwDCwkHBAMAAAECAwQABQYRBxIhExUxlNMIFBYXIkFUVVZhk9EjMlE0Njdxc3WVsbPS1DNCUlNygZGhwSUmQ3SCg7QYJEZjYqPw/8QAGgEBAAMBAQEAAAAAAAAAAAAAAAECBAMFBv/EADcRAAIAAwQFCgYCAwEAAAAAAAABAgMREiFRkQQUMWHRExVBUmJxgZKh4QUiIzNTsTLBQrLw8f/aAAwDAQACEQMRAD8A/qnSlKAUpSgFKUoBXWmXOHbwDKlsRgeI5ZwI/WagOVl5mV96ynrdYkkoEhg7siYQdCW1fzGuBAUPKX0pKUhKl9qJgeOwyVN2WEpwkqU88ylxxRPSStWqifxmtFiCC6Y78F/f/Mmi6TteFVl9cQOso+dPCqy+uIHWUfOngrZfU8DqyPlTwVsvqeB1ZHyp9Hf6E3DwqsvriB1lHzp4VWX1xA6yj508FbL6ngdWR8qeCtl9TwOrI+VPo7/QXDwqsvriB1lHzp4VWX1xA6yj508FbL6ngdWR8qeCtl9TwOrI+VPo7/QXDwqsvriB1lHzp4VWX1xA6yj508FbL6ngdWR8qeCtl9TwOrI+VPo7/QXDwqsvriB1lHzrk3ktoeWEt3WEtR6EpkIJP+dcfBWy+p4HVkfKuDmI2J5BQ5ZbctB6UqiNkH/Kn0d/oRcSwIUAQdQeIIr9qsqwlm1kv486bI+CVcg0NYjpPmWz0Ae9G6r39IMlYb1zuy6h9gwrhGVycqIpW9yavMUq0G8hQ4pVoNR0hJBSKxQKlqB1XqKYEpSlK4kClKUApSlAKUpQClKUAqt57IcFjbgsrLTtzkswAtJIKUOKAcII4ghsOaEefTo6aslVjPRyES0TzrycG6R3nNBrohSi0T+IB3U+4Gu8j7sP/dxK2ljjx2ojDbDLaWmWkhCG0DRKUgaAAfYBWHZXtD2oXHbPkeI4QjEkwrJaIdyWL+xJ5WQt9Tw3A604EoA5HpKD0+et1rzfkGwqHtQ7pXNZWSw783jysftjDTsKbKgxZqt+TyrS1tKSHdAU6pJOm95ta4bSCwYB3XOE5Xi2OTLk/ItV/u1tbuJscaHImvIQp1xkqRyLSt9AcZc8oDgN0qCQoa2yd3Qez625irF5GRtN3dMpEBYEd5UduSrTdYXICORQ4dQNxSwrUgaa1WMOwBvFO6UuTlssKrdjkTCLda4L7UZSYyCiXJKmEL003gnkyU668Uk9IrHr7jWTMbHcz2MNYZfZWT3m/wApyLfUQlKtrrL8/vlE1yX9RJbQRqknf3mwAD00Bvt/7pfZti90uEC6ZIIj9uliDOWqFJUzDeO7uh90NlDQO8NFLUEnjoTodO1b+6EwC52nIrkzfVIjY/E5wuIkQZLLrUbRRDyW1thbjZCVaKQlQOnAmsRzjCMhl7Ge6Rgs2G5yJt2ydb9vjohuKcmtchAAW0kJ1cTqhY1TqNUq+w12O6jslwF/2nXcQJItR2UTIhn8irkC8JK1BrlNN3f3SVbuuuh1oDaca2+4Ll8+RBtd5demsw1XARnrfJYdfjpGpdYS42kvp4ji1vdI+0Vl6+65OQdzZlm0PH7UqLfrLGcf7wukGUIo0kLab+lUhoPapRqoNq1STodDwr8x+4XLa3tW2TXC34pkFktWHQ5ci4XS+W9UNDqnogYRHY3uLuqlb6lJ1SA2niSRVGmWvIf/AEc55sxVh+SJym1x5yAhNqdWxN3563EGM6kEPbyFhWidSNDr0UB6Jy7ugMEwW8zrRer2uPc4DDcqVFYgSZC2WVgkOqDTatGxunVf1U8N4jUa/uS7fsExOfAhTry67Kn24XaK1brfJml6IToHk8g2vVP+nHo41U7djdy8dG2aY7a5XeFwx20x4shcdXJSVpRNDiG1aaLKd9GoGpG8nXpFY5sxyeVsmzXZjzzjWSTZrGymNEft1stTsmZHcTJb8lxkDfToU7vEcDprpQHqK07W8Rvq8WTbr2xO8J233bQuOha0Sksp3nfKCdEFI6Qsg6gjTUEVTNqPdM45s+tXfUREi8ux8mjY1OaYiSVd6vLLSnT5DSt8padSpIT9ckJSSrhWRYjh+TbP5GznOLti13RCGQ5Fc5tmtsRUyXamLjvKjoLLQUohOid4IB3Svj0Gvm9YMovGC7QL+3h1+Yca2owsmZtL8MomyoLHeClLab/nkpbWQASdUlP1gRQHrSxXqLkdniXOFy/ekpsOtd8x3I7m6ejebcSlaD7lAH3VD37S05bYbi3onv1arZJ6dVpKFuNE/wBlSVAflVfbUli2Qt5XYYt1ahXC3NyAopjXSIuLJRoop8tpYCk66ajUdBB89RuXJ78vGLwU6largZStBro200skn7PKU2P+oVokfzp0Uf6ZKLPSlKzkClKUApSlAKUpQClKUArrz4Me6QZMKW0l+LIbUy60v6q0KBCkn3EEiuxSpTadUCt2q7rsbrNnvL2jv1Ic9wnclp10SlSjwD2mmqf531k8N5KLJXwnQY1ziOxZkdqXFdTuuMPoC0LH2FJ4EVXzgMZjhAul3tjfEhqPOUtA/ElzfCR7hoPdXf6cy9uj9PYm5lnpVX8CZHtVfvjM9lTwJke1V++Mz2VOTl9f0YosS0VFZTjFszXG7nYLzG78tNyjriS4/KKb5RpYKVJ3kkKGoJ4gg1GeBMj2qv3xmeyp4EyPaq/fGZ7KnJy+v6MUWJY4kVqDFZjMJ3GWUJbQnUnRIGgGp91fWsrz633XG5OKoh5TeSm53pmBI5VxknklNuqO79GPK1Qn7fPwq2eBMj2qv3xmeypycvr+jFFiWioU4dZzmScrMT/b4gG1iXyq/uYuBwo3Nd364B10182unCuj4EyPaq/fGZ7KngTI9qr98ZnsqcnL6/oxRYlopVX8CZHtVfvjM9lTwHeUNF5Pflp8474bT/mlsH/OnJy+v6MUWJNXe9wrFGD018NBR3W0AFTjqvMhCBqpaj5kpBJqPsVtkv3B+93JrkJr7YZYilQUYrAOoQSCQXFHyllPDUJSCoIClfW0YjbLLJMpllb00ggzJby5D+h6QFrJIB4eSNBwHDgKmahxQwpwwdPSO4UpSuBApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQGe7XSBN2f6kj/eiNp8F/31oVZ7tc179wDo++eN06f1L/2/6VoVAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAZ5tdGs3Z95QH+9EbpHT9C/Wh1nm17Tv3Z9r7URtOGv/AAX60OgFKUoBSlKAUpSgFKUoBSlKAUpSgFKVAZBkrtulN2+3xUT7m42XeTddLTTTfEBbiwlRGpGgABJIPmBIvBBFMdmEE/SqSb5l+p0gWQjzay3uzr859zD0Cx9be7OtOqx4rNE0LvSqRz7mHoFj6292dOfcw9AsfW3uzpqseKzQoXelUjn3MPQLH1t7s6c+5h6BY+tvdnTVY8VmhQu9KpHPuYegWPrb3Z059zD0Cx9be7Omqx4rNChd6VSOfcw9AsfW3uzpz7mHoFj6292dNVjxWaFDy/3YHdjyNje1Kz4xOwN6XHtUyLfIly5yDaZzXJLSoBBZVuaLUtOup/k/fpXqzZVmU/aFs7sOS3KyKx2VdYwlc2LkcuplCiS3qvdTqSjdVpoNN7Tza1iu3rYDI7oK4YlMv8GzsvWCcJALMl3WUwdCuOs8nqEqKUnXzaHT6xrXUXrL20JQi3WJKUjQJEp4AD4dNVjxWaFC8Uqkc+5h6BY+tvdnTn3MPQLH1t7s6arHis0KF3pVI59zD0Cx9be7OnPuYegWPrb3Z01WPFZoULvSqRz7mHoFj6292dOfcw9AsfW3uzpqseKzQoXelUjn3MPQLH1t7s6c+5h6BY+tvdnTVY8VmhQu9KpHPuYegWPrb3Z1y8Lb9am1Srta4TkBsFTy7dIccdbQOlQbU2N/QakgHe0HAKOgpqszopmhQutK4NOofaQ60tLja0hSVoOoUD0EHziudYyBVGcOu0m+e62QOP8A3JVXmqKv8JV9/NkD9pKrbo3+fd/aLLYyXpSq1nm0jGtmVpbuWTXZq1xXXQwzvJU4484ehDbaAVrV7kgmuuwqWWlUaz7bMMvtqtVxh3da4l0ugssRTkKQ0pcwoUsNFC2wpB3Uk6qAHv4irzStQKV0rzeoGOWqXc7pMYt9uiNl1+VJcDbbSB0qUo8AKr20Laxi+yyJbpOS3B2E1cX+94gYhPyluubpXuhDKFq+qknUjThStAW6lVzBdouN7TLQ5c8ZuzF2iNulh0thSVsuDpQ4hQCkK4jgoA8RVjoBSlKkClKh5WX2eIm9709p1yyNB64sx9XnYyS2XBvNoBVqUDeCQNSCNAdRUAmKVjSe6/2Vrlripvd0VKQgOLYGOXPfSkkgKKe9tQCQePuNbG24l1tK08UqAI1GnCiaewHKlKVIFKh8vy604Hjc+/32X3jaYKA5IkcmtzcTqBruoBUeJHQDUxUAUroyL5b4t3iWt6aw3cpbbjseIpwB11CN3fUlPSQneTqfNvD7a71SBXylAKivAjUFB1B/FX1r5yfuZ3+wf1VK2g+uzhRXs8xdSjqTaopJ/wCymrFVb2a/g6xX81Rf2KaslYp/3Y+9/sl7WKoq/wAJV9/NkD9pKq9VRV/hKvv5sgftJVdtG/z7v7RK2Ml6w3I1Rz3Y+IpvBT3sMTmKsge+r393wjl+T/8As5Dd/wCnWtyqsZ/syxjajbGIGT2hq6R47ofYUpa23WHB/ObcQUrQfekiujVSpm3dPQBfxsxtzNxkW9bmbw2VyYDgS+zrElE7qiDuq3SNDpqNQR5jVMmZRJwVrahhUy+5VeY0W7WiFYlx7l/tVT01tChGTLc4pSVhXlqOqUKVodQK2ey7C8Gx2322Fb7CiOzb7om9MK74eU4ZiUKbDzjillTqglah5ZUNNOHAadq+7H8QyVWQqudmTKXf1xnLgpT7qVOLjgBhaSFAtqQANFN7p14661Wj2g8qZecmVsd7oLD8mmXPk7DAgz4bL99duL7IeaWpTa5RQ2t1BLYO4sEDeI1I41pG2Kyz8IuOxONiqH8iuLeQPuRm7/eHnS6VQXyQqS5yiwACdOBHADhWs2PYXg2ON3lEGwoSm8w+8bmH5Dz/AH815XB7lFq5RXlrG+rVWh0104VysWxHDcbYsjUG1vJRZZi7hb+XuEl8sPKZLJUC44okcmSkJOqR5gDxqLLBgN32e5radpuO3G95Acbu+f5P/tKHikt1DLcSPbHkoZ5QhJWtQQSpe6NFEFOhSCLk1jEzM9q2V4bIzHJ7HY8StEA28Qby63IkOSA8tcp99RK3t0thACyU8DqONbdecRtOQ3WyXK4RO+JtlkLlQHeUWnkXVNqaUrQEBWqFqGigRx16dDUDnmxbDdpc9mdkVn78mNMGKJDMp6M4tknUtLU0tJcb11O4rVPE8OJqbIMB2aZHkG2697NY18yW9wI9wwubNmiyznIPfj7M1lhD5LZBSVJUV6oKenT6pKT1sAuWRWzCdk2ZPZjkV0ut1yw2Gc1PuCnYsiJy8mOEljgjfAZQrlNN8q1JJ14enIOz7HrXfLbd4draiTrbbVWiGphSkNsRFKQotJbB3NNWkaHTUbugOmtdGLskxOFYbJZWbVuW2y3HnaAx3y6eRlcotzlN4r1V5bqzuqJT5WmmgGkWWDzTcJ19suxTM9pjGZ5IMjsmS3FMONIurrsJ1tu5KaREVHUShSVJ8gcN4EjdIAArRdk2GREd0ptgunf9474izoDiYyrrILC+WgJUrlGt/cWElRCAoEIAAToEjTv7OO5cx2xypN2ye2Rrtfuf514juImSFxkcpJW6yosKKWy6lKkje3CQRwJ0BrRJeynF5uds5ku2qRkbaENmYxKeaDqUghHKNoWEOboUQCtJ014UULBjmcZTNwfbHthyG3MCRPtWz6LNjtqGqS425MUnUecagE+7Wvhshxjak5dsNyY3QyLPObTJuzs3LHbk1PYdZKgpmMYjaGFBZQpPJqCQAUkEHWt7RhVlRlFwyIQUqu9wht2+U+talB1htS1IQUE7ugLi+IGp14k8KruGbCMG2e3sXXH7HzdMSlxDQEt9xphKzqtLTS1lDQP2ISmpsuoPN2HRb/ctnuwy+P57l6rhldzFruqueXSh6OWJK91KDwQscggcokBziolRVoRLXe7X+IzNw9GV38Q4G063WZieLi5393k/HbdUwp/XfWAXFAFRJ001J0FeirfskxO1WbF7TFtXJW/GJIl2lnvl097OhDiArUr1X5Lrg0WVDyujgNKftW7n+3ZxHjNW2LHjiZlEK/XzviS8kykMo5Ne4U6lK9xKAAncHDXUHjUWWkDF9t78/E7FtnwZq+XO/wCPsYzAvDRu8xcx+3vuSltrZ5ZZKylSW0rAWTpx04Gr7t/m3PJsvnWTFZOSpvNlsnOMpy3ZCbTAhpWXOSWsJbWX3SW1+QRubqeJGtajYdiGD41jl7sUKwMm23sEXNEp1yS5MBTu/SuuqUtWgJA1Vw82ldSf3PeAXPmvvqwcuLbDTbmQqZI0cjJOqWXvpPp0Ak+S7vjiftNTZYMRtMFW1baPsFyC93O7MXK74TImSV225vw0qeSIayUpaWkJCi4oqA4KASFahI05XJe0va7nG0ZVimPwjj92XaLchjK3bWiHuMtqQ85FRFdTIC1LK9XFEEeSAnd1O33HYFgl1xyw2ORZFc3WHfFsDU6Q07ESvgpCHkuBwII4bu9u6ADTQABlewHAs2vq7zeLAmRcnWkMvvNSn2BKQnglL6W1pS8AOH0gVw4dFRZYLfjKbqjG7Um+KYXexEaE5UXXkjI3ByhRqAd3e3tOA4aV3pP3M7/YP6q+gAAAHQK+cn7md/sH9VdltBz2a/g6xX81Rf2KaslVvZr+DrFfzVF/YpqyVin/AHo+9/sl7WKoq/wlX382QP2kqr1VVyOyz2Lvz1a2UzXVsJjSYSnA2XEpKlNqbUfJCgVrBCtNQocRugK6aPEk4k3tVPVP+gj70qFVdb8FEDDrooDziTD4/wD76/Odr97GXXrULt62WO0vMuJNCbpUJztfvYy69ahdvTna/exl161C7eljtLzLiKE3SoTna/exl161C7enO1+9jLr1qF29LHaXmXEUJulQnO1+9jLr1qF29Odr97GXXrULt6WO0vMuIoTdKhOdr97GXXrULt6c7X72MuvWoXb0sdpeZcRQm6VU7xm8+wOW5E/FLqwq4Skwow5eIrlHlJUoJ4PHTghXE6Dh01I87X72MuvWoXb0sdpeZcRQm6VCc7X72MuvWoXb052v3sZdetQu3pY7S8y4ihN0qE52v3sZdetQu3pztfvYy69ahdvSx2l5lxFCbpUJztfvYy69ahdvTna/exl161C7eljtLzLiKE3SoTna/exl161C7enO1+9jLr1qF29LHaXmXEUJuvnJ+5nf7B/VURztfvYy69ahdvX6sZFe2VRG7G9ZeWBQqZOfZWGknpUlLTiipWmugOg16TSxS9xLNcRQmdmv4OsV/NUX9imrJXWtsBm1W6LCjghiM0hlsKOp3UgAan8Qrs15k2JRxxRLpbId7FKUrmQKUpQClKUApSlAKUpQClKUBn21sazMB4a6ZPG82v8AwX/cf9K0Gs92uJ3puz/gTplEY8Brp9C/WhUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUBnm14gTdn2p/+URtOGv8AwX60Os92thRm4BulX3zxtd0ebkX+n3VoVAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSuDrqGGluurS22hJUpazoEgdJJ8woDnSqwrahh6FEHKLPr/wA63864+NLDvam0ddb+daNXndR5MtZeBaaVVvGlh3tTaOut/OnjSw72ptHXW/nTV53UeTFl4FppVW8aWHe1No66386eNLDvam0ddb+dNXndR5MWXgUPbbtIxC0X/DbdcMpssK4QcjjPSokm4Mtux0Fh0hbiCsFI0Wk6kaeUPtFavZL7bcltjNytFwi3W3P73JS4T6XmXNFFJ3VpJB0IIOh6QRX89u722K2nartIxPKMOvFrky7s63arwpqUgpZ00DclzQ8EhAKVK6BuI+2vZuz29bO9mmD2TFrPklnat1qiojNf+8aBVoOKzofrKOqj7yaavO6jyYsvA0qlVbxpYd7U2jrrfzp40sO9qbR11v501ed1HkxZeBaaVVvGlh3tTaOut/OnjSw72ptHXW/nTV53UeTFl4FppVW8aWHe1No66386lLLlVlyQuC1XaFclN8VpiSEOFH2agE6f31WKTNgVYoWl3EUZK0pSuJApSlAKUpQClKUApSlAKpucKE2+Y/anvLhPl+Q6yfquFoI3Ar7QCve0PDVI+yrlVLy7798Y/ITf1M1r0X7vg/0yVtJAAJAAGgHQBX7SlaCBSlKAUpSgFKUoBSlKAUpSgFQGYkQIDN2aAROhPsqaeTwUEqdSlaNfOlSSQQeHQdNQKn6r2f8A3qyvyjP7ZFdpN8yFby0O1GiUpSvGKilKUApSlAKUpQClKUAql5d9++MfkJv6maulUvLvv3xj8hN/UzWvRfu+EX+rJRI1km3vJr23NwjCsduTlkuOX3RcR26sAF6LEaZW8+prUEBwpQEpJB03iekCtbrO9s2zKftBgWOdYbm1Zsrx24JudqmSGy4yVhKkLZdSCCW3EKKVaHUcCOjQ9nsuIKHtew6Xsd7n7aTcLPmWWzZqrZyjEm63l2Q7EWgny2XDopBVvcdDp5I004623FNr17n5m5iuQYa5j90ftLl3tgFybkiY02tKFtrISA06C43qnVSfKJCjpUFmOBbVNp+y7N8dySViMKTdrd3nb49rMlTSHSdVOOvLTvaEaDdS2dNOk1LbTNj92zvMG7hEujNriHFLpYFSEKV3wy9KLPJuoAGhCeTUT5QOumn2it/QCKwbujpmW5pOxCTj1qh5ILc/OhR4OSMXBpxbRSlTEhbSNY69Vo6UrGm8QTu6Gh2DbjtEV3KkXJbixCYyK6ri2+1XgSQ+XnpMosl51kMoS1yYUCEgrB0Gug4G2bPdi2ZWDOMDvFwjYja7Zjdsk2hUGxF/ecbcQ39OFKbSCorZR9GQNApZ31HhXCxdz7kj+xe5bLb/AHC1NWaInSx3u2KdMxDiJBeYddaUkJSUEI1CVne0PEa1HzA+GV4/edgF2wm+wM3ybIodyvsSyXi35DPMtp9ElRbDzYI+iWhZSrRGiSNRpXeV3TN4iWK6ZVOwUM4TarxItU66M3hLkhlLUkxzIEctDeb1AJG/vDjoFAantI2Z7SdoORYq5tFuONN2PHJzd1RGx5L5cuMxoEMrdLoAbQlR39xO9qQOPRpmuDYBm+1XZ3lGJNSbDbsFuWWXZM+YVPKuRZTcnFONNt7vJ6rKSnfKuAV9Uka0vWwF0znuxbNieQ5DDiRLRPgY88qPcFy8kiwprjiAC6mLEc8p7d104lG8oFKddKmbr3RN1euOVN4xhYyG349b4t2kTXbqmLy0d+Py6Q2gtqJc3QrRJ0SdOKgSBXC37K87wHKcm8EV4rPxy/3Vy8Hn5D4lQHntC+lAbSUuoJBUkFSCCojUip9Oyy6JyjaxcQ/CEbLLfEiQEBa95pTUZxpXKjd0SN5Y03SrgDwHRU/MCC20bR7zftm2MM7PJC2Mky5pM+1uKGikR2mO+1qUAehQS2yfe+K+WebSHMns+wzILDcJMKBkWSwlPNx31I5VhyHJWWXN0jeAUBqk8NUDhqK6OLdzXc5V0x93Lbw8xFxzFrfYLWnGrxLhuB1DY78cWtvkzurWhoJGp1S2CQk8K42LuechsFuxSxsT4DlhxjOF363B6S84+m2qaf0ZUVIO86lx9WmqiCniVa8Kj5mD0JVez/71ZX5Rn9sirDVez/71ZX5Rn9sitcj7sPev2Wh2o0SlKV4xUUpSgFKUoBSlKAUpSgFUvLvv3xj8hN/UzV0qm5wkQr3YLq95EKOX47zx+q1yoTuqV9g1RpqeAKhWvRfu+D/TJW079K/EqC0hSSCkjUEeev2tBApSlAKUpQClKUApSlAKUpQCq9n/AN6sr8oz+2RVhqv5gE3CE1aGVBydNfZDbKeKt1LqFLWQOhKUgkk8Oga6kV2k3TIXgy0O1Gh0pSvGKilKUApSlAKUpQClKUAri42h5tbbiErbWClSVDUEHpBFcqUBWF7L8OdWVKxSyqUekm3tfu1x8VeGeydk/R7X7tWmlaNYndd5smrxKt4q8M9k7J+j2v3aeKvDPZOyfo9r92rTSmsTuu82KvEq3irwz2Tsn6Pa/dp4q8M9k7J+j2v3atNKaxO67zYq8THdqGzvF4EvCBFx61RRIyKOw8GobSOVbLTxKFcBqkkA6cegcKvPirwz2Tsn6Pa/dqG2uEibgGh0/wB542vTx+hf+z/WtCprE7rvNirxKt4q8M9k7J+j2v3aeKvDPZOyfo9r92rTSmsTuu82KvEq3irwz2Tsn6Pa/dp4q8M9k7J+j2v3atNKaxO67zYq8SreKvDPZOyfo9r92pWy4vZscDnNNpg2zlPr95x0Nb3490DWpSlVinTY1SKJtd4qxSlK4kClKUApSlAKUpQClKUApSlAKUpQClKUApSlAZ7tcBM3ANEb2mURj5+H0L/GtCrPNrqSqbs/0SVaZRGPDzfQv8TWh0ApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUApSlAKUpQClKUBnu1wAzcA104ZPG6df6l/o0/1rQq8K93T3RW03YztNxeBb7LYZuOKfZutokSIshTy5LaVNuMuKS8lJ0LhOiQDopPHp19j7OZWST8FskrL2YUbJX4qHZ8e3tLbZZcVx5NKVrWryQQk6qOpBPQdKAsdKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBULld9dsVvaVGaS9NlPJjRkOEhG+rU6q047oAUo6dOmnDXWpqqdtE+6MU/PA/wDGkVokQqOYlFsJW06i7Zf3TvKzC5NKPSmPFiBH9wUyo/4k1x5nvvtpeOrQf4epulb7fZXlXAVITme++2l46tB/h6cz3320vHVoP8PU3SnKdleWHgKkJzPffbS8dWg/w9OZ777aXjq0H+HqbpTlOyvLDwFSE5nvvtpeOrQf4enM999tLx1aD/D1N0pynZXlh4CpnuabHIu0R+yPZHfbjdXbLNRcYCnWIY5B9PQrgwNR9qVapOg1B0FWfme++2l46tB/h6m6U5Tsryw8BUhOZ777aXjq0H+HpzPffbS8dWg/w9TdKcp2V5YeAqQnM999tLx1aD/D05nvvtpeOrQf4epulOU7K8sPAVITme++2l46tB/h6cz3320vHVoP8PU3SnKdleWHgKkJzPffbS8dWg/w9dm33W6Y/coMa43FV4hTnhHQ+80ht5lwpJTryaUpUlW7p0AgkdIPCSqByr+Xx/8AO8b9ZqVSZ8sSWSX6RKdbjQaUpXjlRSlKAUpSgFU7aJ90Yp+eB/40irjVO2ifdGKfngf+NIrVov3V4/pkrad6q1tC2gWrZpja7zdhJdaLzUViLCZL0iS+4sIbZaQPrLUogAcPeQKstUfbJjeNZThS4eUXgY/DRKjvxrsmWiM5ElocBYcbcX5IWF6aA6666aca0PYQfGJtktzeFXHJ7/Zb7h8KDKREdYvkIIfUtam0IKEtqWFJUp1CQoHTXXXTQ6X6vGW0/KHs/wBiO02w5ROtebs4ZebV3vk8aOhLUhC3mVOFYSShLrba3ULKCBovoGpq85js7wq+7ZdkGOwbfb3cQYtV9Um2QCBDc8qIooUlB3VJ31bxSeBIGoqloGm5ttyteIZMvHYdiyDLr2xHTKlw8chJkKhtKJ3FOqUtCUlW6rdSCVEDUDSrNgOe2baVjEa/WKQt+E8pbakPNlp1lxCilbTiFcULSoEEH9WhrGtlt4x/ZZto2sWG9zIGOrkvQLjbTMdRHbet6YiGUBoqIBS0ppSCB9Ws1w+azIyrHMwSoRsXve1e5S7dLd8hp1pdudYS6CfM662vT7SffS0D1tmOYWrA8ek3u9SDFt0dTaFuJbUs7zjiW0DRIJ4rWke7XjoONTVeKNsbdhy+1d0TNcEK7wYV/wAedS/5LrbSm2orTygeIBSgvIV9g3gfPV6zbENmz22XZhYnodhGGNWK9BiFvtpglfKxVFISDuq4latOPHj0jWloHp2oXMcwtWB49Jvd6kGLbo6m0LcS2pZ3nHEtoGiQTxWtI92vHQca8mYZcY8W5YQ5HnF3CbbtPuMCxTXnytlMQ295LaG3FE7zYeLyEHU66AA9FdXbG3YcvtXdEzXBCu8GFf8AHnUv+S620ptqK08oHiAUoLyFfYN4Hz0tXA9KbS9s8PZtkNgsZxy/5LdL01JejR7DHadUlDHJ8oVBx1H9anTTXz/3zWznaRZtqNgcutmMltLElyFLiTWFMSYkhvTfZdbVxStOo4e8Vhef2BcHbBseteyyVYLC0xbL6Iyu9O+YTaCYqnAltpaOJKienpJ1FQ2Y7Lbfjs2xYJeTZcnyHJJdzyi45FlQcj29DoDCHOTitOoC1bvJBKCvyEoUrXjSrqD1tSvE2FQ2doWBbArTeJ/PUJGV3eCp1l9ZS/HZbnBtve3iotltCE6FR1QdCSDxtO1LFMT2S7TIWUOQbHfrBAFsgt483MLFwx9XL6NOw2UnRaFqcCltkJKt08SNRS1dUHrCleHbPhczanKyu6XfNsWxnNmsjkwRMuUaRzva3EySmK2w5362kIKOT3EhvdUFaELJJOxbMcDs187oPa5e7rDbuNztt3t3ebj4KkRnObY5U42gnRKzqAVdOiQNaKKvQD0BUDlX8vj/AOd436zU9UDlX8vj/wCd436zWmV/NFodpoNKUrxyopSlAKUpQCqdtE+6MU/PA/8AGkVcap+0Mav4sfMm7pJJ/wCXfH6yK1aL91eP6ZK2ndroXzH7Xk9tdt15tsS7W93TlIk5hLzS9OjVCgQf8K79K0kETAxGxWqwqskKy2+HZVIU2q3R4raI5Sr6yS2Bu6HzjTjXws+B4zjpgm1Y7abYYKXURDDgtNd7pcILgb3UjcCylO8BprujXXSp2lQCDybBsbzVMdOQ4/ar8mOoqZFzhNyQ0T0lO+k6HgOiuxdcWst9shs1ytEC4WgpSjm+VGQ7H3U/VHJqBToNBpw4aVKUoCEt+DY3aYc6JBx+1Q4k5CWpbEeE2hEhCUcmlLiQnRQCAEgHXQDToqkXfudcRu2W47cDZbK3j9ngzYYxw2hlUV1UhbK+U3fqpKSz/QOu90jTjqVKUQIeZhuP3DHk2CVY7bJsSUpQLY9EbXGCRxA5Ijd0HmGlcLfg2N2mHOiQcftUOJOQlqWxHhNoRIQlHJpS4kJ0UAgBIB10A06Km6UBA2fAcYx5UA2vHLTbTAS6mGYcFprvYOacqG91I3AvdTvaaa7o110r75Hh9hzFhhi/2S3XtlhzlWm7lEbkJbX/AEkhYOh94qXpQEMxhePRZDL7NitjL7Mpc5t1uG2lSJC0bi3kkDUOKSSkqHEg6E6VwnYJjV0v8e+zMetUu9xtOQuT8JpclrTo3XCneTp7jU5SlAQE7Z/i9zyBm/TMbtEu+M6clc34DS5KNOjddKd4aebQ1Jw7Nb7fMnS4sGNGlzlpclvsspQ5IWlIQlTigNVkJSlIJ10AA6BXcpQCoHKv5fH/AM7xv1mp6oLKBvSMeA6ed4/D8RJ/UK7Sv5lodpoFKUrxyopSlAKUpQCo++WVi/29UV9TjflJcbeZIDjTiTqlaSQRqCPOCDxBBBIMhSrQxOFqJbQUpWPZag7qLtZnUjoW5b3UqP4wHtNf/wC4dFfnMOYes7H1F7tqu1K1a1MwWSJqUnmHMPWdj6i921OYcw9Z2PqL3bVdqU1qZgskKlJ5hzD1nY+ovdtTmHMPWdj6i921XalNamYLJCpSeYcw9Z2PqL3bU5hzD1nY+ovdtV2pTWpmCyQqZTls7LsVesDapNlk87XNu2gpiPJ5IrQtW+fpeIHJ6ae+rBzDmHrOx9Re7aultdUEzdn/AA11yiMPN/Uv1odNamYLJCpSeYcw9Z2PqL3bU5hzD1nY+ovdtV2pTWpmCyQqUnmHMPWdj6i921OYcw9Z2PqL3bVdqU1qZgskKlJ5hzD1nY+ovdtTmHMPWdj6i921XalNamYLJCpSeYcw9Z2PqL3bV3bPic3nBmde57E56MSqMxEYUyy0oggrIUtRWrQkAkgAE8NeNWmlVi0mZEqXLwQqKUpWUgUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgM92uHSbs/wDK3dcojDTU8foX+FaFWebXVlM3Z+B/OyiMOn/6X60OgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgFKUoBSlKAUpSgM82u6d+7P8AXT76I2muv9S/0Vodea+6K7p3ZtgWbY1jl8yVVuvFnvcW4Toxt8pYbjlhwhYWhopWPpEcEknp84Om8YTmln2iYrbsjsElc2zXBsuxpDkdxguI1I3txxKVAEg6agajQjgQaAnKUpQClKUApSlAKUpQClKUApSlAKUrFNqedvXu4SrBb3lNWyMrkpjrSiFSHR9ZrUdDaehX9I6pOgSQvbomix6XM5ODxeCBdb1tixizSHI6ZbtykNnRbduZU8EnoIKx5AIPDTe191Q52+2f1Lez/wBhrtayZttLSEoQkIQkaBKRoAK/a+ug+DaLCqRVb7xVYGseP2z+pb38FntaeP2z+pb38FntayelX5n0TB5i1uNY8ftn9S3v4LPa08ftn9S3v4LPa1k9Kcz6Jg8xa3GseP2z+pb38FntaeP2z+pb38FntayelOZ9EweYtbjWPH7Z/Ut7+Cz2tPH7Z/Ut7+Cz2tZPSnM+iYPMWtxnHdc7NLH3R+UYbeoMC52yVBfTFuzrrLYU9AKt47mjhBcSd7dB0B3+JGlelbTtpxyxWuHbYGO3mLBhsojx2G2Gt1ttCQlKR9L0AAD+6sxpTmfRMHmLW41jx+2f1Le/gs9rTx+2f1Le/gs9rWT0pzPomDzFrcax4/bP6lvfwWe1p4/bP6lvfwWe1rJ6U5n0TB5i1uNY8ftn9S3v4LPa08ftn9S3v4LPa1k9Kcz6Jg8xa3GseP2z+pb38FntaeP2z+pb38FntayelOZ9EweYtbjXWNvNgWtIfg3eGg9LjkTlAn+5tSj/AICrvYsjtmTwu+7XNZmsa6KLSuKD9ih0pPuIBrzXX1t06ZYrmi52t4Rbg2NN8jVDqf6Dg/nIP2dI6QQQDWaf8EkxQ/RbT33oVTPUNKhcPymPmNgj3OOnkisqbdYJ1LLiTotB+3QjgdBqCD0GpqvjY4IpcTgiVGgda5S+8LdKlab3INLc0+3QE/6V5XtJUu2xnHFKcdcQHXFqOpUtXlKJ/GSTXq19hElhxlwatuJKFD7QRoa8stW96yretUnhKtyzFc4Ea7um6r8SklKh7lCvqvgLhpMh6bsrw9hm+3DG59+iWB5FrfyKxwZqn7rY4zu45Ma5NSUkAkBe4ohW4T5WlU6/3VsYns7b2bTGbNaV5GIa4ktl/ebe0dUWnUcolQSlQXvNnz7mhATx17MLNfrxFjJsGQpx6S07vuOLgolpdToRuFKlJ04nXUEHhVQXsTLeJsQY+QPovrV58IDeHo6V780k7ylMggbhCincBGg0417M6VG44nAtq23brk618HcVOUvK8zkbR/BO3myIEeyxrjKuEmO8ocqp1xtaUNhwHdVuap1V5Oh1KtRVZwnZ/ZdsbOR5FlLL1wuLl3lw4iu+HEKt7LLhbbQ1ukBChu7xUBqSrjWiWLCJduzJ7JJ92RcJj9pYtrqW4vIpUpt1xwuDyzprymm75tOk61AT9kd4h3S9O4tmT+NW69PqlTYQgtyCl5YAccZWogtlWmp4K48RpSKXG6RRw2lV3XeG7/0EDsY2mXq8ow6y3BxqcJFvuRfnOAl51UWUlhtW9rp5STqrUEk+f7Y3Lc3yzJG7Gq2zYNrWxnb9m05F1SXUNF0NcoEup3k6IJUnhqd0jd3dDd1bHUWVvFlYpdBYpFgiuwWlyIwlIfZd3SsOJ3kHeKkBW8COJPAg6V0WtiMlnFeb0ZK6q7tZAvIY11XDSd19S1K0W0FALGi1A6FOuvADornyekWOTdXTfuXjtqDsXXJ81dz9OLWp2wtvM2Jm5PypkV9SVvKdcbUlCUujdQdwEakkf/lXWwTa/cMwyLHbc9BjQ++4VwVPaBUtTUmLIbZIQrUDcJUo8Rr0cemo+44jmcva3y8G/c2vJxliM/eVWcOx33BJeJSlJWAlYBCtN46A8QQal4mxZePMY27jt9XAutnbktLmTYolCYJCw48XEbyPKLiQoEHh0aEVdOc4m4U6J7sVsvwqCoZbm+WZI3Y1W2bBta2M7fs2nIuqS6houhrlAl1O8nRBKk8NTukbu7oejtOYx1e2iCnOLS9fyMVbCUWyC+6e+O+VaqbS2VKQD5WhKuGoGutXdrYjJZxXm9GSuqu7WQLyGNdVw0ndfUtStFtBQCxotQOhTrrwA6KstpwWXFzhnJ592ROmJsybU6huLySXFB4ul0eWd3p03eP261zcibGqRrbTbRrKoMjtmc5js5wGyQ5Eu3M3HveRKbtd8YlTLkWA8ssoWGNSgBvcSVr1APA9Bq4+NDI8mu2GRMbi2yM3kNiXd3HbkhxzvUjkiBohSd8fSbunDiQdRpoZzJtmc665XNvVqyE2Y3KAi3T2jDS+pbSFLKS0pShyavpFDUhQPDhwrhh2yheK3DFpK7uJvMVmdsyE97cnyyFLbKVk750KUtAEcdSdeHRV4Zc+F2E3Zu9Gt/Sq9CBGWHaPf5+0m42C5PWO1sQHCkwpDbyJsxkNBXfTBKtxSCvUboBKQDqdRxrNj7oC/X163XOJaBKss6UhtFvZtFw76Qwpe6HjJ5PkFEAhZSOGmoCiavV52ZXLJcwttzu2RJlWi2T+cIduRb0IdbXuFIQXwrUo8okjd1PnNfPDdmN6wZ2Lb7dly/BSI8pxi0uwELeQ2STyPfBVqUAnh5O9oAN6ps6RapV0q8K9FOnZt4Aqt12n5q5i+0K9x0WiJbsck3GGyAw4uQ+WkkNr1391O6ooJ1Ct7RXBPCofM9nNq2d7JBm9ocfazC2x2Lgu9KkuKcmuFSC4l7VRC0r3lDdPAajStZx/Z3FtVoye2TXhc4d+ny5jzam9wBEj6zXSddBqN7hrr0CqkxsOuUiBbrDeMzlXfDoDjSmrQuE2h11DRBaaefB1WhJSngEp10GpqkcmZEvmVW1uueP62X3Al0bRLit7aUnveMlONNoXDBQrVZVCS+Q75XHylEcN3h7+NVSyZDl+SbV8ZkMXK3xoU3FGLi/CcjvLbAW60XdwB0AOEkhKyDongQrpqz5LsouN0uuUSLVkps8TJY6GrgwqCl9e8lrkgttZWNzVASCCFdHAg8a/fFTPt83GJ9myIW64Wi0osz7jsEPty2BuH6hWNxW8jUHU6a6ca6RQzonenRPFX39+AK3K2qZpFseV5MmNY5Fjx67TIjsFLTyZT0dh3dK0ub5SFhPHTdIOnm10qysbSZ0lW0hTTUVTOOtNuwVbitXAqEiR9J5XHylacN3h7+NUzH9lWQ5ZDy623K9yLPjM/JJ7si1m3BL0pgyCobj6iCG1gDiEnUa6HQ1b8i2Sz7hcsmds+ScywsjjIYnxjAS+oKSzyIU0srG5qgJBBCujgQeNUg5dpRJP0wfpsBWrHkOX5JtYxmQzcrfGhTcUYuL8JyO8tvRbrRd3AHQA4SSErIOieBCumturOk7Kp1un4vcLPkIgT7RakWaQt2EHm5kdO4fqlY5NW8jUHU6a6ca0WtciGOG1bBouwiatF2yOBqeRKI0tI14BauUQo/4Nt/4VsNZTsHtCxHvV7UPo5jiIrB/pIZ39VD/rccT/ANFatXw/xVwxaZHZ3Z0VfUuxWf7SdmysmUm6WpSGbw2ndW24dG5SB0JUf5qh/NV/ceGhToFKwSJ8ejzFMlujRB5VuTqrHI73u7Ltokj/AIU0BvXjp5Ktd1X40kivhzvA9Nj/ABU/OvVzrSH0FDiEuIPSlQ1BroHG7QTqbXCJ/wCXR8q+ng+PKnzy79z9hRHmDneD6bH+Kn5053g+mx/ip+den/Bq0eqoXV0fKng1aPVULq6PlV+fZf43n7CiPMHO8H02P8VPzpzvB9Nj/FT869P+DVo9VQuro+VPBq0eqoXV0fKnPsv8bz9hRHmDneD6bH+Kn5053g+mx/ip+den/Bq0eqoXV0fKng1aPVULq6PlTn2X+N5+wojzBzvB9Nj/ABU/OnO8H02P8VPzr0/4NWj1VC6uj5U8GrR6qhdXR8qc+y/xvP2FEeYOd4Ppsf4qfnTneD6bH+Kn516f8GrR6qhdXR8qeDVo9VQuro+VOfZf43n7CiPMHO8H02P8VPzpzvB9Nj/FT869P+DVo9VQuro+VPBq0eqoXV0fKnPsv8bz9hRHmDneD6bH+Kn5053g+mx/ip+den/Bq0eqoXV0fKng1aPVULq6PlTn2X+N5+wojzBzvB9Nj/FT86c7wfTY/wAVPzr0/wCDVo9VQuro+VPBq0eqoXV0fKnPsv8AG8/YUR5g53g+mx/ip+dOd4Ppsf4qfnXp/wAGrR6qhdXR8qeDVo9VQuro+VOfZf43n7CiPLzl8tzWm9OjAngByqdT+Ia8auOI7O7rmD6FvsSbTZ+BXKeRybzo/otIV5Q1/pqAHHhr5t3i2qFBVvRoceOr7WmkpP8AkK7dZp/xyKOGzJgsvFuv9E3I68CBHtcJiJEaSxGYQG220DglIGgFdilK+YbbdWQf/9k=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "flo = Flo.build(session, simple_llm_agent)\n", + "\n", + "flo.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "2024-09-20 00:45:06,730 - COMMON - INFO - Invoking query for session dd5c4a4c-4390-46bc-945d-eb9474e63702: What is pythagorus theorum\n", - "2024-09-20 00:45:06,783 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'messages': [HumanMessage(content='What is pythagorus theorum')]}\n", - "2024-09-20 00:45:06,786 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: {'messages': [HumanMessage(content='What is pythagorus theorum')]}\n", - "2024-09-20 00:45:06,788 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: messages=[SystemMessage(content='You are a high school maths teacher. Answer any questions the students ask \\n'), HumanMessage(content='What is pythagorus theorum')]\n", - "2024-09-20 00:45:06,792 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMStart: ['System: You are a high school maths teacher. Answer any questions the students ask \\n\\nHuman: What is pythagorus theorum']\n", - "2024-09-20 00:45:11,061 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onLLMEnd: [[ChatGeneration(text=\"Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \\n\\nThe theorem can be expressed with the formula:\\n\\n\\\\[ c^2 = a^2 + b^2 \\\\]\\n\\nwhere:\\n- \\\\( c \\\\) is the length of the hypotenuse,\\n- \\\\( a \\\\) and \\\\( b \\\\) are the lengths of the other two sides.\\n\\nThis theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\", generation_info={'finish_reason': 'stop', 'logprobs': None}, message=AIMessage(content=\"Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \\n\\nThe theorem can be expressed with the formula:\\n\\n\\\\[ c^2 = a^2 + b^2 \\\\]\\n\\nwhere:\\n- \\\\( c \\\\) is the length of the hypotenuse,\\n- \\\\( a \\\\) and \\\\( b \\\\) are the lengths of the other two sides.\\n\\nThis theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\", response_metadata={'token_usage': {'completion_tokens': 162, 'prompt_tokens': 34, 'total_tokens': 196, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_e9627b5346', 'finish_reason': 'stop', 'logprobs': None}, id='run-5851e0be-448a-4e3c-a517-187dcdfbea46-0', usage_metadata={'input_tokens': 34, 'output_tokens': 162, 'total_tokens': 196}))]]\n", - "2024-09-20 00:45:11,063 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainStart: content=\"Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \\n\\nThe theorem can be expressed with the formula:\\n\\n\\\\[ c^2 = a^2 + b^2 \\\\]\\n\\nwhere:\\n- \\\\( c \\\\) is the length of the hypotenuse,\\n- \\\\( a \\\\) and \\\\( b \\\\) are the lengths of the other two sides.\\n\\nThis theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\" response_metadata={'token_usage': {'completion_tokens': 162, 'prompt_tokens': 34, 'total_tokens': 196, 'completion_tokens_details': {'reasoning_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_e9627b5346', 'finish_reason': 'stop', 'logprobs': None} id='run-5851e0be-448a-4e3c-a517-187dcdfbea46-0' usage_metadata={'input_tokens': 34, 'output_tokens': 162, 'total_tokens': 196}\n", - "2024-09-20 00:45:11,064 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \n", - "\n", - "The theorem can be expressed with the formula:\n", - "\n", - "\\[ c^2 = a^2 + b^2 \\]\n", - "\n", - "where:\n", - "- \\( c \\) is the length of the hypotenuse,\n", - "- \\( a \\) and \\( b \\) are the lengths of the other two sides.\n", - "\n", - "This theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\n", - "2024-09-20 00:45:11,065 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onChainEnd: Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \n", - "\n", - "The theorem can be expressed with the formula:\n", - "\n", - "\\[ c^2 = a^2 + b^2 \\]\n", - "\n", - "where:\n", - "- \\( c \\) is the length of the hypotenuse,\n", - "- \\( a \\) and \\( b \\) are the lengths of the other two sides.\n", - "\n", - "This theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\n" + "2024-09-22 13:22:49,285 - COMMON - INFO - Invoking query for session e7f22b4d-7380-4d00-97c9-5267a468468a: What is pythagorus theorum, just give me the formula\n" ] }, { "data": { "text/plain": [ - "\"Pythagoras' Theorem is a fundamental principle in geometry that relates to right-angled triangles. It states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. \\n\\nThe theorem can be expressed with the formula:\\n\\n\\\\[ c^2 = a^2 + b^2 \\\\]\\n\\nwhere:\\n- \\\\( c \\\\) is the length of the hypotenuse,\\n- \\\\( a \\\\) and \\\\( b \\\\) are the lengths of the other two sides.\\n\\nThis theorem is useful for calculating the length of one side of a right triangle if the lengths of the other two sides are known. Would you like to see an example of how to use it?\"" + "'The Pythagorean theorem states that in a right triangle, the square of the length of the hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the lengths of the other two sides. The formula is:\\n\\n\\\\( a^2 + b^2 = c^2 \\\\)\\n\\nwhere \\\\( c \\\\) is the length of the hypotenuse, and \\\\( a \\\\) and \\\\( b \\\\) are the lengths of the other two sides.'" ] }, - "execution_count": 7, + "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "flo = Flo.build(session, simple_llm_agent)\n", + "flo.invoke(\"What is pythagorus theorum, just give me the formula\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tool Agent (tool)\n", "\n", - "flo.invoke(\"What is pythagorus theorum\")\n", - "# print(flo.invoke(\"What is pythagorus theorum\"))" + "Lets create a simple tool agent, which has just a tool and nothing else. The tool agent just executes a tool and nothing else. The tool can invoke llms within if in needs to" ] }, { @@ -699,36 +309,23 @@ "name": "stderr", "output_type": "stream", "text": [ - "2024-09-20 00:45:45,169 - SESSION - INFO - Tool 'printStateTool' registered for session dd5c4a4c-4390-46bc-945d-eb9474e63702\n", - "2024-09-20 00:45:45,170 - BUILDER - INFO - Building Flo instance from YAML\n", - "2024-09-20 00:45:45,173 - COMMON - INFO - Flo instance created for session dd5c4a4c-4390-46bc-945d-eb9474e63702\n", - "2024-09-20 00:45:45,175 - COMMON - INFO - Invoking query for session dd5c4a4c-4390-46bc-945d-eb9474e63702: Print what I am saying\n", - "2024-09-20 00:45:45,177 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onToolStart: {'messages': [HumanMessage(content='Print what I am saying')]}\n", - "2024-09-20 00:45:45,178 - FloLangChainLogger-dd5c4a4c-4390-46bc-945d-eb9474e63702 - INFO - Session ID: dd5c4a4c-4390-46bc-945d-eb9474e63702, onToolEnd: Tool call success\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "{'messages': [HumanMessage(content='Print what I am saying')]}\n" + "2024-09-23 14:19:07,287 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-23 14:19:07,290 - COMMON - INFO - Flo instance created for session a1e122c1-ed97-46c7-ad31-e67d373636be\n" ] }, { "data": { + "image/jpeg": "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", "text/plain": [ - "'Tool call success'" + "" ] }, - "execution_count": 8, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "from typing import Optional, Type\n", - "from langchain.pydantic_v1 import BaseModel, Field\n", - "from langchain.tools import BaseTool, StructuredTool, tool\n", + "from langchain.tools import BaseTool\n", "\n", "class PrintStateTool(BaseTool):\n", " name = \"printStateTool\"\n", @@ -738,7 +335,7 @@ " self, **kwargs\n", " ) -> str:\n", " print(kwargs)\n", - " return \"Tool call success\"\n", + " return \"Print tool call success\"\n", " \n", "session.register_tool(\n", " name=\"printStateTool\", \n", @@ -758,7 +355,188 @@ "\n", "flo = Flo.build(session, simple_tool_agent)\n", "\n", - "flo.invoke(\"Print what I am saying\")" + "flo.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-23 14:19:14,084 - COMMON - INFO - Invoking query for session a1e122c1-ed97-46c7-ad31-e67d373636be: Print what I am saying\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [HumanMessage(content='Print what I am saying')]}\n", + "Print tool call success\n" + ] + } + ], + "source": [ + "print(flo.invoke(\"Print what I am saying\"))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Reflection Agent (reflection)\n", + "These agents help evaluate a work based on certain criteria. In the graph you can see flo has automatically added reflection manager to handle retries and its count." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-22 13:22:57,657 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-22 13:22:57,664 - COMMON - INFO - Flo instance created for session 71b7747f-6530-45d7-b78d-54b8cc8bbb02\n" + ] + }, + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAGwAXgDASIAAhEBAxEB/8QAHQABAAIDAQEBAQAAAAAAAAAAAAUGBAcIAwIBCf/EAFgQAAEEAQIDAwYHCA4HCAIDAAEAAgMEBQYRBxIhExUxCBQiQVaUFzJRVWHT1BY1NnWVs9HSIzQ3QlJUcXJzdIGhsbIlM0Nig5G0JEVjgpPBwuE4dqLw8f/EABsBAQEAAwEBAQAAAAAAAAAAAAABAgMEBQYH/8QANBEBAAECAgYIBQQDAQAAAAAAAAECEQNREhQhMZHRBBMzQVJicZIFImGxwSOBofAy4fEV/9oADAMBAAIRAxEAPwD+qaIiAiIgIiICIiAiIgIiICIiAiIgIiICIonOZmTHugqU4PO8na5hBCTs1oHxpJD+9Y3cbnxJIA3JCyppmqbQJVzgxpc4hrQNySdgAo1+psPG4tflqLXD1OssB/xUWzQVK+9tjPvdqK2Dzf8AbRvXjP8A4cHxGgHwJBd4buO26kmaTwcbA1uGx7WjoAKrAB/ct1sGN8zPpH9+0Mtj9+6rC/PFD3ln6U+6rC/PFD3ln6U+5XC/M9D3Zn6E+5XC/M9D3Zn6E/R+v8Gx71c3jrz+Stfq2H/wYpmuP9xWaoS1ojTt1nJYwWNmbtsOepGdv5OnRYMmHuaTYbOHfZv0GAdpiJpO0IaPEwPd6Qd/uOcWnbYcm+6aGHVsonb9ef8AfVNi0osehfr5SlDbqyCavM0PY8AjcfyHqD9B6hZC0TExNpQREUBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBVjSW2UyuezL9nPfbfQgPX0IYCWFv9svbO6fKPkVnVY0IPNYM1j3biWplrbnAjbpNIbDSPlHLMOvyg/IuijZh1zG/Zw/7ZY3SzdZazwnD3TN7UOo8lDicNRYH2Lc5PKwEho8NySSQAACSSAOpWk+JXlqaK0loHEaowMz9QVMhm48OS6rag7D4jpnua6Hm3Yx7XBmwLuYbbrYHlDYDDan4P6hx2fwGZ1Li5mRdpjtPR9pfcRKwtfA3cbvYQH7fIw9D4Hl3L43izrDgPcmyuF1NqKtpzWePyeGjzGPbBnr2OheC/tIG7FzwT0JHM7Ylc6Ol9SeU3w30hpfA6hzGoJaOIzjJJKEz8ZbLpWxkB5MYiL2Aczd+cN8V66r8pPhtonGaZyOX1TXgo6likmxFiGCawy22NrXO5TGx2x9NoDTsSTsAT0WkeMWsNa681RpS8NPcUsRw4v4aw52M0xUNTLHJiw5jY7mx5oYTEA4buA9IE+vam8C+Guq8bL5KtPN6UzFaTTNnVMeWNuhJ2dJzg813SP5eVrXEs5Hb7OI9EnZBuR3lq6MPGfEaNYbPdORw0WQiyzsfcD32JpWCCDsew5mtdG/nMp9EbgHYgrohc28TZc3w+8rbAa7bpDUOptPXdJP086XT1Lzt9aybomBlaCORnLt6R6f8AIrpJBWNP7YvVedxDNm13iPKQsG+zTM6QSj+2SNzz9MhVnVYx7fPOIeYsN37OpRrUySOnaF0kjhv69mujP/mVnXRj/wCUT9I+0LIiIudBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBV7MUbGLy3ftCA2XOibDeqs+PNE0ktcwet7S53T98CR4hqsKLOiuaJuu5h4rMUs5TbaoWY7UBJbzRnflcPFrh4tcPAtOxB6ELMUHldGYrLXTddFLTyBABuUZ315nAeAc5hHOB8jtx9CwzoiYABup88xo9XbxH+8xkrbo4VW6q3rHLlBsWhFqvifj8rpHSYyOP1TmDZ7xx9X9nlhLeSa7DDJ/sx15JHbfTt4+Ctn3E2ParPf8ArQ/VJ1eH4/4ktGa0KFzWo20ZvMKLG5DNSN3ipNfty7+Ekp69nGPW4jc7bNDnENOD9wgl6WdQZ21HtsWG72O4/liax3/IqZxGCoYGu6HH1Y6zHHmeWjdz3eG7nHq4/SSSlsKjbfS+3P8Au82Q89P4VuCx5hMvnFmWR09mwW8pmled3O23Ow9QG52aGj1KTRFpqqmqZqnegiIsQREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREGvuO5aOHo5iQO+cP4fL3nV29Y/wD78vgtgrX3Hbf4PRty/fnD/GAI++dX5f8A/fk6rYKAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiAiIgIiINeceQDw7G7mt/wBNYbq4b/8AedXothrXnHnb4OxuSB31hvAb/wDelVbDQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBEWHl8rXwmOmu2nOEMQG4Y0uc4kgNa0DqSSQAPWSFYiaptG8ZiKlP1DqubZ8OKxVdjuojsXZHSNH+9yx7b/KASPkJXz37rD+IYP3ub6tdeq15xxhbLuipHfusP4hg/e5vq0791h/EMH73N9Wmq15xxgsu6Kkd+6w/iGD97m+rTv3WH8Qwfvc31aarXnHGCzn7y5fKfn4KSYbTc+jpctQyZqZKLLC6ImCStcZK+DkMTtztEz0t+nab7dOu8fJ/wCK1/jZwyx+sb2m3aXiyL3uqU33POXyQA8olJ5GcvMQ7YbHoAd/S6a78ongrkfKP0fTwWcrYmiadxluC7WsSGWP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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "yaml_data = \"\"\"\n", + "apiVersion: flo/alpha-v1\n", + "kind: FloRoutedTeam\n", + "name: adding-team\n", + "team:\n", + " name: EssayTeam\n", + " agents:\n", + " - name: EssayWriter\n", + " kind: llm\n", + " job: >\n", + " You are an essay assistant tasked with writing excellent 300-words essays. Generate the best essay possible for the user's request. \n", + " If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words\n", + " - name: ReflectionAgent\n", + " kind: reflection\n", + " retry: 1\n", + " to: \n", + " - name: EssayWriter\n", + " job: >\n", + " You are a teacher grading an essay submission. Generate critique and recommendations for the user's submission.\n", + " Provide detailed recommendations, including requests for length, depth, style, etc.\n", + " - name: FinalEssayProducer\n", + " kind: llm\n", + " job: >\n", + " Generate the final assay to be returned to the user\n", + " router:\n", + " name: router\n", + " kind: linear\n", + "\"\"\"\n", + "\n", + "input_prompt = \"\"\"\n", + "Question: Write me an interesting blog about latest advancements in agentic AI by reasearching the internet\n", + "\"\"\"\n", + "\n", + "llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini')\n", + "session = FloSession(llm).register_tool(\n", + " name=\"TavilySearchResults\", \n", + " tool=TavilySearchResults()\n", + ")\n", + "\n", + "flo: Flo = Flo.build(session, yaml=yaml_data)\n", + "flo.draw()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Delegator Agent (delegator)\n", + "\n", + "Using these agents you can do redirects, this can help redirecting calls as well as retrying previous steps" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-22 13:25:01,619 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-22 13:25:01,627 - COMMON - INFO - Flo instance created for session 11351ff8-1110-4724-9e15-042eeccd99b4\n" + ] + }, + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "yaml_data = \"\"\"\n", + "apiVersion: flo/alpha-v1\n", + "kind: FloRoutedTeam\n", + "name: adding-team\n", + "team:\n", + " name: EssayTeam\n", + " agents:\n", + " - name: EssayWriter\n", + " kind: llm\n", + " job: >\n", + " You are an essay assistant tasked with writing excellent 300 words essay. Generate the best essay possible for the user's request. \n", + " If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words\n", + " - name: DelegatorAgent\n", + " kind: delegator\n", + " retry: 1\n", + " to: \n", + " - name: EssayWriter\n", + " job: >\n", + " You are a teacher grading an essay submission. Score the essay between 1 to 10, with 10 being perfect\n", + " If the score is greater than 7 sent it to FinalEssayProducer\n", + " else if its less than or equal to 7 sent it to EssayWriter with suggestions to change\n", + " - name: FinalEssayProducer\n", + " kind: llm\n", + " job: >\n", + " Generate the final assay to be returned to the user\n", + " router:\n", + " name: router\n", + " kind: linear\n", + "\"\"\"\n", + "\n", + "input_prompt = \"\"\"\n", + "Question: Write me an interesting blog about latest advancements in agentic AI by reasearching the internet\n", + "\"\"\"\n", + "\n", + "llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini')\n", + "session = FloSession(llm).register_tool(\n", + " name=\"TavilySearchResults\", \n", + " tool=TavilySearchResults()\n", + ")\n", + "\n", + "flo: Flo = Flo.build(session, yaml=yaml_data)\n", + "flo.draw()\n", + "# data = flo.invoke(input_prompt)\n", + "# print((data['messages'][-1]).content)" ] } ], diff --git a/examples/agentic_rag.ipynb b/examples/agentic_rag.ipynb new file mode 100644 index 00000000..09f3b3f8 --- /dev/null +++ b/examples/agentic_rag.ipynb @@ -0,0 +1,251 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Agentic RAG implemented using FloAI\n", + "\n", + "FloAI has just made implementing agentic RAG simple and easy to manage. The Diagram shows what we are going to implement:\n", + "\n", + "
\n", + " \"Sample\n", + "
\n", + "\n", + "To implement this we use the following components, already available in flo-ai:\n", + "\n", + "1. `tool` agent: This will be a tool agent to retrieve the records from vector store\n", + "2. `delegator` agent: The agent will check if the retrieved records are relevent, else it will re-write the query and send it back for re-retrieval\n", + "3. `llm` agent: This will generate the output from the relevant documents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from flo_ai import Flo\n", + "from flo_ai import FloSession\n", + "from langchain_openai import ChatOpenAI, OpenAIEmbeddings\n", + "from langchain_chroma import Chroma\n", + "from langchain_community.document_loaders import TextLoader\n", + "from langchain_community.embeddings.sentence_transformer import (\n", + " SentenceTransformerEmbeddings,\n", + ")\n", + "from langchain_text_splitters import CharacterTextSplitter\n", + "\n", + "from dotenv import load_dotenv\n", + "load_dotenv()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Setting up Vector Store with Sample Data\n", + "\n", + "Using Chroma is this example" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/vizsatiz/Documents/hub/flo/.venv/lib/python3.11/site-packages/sentence_transformers/cross_encoder/CrossEncoder.py:11: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from tqdm.autonotebook import tqdm, trange\n", + "/Users/vizsatiz/Documents/hub/flo/.venv/lib/python3.11/site-packages/transformers/tokenization_utils_base.py:1601: FutureWarning: `clean_up_tokenization_spaces` was not set. It will be set to `True` by default. This behavior will be depracted in transformers v4.45, and will be then set to `False` by default. For more details check this issue: https://github.com/huggingface/transformers/issues/31884\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "# load the document and split it into chunks\n", + "loader = TextLoader(\"./rag_document.txt\")\n", + "documents = loader.load()\n", + "\n", + "# split it into chunks\n", + "text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0)\n", + "docs = text_splitter.split_documents(documents)\n", + "\n", + "# create the open-source embedding function\n", + "embedding_function = SentenceTransformerEmbeddings(model_name=\"all-MiniLM-L6-v2\")\n", + "\n", + "# load it into Chroma\n", + "db = Chroma.from_documents(docs, embedding_function)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating the retrival tool\n", + "\n", + "This tool will retrive the records from vector db" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-23 14:42:29,261 - SESSION - INFO - New FloSession created with ID: fb386a6f-07b9-4cf4-a83a-21f322c7289c\n", + "2024-09-23 14:42:29,359 - SESSION - INFO - Tool 'HousingLoanTool' registered for session fb386a6f-07b9-4cf4-a83a-21f322c7289c\n" + ] + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from flo_ai.retrievers.flo_retriever import FloRagBuilder\n", + "from flo_ai.retrievers.flo_compression_pipeline import FloCompressionPipeline\n", + "\n", + "llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini')\n", + "session = FloSession(llm)\n", + "builder = FloRagBuilder(session, db.as_retriever())\n", + "compression_pipeline = FloCompressionPipeline(OpenAIEmbeddings(model=\"text-embedding-3-small\"))\n", + "compression_pipeline.add_embedding_reduntant_filter()\n", + "compression_pipeline.add_embedding_relevant_filter()\n", + "\n", + "retriever_tool = builder.with_compression(compression_pipeline).build_retriever_tool(name=\"HousingLoanRetreiver\",\n", + " description=\"Tool to fetch data around housing loans\")\n", + "session.register_tool(name=\"HousingLoanTool\", tool=retriever_tool)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating the flo\n", + "\n", + "This follow as you can see consist of the following components:\n", + "\n", + "1. HousingLoanRetriver: This is a `tool agent`, with ability to retrieve from vector store\n", + "2. RelevancyChecker: This is a `delegator agent` with ability to delegate based on relevancy\n", + "3. ResponseGenerator: This is a `llm agent` with ability to response based on documents" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-23 14:45:06,389 - BUILDER - INFO - Building Flo instance from YAML\n", + "2024-09-23 14:45:06,398 - COMMON - INFO - Flo instance created for session fb386a6f-07b9-4cf4-a83a-21f322c7289c\n" + ] + }, + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "agent_yaml = \"\"\"\n", + "apiVersion: flo/alpha-v1\n", + "kind: FloRoutedTeam\n", + "name: agentic-rag\n", + "team:\n", + " name: AgenticRAGTeam\n", + " router:\n", + " name: SupportSupervisor\n", + " kind: linear\n", + " agents:\n", + " - name: HousingLoanRetriver\n", + " kind: tool\n", + " job: Fetching the loan information from the loan tool\n", + " tools:\n", + " - name: HousingLoanTool\n", + " - name: RelevancyChecker\n", + " kind: delegator\n", + " to:\n", + " - name: HousingLoanRetriver\n", + " - name: ResponseGenerator\n", + " retry: 1\n", + " job: >\n", + " Your job is to check if the records fetched by the retriever are relevent to the question.\n", + " If its not relevant re-write the query send to HousingLoanRetriver for re-retrieval.\n", + " If its relevant then send to ResponseGenerator for response generation\n", + " - name: ResponseGenerator\n", + " kind: llm\n", + " job: Based on the documents given answer the user question that was asked\n", + "\"\"\"\n", + "\n", + "flo = Flo.build(session, agent_yaml)\n", + "\n", + "flo.draw()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "2024-09-23 14:45:11,703 - COMMON - INFO - Invoking query for session fb386a6f-07b9-4cf4-a83a-21f322c7289c: Whats the interest rate for housing loan ?\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{'messages': [HumanMessage(content='Whats the interest rate for housing loan ?'), HumanMessage(content='You can use any one of the ways to repay the loan:\\n \\n Cheques\\n Standing instructions at your branch\\n FedNet - Internet Banking\\n Automated Payment through ECS\\n Mobile Banking\\n\\n Housing loan interest rates will change subject to the changes made by Bank/RBI from time to time. \\n\\nPresent Repo Rate\\t 6.50% (p.a)\\n \\n\\nLoan scheme\\n\\nInterest Rate (%) * \\n\\nHome Loan \\n\\n8.80 (Repo Rate+ 2.30) Onwards \\n\\n*T&C Apply\\nHousing Loan\\n\\nYour dream home is never far away! Get hassle free home loans from Federal Bank to turn your dream home into reality. We assist you to realize your dream home. Avail your Housing Loan from us at competitive interest rates. The loan scheme assists borrowers for construction of house, acquisition of land & construction of house, repairs / renovation / remodeling / extension of house, reimbursement of debt incurred for construction / purchase / furnishing / beautification / purchase of flat / villa / house plots / takeover of housing loans / supplementary housing loan to employees of well-run companies / purchase of house plot for subsequent construction of house etc.', name='HousingLoanRetriver'), HumanMessage(content='The interest rate for housing loans currently starts at 8.80% per annum, which is based on the present repo rate of 6.50% plus an additional margin of 2.30%. Please note that interest rates may change based on adjustments made by the bank or the Reserve Bank of India (RBI) over time.', name='ResponseGenerator')], 'next': 'ResponseGenerator', 'loop_tracker': {'RelevancyChecker': 1}}\n" + ] + } + ], + "source": [ + "print(flo.invoke(\"Whats the interest rate for housing loan ?\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/delegator_example.py b/examples/delegator_example.py new file mode 100644 index 00000000..d1a3b4cf --- /dev/null +++ b/examples/delegator_example.py @@ -0,0 +1,51 @@ +from flo_ai.core import Flo +from flo_ai import FloSession +from langchain_openai import ChatOpenAI +from langchain_community.tools.tavily_search.tool import TavilySearchResults +from dotenv import load_dotenv +load_dotenv() + +yaml_data = """ +apiVersion: flo/alpha-v1 +kind: FloRoutedTeam +name: adding-team +team: + name: EssayTeam + agents: + - name: EssayWriter + kind: llm + job: > + You are an essay assistant tasked with writing excellent 300 words essay. Generate the best essay possible for the user's request. + If the you are provided critique view, respond with a revised version of your previous attempts. A maximum of total 100 words + - name: DelegatorAgent + kind: delegator + retry: 1 + to: + - name: EssayWriter + job: > + You are a teacher grading an essay submission. Score the essay between 1 to 10, with 10 being perfect + If the score is greater than 7 sent it to FinalEssayProducer + else if its less than or equal to 7 sent it to EssayWriter with suggestions to change + - name: FinalEssayProducer + kind: llm + job: > + Generate the final assay to be returned to the user + router: + name: router + kind: linear +""" + +input_prompt = """ +Question: Write me an interesting blog about latest advancements in agentic AI by reasearching the internet +""" + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') +session = FloSession(llm).register_tool( + name="TavilySearchResults", + tool=TavilySearchResults() +) + +flo: Flo = Flo.build(session, yaml=yaml_data) +flo.draw_to_file("delegate.png", xray=True) +# data = flo.invoke(input_prompt) +# print((data['messages'][-1]).content) \ No newline at end of file diff --git a/examples/rag_tool.py b/examples/rag_tool.py new file mode 100644 index 00000000..1160fa43 --- /dev/null +++ b/examples/rag_tool.py @@ -0,0 +1,70 @@ +from langchain.tools import BaseTool +from flo_ai import Flo +from flo_ai import FloSession +from flo_ai.common.flo_logger import get_logger +from flo_ai.common.flo_langchain_logger import FloLangchainLogger +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_chroma import Chroma +from langchain_community.document_loaders import TextLoader +from langchain_community.embeddings.sentence_transformer import ( + SentenceTransformerEmbeddings, +) +from langchain_text_splitters import CharacterTextSplitter + +from dotenv import load_dotenv +load_dotenv() + + + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') + +session = FloSession( + llm, + log_level="ERROR" +) + +# load the document and split it into chunks +loader = TextLoader("./examples/rag_document.txt") +documents = loader.load() + +# split it into chunks +text_splitter = CharacterTextSplitter(chunk_size=1000, chunk_overlap=0) +docs = text_splitter.split_documents(documents) + +# create the open-source embedding function +embedding_function = SentenceTransformerEmbeddings(model_name="all-MiniLM-L6-v2") + +# load it into Chroma +db = Chroma.from_documents(docs, embedding_function) + +from flo_ai.retrievers.flo_retriever import FloRagBuilder +from flo_ai.retrievers.flo_compression_pipeline import FloCompressionPipeline + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') +session = FloSession(llm) +builder = FloRagBuilder(session, db.as_retriever()) +compression_pipeline = FloCompressionPipeline(OpenAIEmbeddings(model="text-embedding-3-small")) +compression_pipeline.add_embedding_reduntant_filter() +compression_pipeline.add_embedding_relevant_filter() +# Reranking + +retriever_tool = builder.with_compression(compression_pipeline).build_rag_tool(name="HousingLoanRetreiver", + description="Tool to fetch data around housing loans") +session.register_tool(name="HousingLoanTool", tool=retriever_tool) + +simple_tool_agent = """ +apiVersion: flo/alpha-v1 +kind: FloAgent +name: llm-assistant +agent: + name: tool-get-loan + kind: agentic + job: To retrieve and answer user questions + tools: + - name: HousingLoanTool +""" + +flo = Flo.build(session, simple_tool_agent) + +print(flo.invoke("Whats interest rate on loan")) \ No newline at end of file diff --git a/examples/rag_with_reranking.py b/examples/rag_with_reranking.py index ef128dfb..91999dd1 100644 --- a/examples/rag_with_reranking.py +++ b/examples/rag_with_reranking.py @@ -47,10 +47,15 @@ ] ) +from langchain.schema import BaseMessage compression_pipeline = FloCompressionPipeline(OpenAIEmbeddings(model="text-embedding-3-small")) compression_pipeline.add_embedding_reduntant_filter() compression_pipeline.add_embedding_relevant_filter() -rag = rag_builder.with_prompt(custom_prompt).with_multi_query().with_compression(compression_pipeline).build_rag() +rag = rag_builder.with_prompt( + custom_prompt +).with_multi_query().with_compression( + compression_pipeline + ).build_rag() print(rag.invoke({ "question": "What are the documents applying for housing loan" })) diff --git a/examples/reflexion_example.py b/examples/reflection_example.py similarity index 97% rename from examples/reflexion_example.py rename to examples/reflection_example.py index 57a075ed..ba893af8 100644 --- a/examples/reflexion_example.py +++ b/examples/reflection_example.py @@ -20,7 +20,8 @@ - name: ReflectionAgent kind: reflection retry: 1 - to: EssayWriter + to: + - name: EssayWriter job: > You are a teacher grading an essay submission. Generate critique and recommendations for the user's submission. Provide detailed recommendations, including requests for length, depth, style, etc. diff --git a/examples/tool_agent.py b/examples/tool_agent.py new file mode 100644 index 00000000..abfda1b0 --- /dev/null +++ b/examples/tool_agent.py @@ -0,0 +1,54 @@ +from flo_ai import Flo +from flo_ai import FloSession +from flo_ai.common.flo_logger import get_logger +from flo_ai.common.flo_langchain_logger import FloLangchainLogger +from langchain_openai import ChatOpenAI, OpenAIEmbeddings + +from dotenv import load_dotenv +load_dotenv() + +from langchain_community.tools.tavily_search.tool import TavilySearchResults +from flo_ai.common.flo_langchain_logger import FloLangchainLogger + +llm = ChatOpenAI(temperature=0, model_name='gpt-4o-mini') + +session = FloSession( + llm, + log_level="ERROR" +) + +from langchain.tools import BaseTool + +class PrintStateTool(BaseTool): + name = "printStateTool" + description = "Just print the state" + + def _run( + self, **kwargs + ) -> str: + return "Print tool call success" + +session.register_tool( + name="printStateTool", + tool=PrintStateTool() +) + +simple_tool_agent = """ +apiVersion: flo/alpha-v1 +kind: FloRoutedTeam +name: llm-assistant +team: + name: tool-to-print-state + router: + name: LinearRouter + kind: linear + agents: + - name: tool-to-print + kind: tool + tools: + - name: printStateTool +""" + +flo = Flo.build(session, simple_tool_agent) + +print(flo.invoke("Testing ....")) \ No newline at end of file diff --git a/flo_ai/common/flo_langchain_logger.py b/flo_ai/common/flo_langchain_logger.py index 022aee7a..b8a07c31 100644 --- a/flo_ai/common/flo_langchain_logger.py +++ b/flo_ai/common/flo_langchain_logger.py @@ -14,40 +14,40 @@ def __init__(self, self.session_id = session_id def on_llm_start(self, serialized: Dict[str, Any], prompts: List[str], **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onLLMStart: {prompts}") + self.logger.debug(f"Session ID: {self.session_id}: onLLMStart: {prompts}") def on_llm_new_token(self, token: str, **kwargs: Any) -> None: self.logger.debug(f"Session ID: {self.session_id}: onNewToken: {token}") def on_llm_end(self, response: LLMResult, **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onLLMEnd: {response.generations}") + self.logger.debug(f"Session ID: {self.session_id}: onLLMEnd: {response.generations}") def on_llm_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None: self.logger.error(f"Session ID: {self.session_id}: onLLMError: {error}") def on_chain_start(self, serialized: Dict[str, Any], inputs: Dict[str, Any], **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onChainStart: {inputs}") + self.logger.debug(f"Session ID: {self.session_id}: onChainStart: {inputs}") def on_chain_end(self, outputs: Dict[str, Any], **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onChainEnd: {outputs}") + self.logger.debug(f"Session ID: {self.session_id}: onChainEnd: {outputs}") def on_chain_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None: self.logger.error(f"Session ID: {self.session_id}: onChainError: {error}") def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onToolStart: {input_str}") + self.logger.debug(f"Session ID: {self.session_id}: onToolStart: {input_str}") def on_tool_end(self, output: str, **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onToolEnd: {output}") + self.logger.debug(f"Session ID: {self.session_id}: onToolEnd: {output}") def on_tool_error(self, error: Union[Exception, KeyboardInterrupt], **kwargs: Any) -> None: self.logger.error(f"Session ID: {self.session_id}: onToolError: {error}") def on_text(self, text: str, **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onText: {text}") + self.logger.debug(f"Session ID: {self.session_id}: onText: {text}") def on_agent_action(self, action: AgentAction, **kwargs: Any) -> Any: - self.logger.info(f"Session ID: {self.session_id}: onAgentAction: {action.tool} - {action.tool_input}") + self.logger.debug(f"Session ID: {self.session_id}: onAgentAction: {action.tool} - {action.tool_input}") def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> None: - self.logger.info(f"Session ID: {self.session_id}: onAgentFinish: {finish.return_values}") \ No newline at end of file + self.logger.debug(f"Session ID: {self.session_id}: onAgentFinish: {finish.return_values}") \ No newline at end of file diff --git a/flo_ai/constants/flo_node_contants.py b/flo_ai/constants/flo_node_contants.py new file mode 100644 index 00000000..6106d5c3 --- /dev/null +++ b/flo_ai/constants/flo_node_contants.py @@ -0,0 +1,2 @@ +INTERNAL_NODE_REFLECTION_MANAGER = "f/ReflectionManager" +INTERNAL_NODE_DELEGATION_MANAGER = "f/DelegationManager" \ No newline at end of file diff --git a/flo_ai/core.py b/flo_ai/core.py index 088f2d37..f857bae9 100644 --- a/flo_ai/core.py +++ b/flo_ai/core.py @@ -38,7 +38,8 @@ def build(session: FloSession, yaml: str, log_level: str = "INFO"): return Flo(session, to_supervised_team(yaml), log_level) def draw(self, xray=True): - return self.runnable.draw(xray) + from IPython.display import Image, display + return display(Image(self.runnable.draw(xray))) def draw_to_file(self, filename: str, xray=True): from PIL import Image as PILImage diff --git a/flo_ai/factory/agent_factory.py b/flo_ai/factory/agent_factory.py index 915c47fe..1820015e 100644 --- a/flo_ai/factory/agent_factory.py +++ b/flo_ai/factory/agent_factory.py @@ -3,6 +3,8 @@ from flo_ai.models.flo_agent import FloAgent from flo_ai.models.flo_llm_agent import FloLLMAgent from flo_ai.models.flo_reflection_agent import FloReflectionAgent +from flo_ai.models.flo_delegation_agent import FloDelegatorAgent +from flo_ai.models.flo_tool_agent import FloToolAgent from flo_ai.models.flo_executable import ExecutableFlo, ExecutableType from enum import Enum @@ -12,6 +14,7 @@ class AgentKinds(Enum): tool = "tool" function = "function" reflection = "reflection" + delegator = "delegator" class AgentFactory(): @@ -30,6 +33,8 @@ def create(session: FloSession, agent: AgentConfig): return AgentFactory.__create_runnable_agent(session, agent) case AgentKinds.reflection: return AgentFactory.__create_reflection_agent(session, agent) + case AgentKinds.delegator: + return AgentFactory.__create_delegator_agent(session, agent) return AgentFactory.__create_agentic_agent(session, agent, tool_map) @staticmethod @@ -51,8 +56,12 @@ def __create_llm_agent(session: FloSession, agent: AgentConfig) -> FloLLMAgent: @staticmethod def __create_runnable_agent(session: FloSession, agent: AgentConfig) -> FloLLMAgent: runnable = session.tools[agent.tools[0].name] - return ExecutableFlo(agent.name, runnable, ExecutableType.tool) + return FloToolAgent.Builder(session, agent, runnable).build() @staticmethod def __create_reflection_agent(session: FloSession, agent: AgentConfig) -> FloReflectionAgent: - return FloReflectionAgent.Builder(session, agent).build() \ No newline at end of file + return FloReflectionAgent.Builder(session, agent).build() + + @staticmethod + def __create_delegator_agent(session: FloSession, agent: AgentConfig) -> FloReflectionAgent: + return FloDelegatorAgent.Builder(session, agent).build() \ No newline at end of file diff --git a/flo_ai/models/flo_delegation_agent.py b/flo_ai/models/flo_delegation_agent.py new file mode 100644 index 00000000..bfdd2066 --- /dev/null +++ b/flo_ai/models/flo_delegation_agent.py @@ -0,0 +1,78 @@ +from langchain_core.runnables import Runnable +from flo_ai.yaml.config import AgentConfig +from flo_ai.state.flo_session import FloSession +from flo_ai.models.flo_executable import ExecutableFlo +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from flo_ai.models.flo_executable import ExecutableType +from langchain_core.output_parsers.openai_functions import JsonOutputFunctionsParser + + +class FloDelegatorAgent(ExecutableFlo): + + def __init__(self, + executor: Runnable, + config: AgentConfig) -> None: + super().__init__(config.name, executor, ExecutableType.delegator) + self.executor: Runnable = executor + self.config: AgentConfig = config + + + class Builder(): + def __init__(self, + session: FloSession, + agentConfig: AgentConfig) -> None: + self.config = agentConfig + delegator_base_system_message = ( + "You are a delegator tasked with routing a conversation between the" + " following {member_type}: {members}. Given the following rules," + " respond with the worker to act next " + ) + self.llm = session.llm + self.options = [x.name for x in agentConfig.to] + self.llm_router_prompt = ChatPromptTemplate.from_messages( + [ + ("system", delegator_base_system_message), + MessagesPlaceholder(variable_name="messages"), + ("system", "Rules: {delegator_rules}"), + ( + "system", + "Given the conversation above, who should act next?" + "Select one of: {options}", + ), + ] + ).partial( + options=str(self.options), + members=", ".join(self.options), + member_type="agents", + delegator_rules=agentConfig.job + ) + + def build(self): + function_def = { + "name": "route", + "description": "Select the next role.", + "parameters": { + "title": "routeSchema", + "type": "object", + "properties": { + "next": { + "title": "Next", + "anyOf": [ + {"enum": self.options}, + ], + } + }, + "required": ["next"], + } + } + + chain = ( + self.llm_router_prompt + | self.llm.bind_functions(functions=[function_def], function_call="route") + | JsonOutputFunctionsParser() + ) + + return FloDelegatorAgent(executor = chain, + config=self.config) + + \ No newline at end of file diff --git a/flo_ai/models/flo_executable.py b/flo_ai/models/flo_executable.py index e1381e42..508bdde2 100644 --- a/flo_ai/models/flo_executable.py +++ b/flo_ai/models/flo_executable.py @@ -2,12 +2,14 @@ from langchain_core.runnables import Runnable from langchain_core.messages import HumanMessage from enum import Enum +from flo_ai.state.flo_state import STATE_NAME_MESSAGES class ExecutableType(Enum): agentic = "agentic" llm = "llm" tool = "tool" reflection = "reflection" + delegator = "delegator" @staticmethod def isAgent(type: 'ExecutableType'): @@ -30,14 +32,18 @@ def __init__(self, def stream(self, work, config = None): return self.runnable.stream({ - "messages": [ + STATE_NAME_MESSAGES: [ HumanMessage(content=work) ] }, config) def invoke(self, work, config = None): return self.runnable.invoke({ - "messages": [ + STATE_NAME_MESSAGES: [ HumanMessage(content=work) - ] - }, config) \ No newline at end of file + ], + }, config) + + + def draw(self, xray=True): + return self.runnable.get_graph().draw_mermaid_png() \ No newline at end of file diff --git a/flo_ai/models/flo_node.py b/flo_ai/models/flo_node.py index 5410ff22..ea302f11 100644 --- a/flo_ai/models/flo_node.py +++ b/flo_ai/models/flo_node.py @@ -2,7 +2,7 @@ from flo_ai.models.flo_agent import FloAgent from flo_ai.models.flo_routed_team import FloRoutedTeam from langchain.agents import AgentExecutor -from flo_ai.state.flo_state import TeamFloAgentState +from flo_ai.state.flo_state import TeamFloAgentState, STATE_NAME_MESSAGES from langchain_core.messages import HumanMessage from flo_ai.yaml.config import AgentConfig, TeamConfig from flo_ai.models.flo_executable import ExecutableType @@ -37,20 +37,20 @@ def __teamflo_agent_node(state: TeamFloAgentState, agent: AgentExecutor, name: s result = agent.invoke(state) # TODO see how to fix this output = result if isinstance(result, str) else result["output"] - return { "messages": [HumanMessage(content=output, name=name)] } + return { STATE_NAME_MESSAGES: [HumanMessage(content=output, name=name)] } @staticmethod def __get_last_message(state: TeamFloAgentState) -> str: - return state["messages"][-1].content + return state[STATE_NAME_MESSAGES][-1].content @staticmethod def __join_graph(response: dict): - return { "messages": [ response["messages"][-1] ] } + return { STATE_NAME_MESSAGES: [ response[STATE_NAME_MESSAGES][-1] ] } @staticmethod def __teamflo_team_node(message: str, members: list[str]): results = { - "messages": [HumanMessage(content=message)], + STATE_NAME_MESSAGES: [HumanMessage(content=message)], "team_members": ", ".join(members), } return results diff --git a/flo_ai/models/flo_reflection_agent.py b/flo_ai/models/flo_reflection_agent.py index c8a45ee8..2d531581 100644 --- a/flo_ai/models/flo_reflection_agent.py +++ b/flo_ai/models/flo_reflection_agent.py @@ -9,8 +9,7 @@ from flo_ai.models.flo_executable import ExecutableType from langchain_core.output_parsers import StrOutputParser -# TODO not ready, lot of fixes to do -# can be merged into LLM Agent or made into a child + class FloReflectionAgent(ExecutableFlo): def __init__(self, executor: Runnable, config: AgentConfig) -> None: diff --git a/flo_ai/models/flo_routed_team.py b/flo_ai/models/flo_routed_team.py index 6531162f..552258b7 100644 --- a/flo_ai/models/flo_routed_team.py +++ b/flo_ai/models/flo_routed_team.py @@ -8,5 +8,6 @@ def __init__(self, name: str, graph: CompiledGraph, config: TeamConfig) -> None: super().__init__(name, graph) self.config = config + # Overridden for xray use, doesnt work in base class def draw(self, xray=True): return self.runnable.get_graph(xray=xray).draw_mermaid_png() \ No newline at end of file diff --git a/flo_ai/models/flo_tool_agent.py b/flo_ai/models/flo_tool_agent.py new file mode 100644 index 00000000..365e2452 --- /dev/null +++ b/flo_ai/models/flo_tool_agent.py @@ -0,0 +1,28 @@ +from langchain_core.runnables import Runnable +from flo_ai.helpers.utils import randomize_name +from flo_ai.models.flo_executable import ExecutableFlo +from flo_ai.state.flo_session import FloSession +from flo_ai.yaml.config import AgentConfig +from flo_ai.models.flo_executable import ExecutableType + +class FloToolAgent(ExecutableFlo): + + def __init__(self, + executor: Runnable, + config: AgentConfig) -> None: + super().__init__(config.name, executor, ExecutableType.tool) + self.executor: Runnable = executor + self.config: AgentConfig = config + + class Builder: + def __init__(self, + session: FloSession, + config: AgentConfig, + tool_runnable: Runnable) -> None: + self.name: str = config.name + self.runnable = tool_runnable + self.config = config + + + def build(self) -> Runnable: + return FloToolAgent(self.runnable, self.config) diff --git a/flo_ai/retrievers/flo_retriever.py b/flo_ai/retrievers/flo_retriever.py index dbd2d3b0..57b22f89 100644 --- a/flo_ai/retrievers/flo_retriever.py +++ b/flo_ai/retrievers/flo_retriever.py @@ -3,7 +3,7 @@ from flo_ai.state.flo_session import FloSession from langchain.schema.output_parser import StrOutputParser from langchain.schema.runnable import RunnablePassthrough -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder, BasePromptTemplate from flo_ai.retrievers.flo_multi_query import FloMultiQueryRetriverBuilder from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import DocumentCompressorPipeline @@ -12,9 +12,45 @@ from functools import partial from langchain.pydantic_v1 import BaseModel, Field from langchain_core.tools import Tool +from typing import Optional +from langchain_core.callbacks import Callbacks +from langchain_core.prompts import ( + BasePromptTemplate, + PromptTemplate, + aformat_document, + format_document, +) +from typing import List + +class FloRagBaseMessage(BaseModel): + content: str class FloRagToolInput(BaseModel): - query: str = Field(description="query to look up in retriever") + messages: List[FloRagBaseMessage] = Field(description="query to look up in the vector store") + +def _get_relevant_documents( + messages: List[FloRagBaseMessage], + retriever: VectorStoreRetriever, + document_prompt: BasePromptTemplate, + document_separator: str, + callbacks: Callbacks = None, +) -> str: + docs = retriever.invoke(messages[-1].content, config={"callbacks": callbacks}) + return document_separator.join( + format_document(doc, document_prompt) for doc in docs + ) + +async def _aget_relevant_documents( + messages: List[str], + retriever: VectorStoreRetriever, + document_prompt: BasePromptTemplate, + document_separator: str, + callbacks: Callbacks = None +) -> str: + docs = await retriever.ainvoke(messages[-1].content, config={"callbacks": callbacks}) + return document_separator.join( + [await aformat_document(doc, document_prompt) for doc in docs] + ) class FloRagBuilder(): def __init__(self, @@ -113,20 +149,54 @@ def build_rag(self): return self.__build_history_aware_rag() def build_retriever_tool(self, name, description): - return create_retriever_tool(self.retriever, name, description) + return self.__create_retriever_tool(self.retriever, name, description) @staticmethod - def __get_rag_answer(query: str, runnable: Runnable): - result = runnable.invoke({ "question": query }) + def __get_rag_answer(messages: List[FloRagBaseMessage], runnable: Runnable): + question = messages[-1].content + chat_history = messages[:-1] + result = runnable.invoke({ "question": question, "chat_history": chat_history }) return result["answer"].content @staticmethod - async def __aget_rag_answer(query: str, runnable: Runnable): - result = await runnable.ainvoke({ "question": query }) + async def __aget_rag_answer(messages: List[FloRagBaseMessage], runnable: Runnable): + question = messages[-1].content + chat_history = messages[:-1] + result = await runnable.ainvoke({ "question": question, "chat_history": chat_history }) return result["answer"].content + + def __create_retriever_tool( + self, + retriever: VectorStoreRetriever, + name: str, + description: str, + *, + document_prompt: Optional[BasePromptTemplate] = None, + document_separator: str = "\n", + ) -> Tool: + document_prompt = document_prompt or PromptTemplate.from_template("{page_content}") + func = partial( + _get_relevant_documents, + retriever=retriever, + document_prompt=document_prompt, + document_separator=document_separator, + ) + afunc = partial( + _aget_relevant_documents, + retriever=retriever, + document_prompt=document_prompt, + document_separator=document_separator, + ) + return Tool( + name=name, + description=description, + func=func, + coroutine=afunc, + args_schema=FloRagToolInput, + ) @staticmethod - def __create_flo_rag_tool( + def __create_rag_tool( runnable_rag: Runnable, name: str, description: str @@ -151,4 +221,4 @@ def __create_flo_rag_tool( def build_rag_tool(self, name, description) -> Tool: rag = self.__build_history_aware_rag() - return FloRagBuilder.__create_flo_rag_tool(rag, name, description) \ No newline at end of file + return FloRagBuilder.__create_rag_tool(rag, name, description) \ No newline at end of file diff --git a/flo_ai/router/flo_linear.py b/flo_ai/router/flo_linear.py index 957805f4..49add530 100644 --- a/flo_ai/router/flo_linear.py +++ b/flo_ai/router/flo_linear.py @@ -34,12 +34,16 @@ def build_agent_graph(self): if (flo_agent_nodes[i].kind == ExecutableType.reflection): self.add_reflection_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1]) else: - if (flo_agent_nodes[i+1].kind != ExecutableType.reflection): - workflow.add_edge(agent1_name, agent2_name) + if (flo_agent_nodes[i+1].kind != ExecutableType.reflection and flo_agent_nodes[i].kind != ExecutableType.delegator): + if flo_agent_nodes[i+1].kind == ExecutableType.delegator: + self.add_delegation_edge(workflow, flo_agent_nodes[i], flo_agent_nodes[i+1], flo_agent_nodes[i+2] if (i+2) FloNode: + if (flo_agent.type == ExecutableType.delegator): + return FloNode(flo_agent.executor, flo_agent.name, flo_agent.type, flo_agent.config) node_builder = FloNode.Builder() return node_builder.build_from_agent(flo_agent) @@ -62,10 +66,10 @@ def build_node_for_teams(self, flo_team: FloRoutedTeam): def update_reflection_state(self, state: TeamFloAgentState, reflection_agent_name: str): tracker = None - if "reflection_tracker" not in state or state["reflection_tracker"] is None: + if STATE_NAME_LOOP_CONTROLLER not in state or state[STATE_NAME_LOOP_CONTROLLER] is None: tracker = dict() else: - tracker = state["reflection_tracker"] + tracker = state[STATE_NAME_LOOP_CONTROLLER] if reflection_agent_name in tracker: tracker[reflection_agent_name] += 1 @@ -73,30 +77,70 @@ def update_reflection_state(self, state: TeamFloAgentState, reflection_agent_nam tracker[reflection_agent_name] = 1 return { - "reflection_tracker": tracker + STATE_NAME_LOOP_CONTROLLER: tracker } + def add_delegation_edge(self, workflow: StateGraph, parent: FloNode, delegation_node: FloNode, nextNode: Union[FloNode|str]): + to_agent_names = [x.name for x in delegation_node.config.to] + delegation_node_name = delegation_node.name + next_node_name = nextNode if isinstance(nextNode, str) else nextNode.name + retry = delegation_node.config.retry or 1 + + conditional_map = {} + for agent_name in to_agent_names: + conditional_map[agent_name] = agent_name + conditional_map[next_node_name] = next_node_name + + workflow.add_node( + INTERNAL_NODE_DELEGATION_MANAGER, + functools.partial( + self.update_reflection_state, + reflection_agent_name=delegation_node_name + ) + ) + + workflow.add_edge(parent.name, INTERNAL_NODE_DELEGATION_MANAGER) + workflow.add_conditional_edges( + INTERNAL_NODE_DELEGATION_MANAGER, + self.__get_refelection_routing_fn(retry, delegation_node_name, next_node_name), + { delegation_node_name: delegation_node_name, next_node_name: next_node_name} + ) + + workflow.add_conditional_edges( + delegation_node_name, + FloRouter.__get_delegation_router_fn(next_node_name), + conditional_map + ) + + @staticmethod + def __get_delegation_router_fn(nextNode: str): + def delegation_router(state: TeamFloAgentState): + if STATE_NAME_NEXT not in state: + return nextNode + return state[STATE_NAME_NEXT] + return delegation_router + def add_reflection_edge(self, workflow: StateGraph, reflection_node: FloNode, nextNode: Union[FloNode | str]): - to_agent_name = reflection_node.config.to + to_agent_name = reflection_node.config.to[0].name retry = reflection_node.config.retry or 1 reflection_agent_name = reflection_node.name next = nextNode if isinstance(nextNode, str) else nextNode.name - workflow.add_node("rf/ReflectionManager", functools.partial(self.update_reflection_state, reflection_agent_name=reflection_agent_name)) - workflow.add_edge(to_agent_name, "rf/ReflectionManager") + workflow.add_node(INTERNAL_NODE_REFLECTION_MANAGER, functools.partial(self.update_reflection_state, reflection_agent_name=reflection_agent_name)) + workflow.add_edge(to_agent_name, INTERNAL_NODE_REFLECTION_MANAGER) workflow.add_conditional_edges( - "rf/ReflectionManager", + INTERNAL_NODE_REFLECTION_MANAGER, self.__get_refelection_routing_fn(retry, reflection_agent_name, next), { reflection_agent_name: reflection_agent_name, next: next } ) workflow.add_edge(reflection_agent_name, to_agent_name) @staticmethod - def __get_refelection_routing_fn(retries: int, reflection_agent_name, next): + def __get_refelection_routing_fn(retries: int, reflection_agent_name, next_node_name): def reflection_routing_fn(state: TeamFloAgentState): - tracker = state["reflection_tracker"] + tracker = state[STATE_NAME_LOOP_CONTROLLER] if tracker is not None and reflection_agent_name in tracker and tracker[reflection_agent_name] > retries: - return next + return next_node_name return reflection_agent_name return reflection_routing_fn diff --git a/flo_ai/state/flo_state.py b/flo_ai/state/flo_state.py index 74f611a0..9e35b7e3 100644 --- a/flo_ai/state/flo_state.py +++ b/flo_ai/state/flo_state.py @@ -4,6 +4,10 @@ import operator +STATE_NAME_LOOP_CONTROLLER = "loop_tracker" +STATE_NAME_NEXT = "next" +STATE_NAME_MESSAGES = "messages" + # The agent state is the input to each node in the graph class TeamFloAgentState(TypedDict): # The annotation tells the graph that new messages will always @@ -12,7 +16,7 @@ class TeamFloAgentState(TypedDict): # The 'next' field indicates where to route to next next: str # used for reflection agents - reflection_tracker: dict + loop_tracker: dict class TeamFloAgentStateWithPlan(TypedDict): input: str diff --git a/flo_ai/tools/flo_pdf_rag_tool.py b/flo_ai/tools/flo_pdf_rag_tool.py deleted file mode 100644 index 412ff194..00000000 --- a/flo_ai/tools/flo_pdf_rag_tool.py +++ /dev/null @@ -1,17 +0,0 @@ -from langchain.tools import Tool -from langchain_core.runnables import Runnable -from flo_ai.retrievers.flo_retriever import FloRagBuilder -from flo_ai.state.flo_session import FloSession -from flo_ai.retrievers.flo_compression_pipeline import FloCompressionPipeline -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -from langchain_core.vectorstores import VectorStoreRetriever -from flo_ai.tools.flo_rag_retriver_tool import FloRagRetrieverTool - -class FloPDFRagTool(): - - def __init__(self) -> None: - raise ValueError("You are supposed to use Builder, FloRagRetriverTool.Builder()") - - class Builder(FloRagRetrieverTool.Builder): - def __init__(self) -> None: - super().__init__() \ No newline at end of file diff --git a/flo_ai/tools/flo_rag_retriver_tool.py b/flo_ai/tools/flo_rag_retriver_tool.py deleted file mode 100644 index ddadbe51..00000000 --- a/flo_ai/tools/flo_rag_retriver_tool.py +++ /dev/null @@ -1,57 +0,0 @@ -from langchain.tools import Tool -from langchain_core.runnables import Runnable -from flo_ai.retrievers.flo_retriever import FloRagBuilder -from flo_ai.state.flo_session import FloSession -from flo_ai.retrievers.flo_compression_pipeline import FloCompressionPipeline -from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -from langchain_core.vectorstores import VectorStoreRetriever - -class FloRagRetrieverTool(): - - def __init__(self) -> None: - raise ValueError("You are supposed to use Builder, FloRagRetriverTool.Builder()") - - class Builder(): - - def __init__(self, - session: FloSession, - retriever: VectorStoreRetriever) -> None: - self.session = session - self.retriver = retriever - self.multiquery = False - self.custom_prompt: ChatPromptTemplate = None - self.extraction_pipeline: FloCompressionPipeline = None - - def add_custom_prompt(self, custom_prompt: ChatPromptTemplate): - self.custom_prompt = custom_prompt - - def add_custom_system_prompt(self, custom_system_prompt: str): - system_prompt = custom_system_prompt if custom_system_prompt is not None else """You are an assistant for question-answering tasks. - Use the following pieces of retrieved context to answer the question. - If you don't know the answer, just say that you don't know. - Use three sentences maximum and keep the answer concise.""" - self.custom_prompt = ChatPromptTemplate.from_messages( - [ - ("system", system_prompt), - MessagesPlaceholder(variable_name="chat_history"), - ("human", "{question}"), - ] - ) - - def add_extraction_pipeline(self, pipeline: FloCompressionPipeline): - self.extraction_pipeline = pipeline - - def enable_multi_query(self): - self.multiquery = True - - def build(self, name: str, description: str) -> Tool: - rag_builder = FloRagBuilder(self.session, self.retriver) - if self.custom_prompt is not None: - rag_builder.with_prompt(self.custom_prompt) - if self.multiquery: - rag_builder.with_multi_query() - if self.extraction_pipeline is not None: - rag_builder.with_compression(self.extraction_pipeline) - return rag_builder.build_rag_tool(name=name, description=description) - - diff --git a/flo_ai/yaml/config.py b/flo_ai/yaml/config.py index 159bd47e..06d30faf 100644 --- a/flo_ai/yaml/config.py +++ b/flo_ai/yaml/config.py @@ -35,13 +35,16 @@ class PromptStrategy(BaseModel): retries: int next: str | None = None +class MemberKey(BaseModel): + name: str + class AgentConfig(BaseModel): name: str role: Optional[str] = None kind: Optional[str] = None job: Optional[str] = None tools: List[ToolConfig] = [] - to: Optional[str] = None + to: Optional[List[MemberKey]] = None retry: Optional[int] = 1 class EdgeConfig(BaseModel):