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:
parent
b898d8a652
commit
8681c56cdc
11 changed files with 438 additions and 56 deletions
|
|
@ -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}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue