feat: Enhance backend with context management, error handling, and refactored code (#4286)

* Add cycle detection and management for graph vertices in run manager

* Refactor: Move AIMLEmbeddingsImpl to a new module path

* Add AIMLEmbeddingsImpl class for document and query embeddings using AIML API

* Add agent components for action routing, decision-making, execution, and context management

- Introduced `AgentActionRouter` to route agent flow based on action type.
- Added `DecideActionComponent` for determining actions from context and prompts.
- Implemented `ExecuteActionComponent` to execute actions using available tools.
- Created `GenerateThoughtComponent` for generating thoughts based on context.
- Developed `ProvideFinalAnswerComponent` to generate final answers from context.
- Built `AgentContextBuilder` for constructing `AgentContext` instances.
- Added `ObserveResultComponent` to process and observe action results.
- Implemented `CheckTerminationComponent` to determine if the agent should continue or terminate.

* Add AgentContext class for managing agent state and context serialization

- Introduced `AgentContext` class in `context.py` to handle agent state, including tools, language model, and context history.
- Implemented serialization methods for converting agent context to JSON-compatible format.
- Added validation for language model instances to ensure compatibility.
- Provided methods for updating and retrieving full context, including context history management.

* Add new agent components to the langflow module's init file

* Update `apply_on_outputs` to use `_outputs_map` in vertex base class

* Add _pre_run_setup method to custom component for pre-execution setup

* Handle non-list action types in decide_action method

* Enhance AgentActionRouter with iteration control and context routing logic

* Fix incorrect variable usage in tool call result message formatting

* Add AgentActionRouter to module exports in agents package

* Refactor cycle detection logic in graph base class

* Add test for complex agent flow with cyclic graph validation

* Enhance readiness checks in tracing service methods

* Add context management to Graph class with dotdict support

* Add context management methods to custom component class

- Introduced a `_ctx` attribute to store context data.
- Added `ctx` property to access the graph's context, raising an error if the graph is not built.
- Implemented `add_to_ctx` method to add key-value pairs to the context with an optional overwrite flag.
- Implemented `update_ctx` method to update the context with a dictionary of values, ensuring the graph is built and the input is a dictionary.

* Add customizable Agent component with input/output handling and action routing

* Handle non-list 'tools' attribute in 'build_context' method

* Convert `get_response` method to asynchronous and update graph processing to use async iteration.

* Add async test for Agent component in graph cycle tests

* Refactor Agent Flow JSON: Simplify input types and update agent component structure

- Removed "BaseTool" from input types for "ToolCallingAgent" to streamline tool handling.
- Updated agent component to a more modular structure with new prompts and input configurations.
- Replaced deprecated methods and fields with updated implementations for improved functionality.
- Adjusted metadata and configuration settings for better clarity and usability.

* [autofix.ci] apply automated fixes

* Add Agent import to init, improve error handling, and clean up imports

- Added `Agent` import to `__init__.py` for better module accessibility.
- Improved error handling in `aiml_embeddings.py` by raising a `ValueError` when the expected embedding count is not met.
- Cleaned up redundant imports in `test_cycles.py` to enhance code readability.

* Refactor agent component imports for improved modularity and organization

* Remove agent components and update `__init__.py` exports

* Add iteration control and default route options to ConditionalRouter component

* Refactor graph tests to include new components and update iteration logic

- Replaced complex agent flow with a simplified guessing game using OpenAI components and conditional routing.
- Introduced `TextInputComponent` and updated `ChatInput` initialization.
- Added new test `test_conditional_router_max_iterations` to validate conditional routing with max iterations.
- Updated graph cyclicity assertions and snapshot checks for improved test coverage.
- Removed deprecated agent components and related logic.

* Refactor conditional router to return message consistently and use iterate_and_stop_once method

* Add return type annotations to methods in langsmith.py

* Remove unnecessary `@override` decorator and add `# noqa: ARG002` comments for unused arguments

* Move ChatInput import inside flow_component fixture in conftest.py

* Update test to use _outputs_map for cycle outputs retrieval

* Refactor `iterate_and_stop_once` to remove redundant `_id` variable usage

* Add default route to ConditionalRouterComponent in cycle test

* Implement synchronous graph execution using threading and queues

- Removed `nest_asyncio` dependency and replaced it with a new threading-based approach for synchronous graph execution.
- Introduced a `queue.Queue` to handle results and exceptions between threads.
- Added a new thread to run asynchronous code, ensuring proper event loop management and task completion.
- Updated methods to return sorted lists of runnable vertices for consistency.

* Update import path for ModelConstants in test_model_constants.py

* [autofix.ci] apply automated fixes

* fix: add property decorator

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: italojohnny <italojohnnydosanjos@gmail.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-11-08 17:28:30 -03:00 • committed by GitHub
commit 8681c56cdc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 438 additions and 56 deletions

View file

@ -2,6 +2,7 @@ import os
import pytest
from langflow.components.inputs import ChatInput
from langflow.components.inputs.text import TextInputComponent
from langflow.components.models import OpenAIModelComponent
from langflow.components.outputs import ChatOutput, TextOutputComponent
from langflow.components.prompts import PromptComponent
@ -31,7 +32,7 @@ class Concatenate(Component):
@pytest.mark.skip(reason="Temporarily disabled")
def test_cycle_in_graph():
chat_input = ChatInput(_id="chat_input")
router = ConditionalRouterComponent(_id="router")
router = ConditionalRouterComponent(_id="router", default_route="true_result")
chat_input.set(input_value=router.false_response)
concat_component = Concatenate(_id="concatenate")
concat_component.set(text=chat_input.message_response)
@ -59,7 +60,6 @@ def test_cycle_in_graph():
snapshots.append(graph._snapshot())
results.append(result)
results_ids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert results_ids[-2:] == ["text_output", "chat_output"]
assert len(results_ids) > len(graph.vertices), snapshots
# Check that chat_output and text_output are the last vertices in the results
assert results_ids == [
@ -127,7 +127,9 @@ def test_that_outputs_cache_is_set_to_false_in_cycle():
graph = Graph(chat_input, chat_output)
cycle_vertices = find_cycle_vertices(graph._get_edges_as_list_of_tuples())
cycle_outputs_lists = [graph.vertex_map[vertex_id].custom_component.outputs for vertex_id in cycle_vertices]
cycle_outputs_lists = [
graph.vertex_map[vertex_id].custom_component._outputs_map.values() for vertex_id in cycle_vertices
]
cycle_outputs = [output for outputs in cycle_outputs_lists for output in outputs]
for output in cycle_outputs:
assert output.cache is False
@ -206,3 +208,119 @@ def test_updated_graph_with_prompts():
# Extract the vertex IDs for analysis
results_ids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert "chat_output_1" in results_ids, f"Expected outputs not in results: {results_ids}"
@pytest.mark.api_key_required
def test_updated_graph_with_max_iterations():
# Chat input initialization
chat_input = ChatInput(_id="chat_input").set(input_value="bacon")
# First prompt: Guessing game with hints
prompt_component_1 = PromptComponent(_id="prompt_component_1").set(
template="Try to guess a word. I will give you hints if you get it wrong.\n"
"Hint: {hint}\n"
"Last try: {last_try}\n"
"Answer:",
)
# First OpenAI LLM component (Processes the guessing prompt)
openai_component_1 = OpenAIModelComponent(_id="openai_1").set(
input_value=prompt_component_1.build_prompt, api_key=os.getenv("OPENAI_API_KEY")
)
# Conditional router based on agent response
router = ConditionalRouterComponent(_id="router").set(
input_text=openai_component_1.text_response,
match_text=chat_input.message_response,
operator="contains",
message=openai_component_1.text_response,
)
# Second prompt: After the last try, provide a new hint
prompt_component_2 = PromptComponent(_id="prompt_component_2")
prompt_component_2.set(
template="Given the following word and the following last try. Give the guesser a new hint.\n"
"Last try: {last_try}\n"
"Word: {word}\n"
"Hint:",
word=chat_input.message_response,
last_try=router.false_response,
)
# Second OpenAI component (handles the router's response)
openai_component_2 = OpenAIModelComponent(_id="openai_2")
openai_component_2.set(input_value=prompt_component_2.build_prompt, api_key=os.getenv("OPENAI_API_KEY"))
prompt_component_1.set(hint=openai_component_2.text_response, last_try=router.false_response)
# chat output for the final OpenAI response
chat_output_1 = ChatOutput(_id="chat_output_1")
chat_output_1.set(input_value=router.true_response)
# Build the graph without concatenate
graph = Graph(chat_input, chat_output_1)
# Assertions for graph cyclicity and correctness
assert graph.is_cyclic is True, "Graph should contain cycles."
# Run and validate the execution of the graph
results = []
max_iterations = 20
snapshots = [graph.get_snapshot()]
for result in graph.start(max_iterations=max_iterations, config={"output": {"cache": False}}):
snapshots.append(graph.get_snapshot())
results.append(result)
assert len(snapshots) > 2, "Graph should have more than one snapshot"
# Extract the vertex IDs for analysis
results_ids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert "chat_output_1" in results_ids, f"Expected outputs not in results: {results_ids}"
def test_conditional_router_max_iterations():
# Chat input initialization
text_input = TextInputComponent(_id="text_input")
# Conditional router setup with a condition that will never match
router = ConditionalRouterComponent(_id="router").set(
input_text=text_input.text_response,
match_text="bacon",
operator="equals",
message="This message should not be routed to true_result",
max_iterations=5,
default_route="true_result",
)
# Chat output for the true route
text_input.set(input_value=router.false_response)
# Chat output for the false route
chat_output_false = ChatOutput(_id="chat_output_false")
chat_output_false.set(input_value=router.true_response)
# Build the graph
graph = Graph(text_input, chat_output_false)
# Assertions for graph cyclicity and correctness
assert graph.is_cyclic is True, "Graph should contain cycles."
# Run and validate the execution of the graph
results = []
snapshots = [graph.get_snapshot()]
previous_iteration = graph.context.get("router_iteration", 0)
for result in graph.start(max_iterations=20, config={"output": {"cache": False}}):
snapshots.append(graph.get_snapshot())
results.append(result)
if hasattr(result, "vertex") and result.vertex.id == "router":
current_iteration = graph.context.get("router_iteration", 0)
assert current_iteration == previous_iteration + 1, "Iteration should increment by 1"
previous_iteration = current_iteration
# Check if the max_iterations logic is working
router_id = router._id.lower()
assert graph.context.get(f"{router_id}_iteration", 0) == 5, "Router should stop after max_iterations"
# Extract the vertex IDs for analysis
results_ids = [result.vertex.id for result in results if hasattr(result, "vertex")]
assert "chat_output_false" in results_ids, f"Expected outputs not in results: {results_ids}"