Skip to content

Commit

Permalink
Fix issues with external agent workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
ciprianjichici committed Dec 19, 2024
1 parent a6701b8 commit 06323c9
Show file tree
Hide file tree
Showing 22 changed files with 310 additions and 75 deletions.
30 changes: 29 additions & 1 deletion docs/release-notes/breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
> [!NOTE]
> This section is for changes that are not yet released but will affect future releases.
## Starting with 0.9.1-rc103
## Starting with 0.9.1-rc106

### Configuration changes

The following new App Configuration settings are required:

|Name | Default value | Description |
|--- | --- | --- |
|`FoundationaLLM:PythonSDK:Logging:LogLevel:Azure` | `Warning` | Provides the default level of logging for Azure modules in the Python SDK. |

### Agent workflow configuration changes

Agent resource configuration files that have a `workflow` property now requires a `name` and `package_name` property. This is to support loading external workflows via plugins. For internal workflows, the `package_name` should be set to `FoundationaLLM`. Example below truncated for brevity.

```json
Expand All @@ -22,6 +30,26 @@ Agent resource configuration files that have a `workflow` property now requires
}
```

A new `Workflow` resource must be added to the `FoundationaLLM.Agent` resource provider:

```json
{
"type": "external-agent-workflow",
"name": "ExternalAgentWorkflow",
"object_id": "/instances/<instance_id>/providers/FoundationaLLM.Agent/workflows/ExternalAgentWorkflow",
"display_name": "ExternalAgentWorkflow",
"description": "External Agent workflow",
"cost_center": null,
"properties": null,
"created_on": "2024-11-13T18:12:07.0223039+00:00",
"updated_on": "0001-01-01T00:00:00+00:00",
"created_by": "[email protected]",
"updated_by": null,
"deleted": false,
"expiration_date": null
}
```

## Starting with 0.9.1-rc102

### Configuration changes
Expand Down
8 changes: 8 additions & 0 deletions src/dotnet/Common/Constants/Data/AppConfiguration.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@
"content_type": "",
"first_version": "0.9.0"
},
{
"name": "Logging:LogLevel:Azure",
"description": "The default logging level used by the Python SDK.",
"secret": "",
"value": "Warning",
"content_type": "",
"first_version": "0.9.1"
},
{
"name": "Logging:EnableConsoleLogging",
"description": "The flag indicating whether the Python SDK sends logs to the console or not.",
Expand Down
7 changes: 7 additions & 0 deletions src/dotnet/Common/Templates/AppConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ public static class AppConfigurationKeys
public const string FoundationaLLM_PythonSDK_Logging_LogLevel_Default =
"FoundationaLLM:PythonSDK:Logging:LogLevel:Default";

/// <summary>
/// The app configuration key for the FoundationaLLM:PythonSDK:Logging:LogLevel:Azure setting.
/// <para>Value description:<br/>The default logging level used by the Python SDK.</para>
/// </summary>
public const string FoundationaLLM_PythonSDK_Logging_LogLevel_Azure =
"FoundationaLLM:PythonSDK:Logging:LogLevel:Azure";

/// <summary>
/// The app configuration key for the FoundationaLLM:PythonSDK:Logging:EnableConsoleLogging setting.
/// <para>Value description:<br/>The flag indicating whether the Python SDK sends logs to the console or not.</para>
Expand Down
7 changes: 7 additions & 0 deletions src/dotnet/Common/Templates/appconfig.template.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
"content_type": "",
"tags": {}
},
{
"key": "FoundationaLLM:PythonSDK:Logging:LogLevel:Azure",
"value": "Warning",
"label": null,
"content_type": "",
"tags": {}
},
{
"key": "FoundationaLLM:PythonSDK:Logging:EnableConsoleLogging",
"value": "false",
Expand Down
10 changes: 9 additions & 1 deletion src/dotnet/Orchestration/Orchestration/OrchestrationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,15 @@ await cosmosDBService.PatchOperationsItemPropertiesAsync<LongRunningOperationCon
}
break;
case AgentResourceTypeNames.Workflows:
agentWorkflow.Name = resourcePath.MainResourceId!;

var retrievedWorkflow = await agentResourceProvider.GetResourceAsync<Workflow>(
resourceObjectId.ObjectId,
currentUserIdentity);

explodedObjectsManager.TryAdd(
retrievedWorkflow.ObjectId!,
retrievedWorkflow);

break;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .skunkworks_tool_plugin_manager import SkunkworksToolPluginManager
from .skunkworks_workflow_plugin_manager import SkunkworksWorkflowPluginManager
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class SkunkworksToolPluginManager(ToolPluginManagerBase):
FOUNDATIONALLM_CODE_INTERPRETER_TOOL_NAME = 'FoundationaLLMCodeInterpreterTool'

def __init__(self):
pass
super().__init__()

def create_tool(self,
tool_config: AgentTool,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import List

from foundationallm.config import Configuration, UserIdentity
from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow
from foundationallm.langchain.common import FoundationaLLMWorkflowBase
from foundationallm.plugins import WorkflowPluginManagerBase

from skunkworks_foundationallm.workflows import (
FoundationaLLMRouterWorkflow
)

class SkunkworksWorkflowPluginManager(WorkflowPluginManagerBase):

FOUNDATIONALLM_ROUTER_WORKFLOW_NAME = 'FoundationaLLMRouterWorkflow'

def __init__(self):
super().__init__()

def create_workflow(
self,
workflow_config: ExternalAgentWorkflow,
objects: dict,
tools: List[AgentTool],
user_identity: UserIdentity,
config: Configuration) -> FoundationaLLMWorkflowBase:
"""
Create a workflow instance based on the given configuration and tools.
Parameters
----------
workflow_config : ExternalAgentWorkflow
The workflow assigned to the agent.
objects : dict
The exploded objects assigned from the agent.
tools : List[AgentTool]
The tools assigned to the agent.
user_identity : UserIdentity
The user identity of the user initiating the request.
config : Configuration
The application configuration for FoundationaLLM.
"""
match workflow_config.name:
case SkunkworksWorkflowPluginManager.FOUNDATIONALLM_ROUTER_WORKFLOW_NAME:
return FoundationaLLMRouterWorkflow(workflow_config, objects, tools, user_identity, config)
case _:
raise ValueError(f"Unknown tool name: {workflow_config.name}")

def refresh_tools():
print('Refreshing tools...')
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .foundationallm_router_workflow import FoundationaLLMRouterWorkflow
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Class: FoundationaLLMWorkflowBase
Description: FoundationaLLM base class for tools that uses the agent workflow model for its configuration.
"""
from abc import ABC, abstractmethod
from azure.identity import DefaultAzureCredential
from langchain_core.messages import BaseMessage
from pydantic import BaseModel
from typing import List
from foundationallm.config import Configuration, UserIdentity
from foundationallm.langchain.common import FoundationaLLMWorkflowBase
from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow
from foundationallm.models.constants import AgentCapabilityCategories
from foundationallm.models.orchestration import CompletionResponse, OpenAITextMessageContentItem
from foundationallm.telemetry import Telemetry

class FoundationaLLMRouterWorkflow(FoundationaLLMWorkflowBase):
"""
FoundationaLLM base class for workflows that uses the agent workflow model for its configuration.
"""
def __init__(self,
workflow_config: ExternalAgentWorkflow,
objects: dict,
tools: List[AgentTool],
user_identity: UserIdentity,
config: Configuration):
"""
Initializes the FoundationaLLMWorkflowBase class with the workflow configuration.
Parameters
----------
workflow_config : ExternalAgentWorkflow
The workflow assigned to the agent.
objects : dict
The exploded objects assigned from the agent.
tools : List[AgentTool]
The tools assigned to the agent.
user_identity : UserIdentity
The user identity of the user initiating the request.
config : Configuration
The application configuration for FoundationaLLM.
"""
super().__init__(workflow_config, objects, tools, user_identity, config)

async def invoke_async(self,
operation_id: str,
user_prompt:str,
message_history: List[BaseMessage])-> CompletionResponse:
"""
Invokes the workflow asynchronously.
Parameters
----------
operation_id : str
The unique identifier of the FoundationaLLM operation.
user_prompt : str
The user prompt message.
message_history : List[BaseMessage]
The message history.
"""
response_content = OpenAITextMessageContentItem(
value = '42 is the answer to all questions',
agent_capability_category = AgentCapabilityCategories.FOUNDATIONALLM_KNOWLEDGE_MANAGEMENT
)


return CompletionResponse(
operation_id = operation_id,
content = [response_content],
content_artifacts = [],
user_prompt = user_prompt,
full_prompt = '',
completion_tokens = 0,
prompt_tokens = 0,
total_tokens = 0,
total_cost = 0
)
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from foundationallm.langchain.agents import LangChainAgentBase
from foundationallm.langchain.exceptions import LangChainException
from foundationallm.langchain.retrievers import RetrieverFactory, ContentArtifactRetrievalBase
from foundationallm.langchain.workflows import ExternalWorkflowFactory
from foundationallm.langchain.workflows import WorkflowFactory
from foundationallm.models.agents import AzureOpenAIAssistantsAgentWorkflow, ExternalAgentWorkflow, LangGraphReactAgentWorkflow
from foundationallm.models.constants import (
AgentCapabilityCategories,
Expand Down Expand Up @@ -518,8 +518,8 @@ async def invoke_async(self, request: KnowledgeManagementCompletionRequest) -> C
tools.append(tool_factory.get_tool(tool, request.objects, self.user_identity, self.config))

# create the workflow
workflow_factory = ExternalWorkflowFactory(self.plugin_manager)
workflow = workflow_factory.create_workflow(
workflow_factory = WorkflowFactory(self.plugin_manager)
workflow = workflow_factory.get_workflow(
agent.workflow,
request.objects,
tools,
Expand All @@ -530,6 +530,7 @@ async def invoke_async(self, request: KnowledgeManagementCompletionRequest) -> C
messages = self._build_conversation_history_message_list(request.message_history, agent.conversation_history_settings.max_history)

response = await workflow.invoke_async(
operation_id=request.operation_id,
user_prompt=parsed_user_prompt,
message_history=messages
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
from abc import ABC, abstractmethod
from azure.identity import DefaultAzureCredential
from langchain_core.messages import BaseMessage
from pydantic import BaseModel
from typing import List
from foundationallm.config import Configuration, UserIdentity
from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow
from foundationallm.models.orchestration import CompletionResponse
from foundationallm.telemetry import Telemetry

class FoundationaLLMWorkflowBase(BaseModel, ABC):
class FoundationaLLMWorkflowBase(ABC):
"""
FoundationaLLM base class for workflows that uses the agent workflow model for its configuration.
"""
Expand Down Expand Up @@ -49,20 +48,19 @@ def __init__(self,

@abstractmethod
async def invoke_async(self,
operation_id: str,
user_prompt:str,
message_history: List[BaseMessage])-> CompletionResponse:
"""
Invokes the workflow asynchronously.
Parameters
----------
operation_id : str
The unique identifier of the FoundationaLLM operation.
user_prompt : str
The user prompt message.
message_history : List[BaseMessage]
The message history.
"""
pass

class Config:
""" Pydantic configuration for FoundationaLLMWorkflowBase. """
extra = "allow"
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from foundationallm.langchain.exceptions import LangChainException
from foundationallm.langchain.tools import DALLEImageGenerationTool
from foundationallm.models.agents import AgentTool
from foundationallm.plugins import PluginManager
from foundationallm.plugins import PluginManager, PluginManagerTypes

class ToolFactory:
"""
Expand Down Expand Up @@ -46,7 +46,12 @@ def get_tool(
tool_plugin_manager = None

if tool_config.package_name in self.plugin_manager.external_modules:
tool_plugin_manager = self.plugin_manager.external_modules[tool_config.package_name].plugin_manager
tool_plugin_manager = next(( \
pm for pm \
in self.plugin_manager.external_modules[tool_config.package_name].plugin_managers \
if pm.plugin_manager_type == PluginManagerTypes.TOOLS), None)
if tool_plugin_manager is None:
raise LangChainException(f"Tool plugin manager not found for package {tool_config.package_name}")
return tool_plugin_manager.create_tool(tool_config, objects, user_identity, config)
else:
raise LangChainException(f"Package {tool_config.package_name} not found in the list of external modules loaded by the package manager.")
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .external_workflow_factory import ExternalWorkflowFactory
from .workflow_factory import WorkflowFactory
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from foundationallm.langchain.common import FoundationaLLMWorkflowBase
from foundationallm.langchain.exceptions import LangChainException
from foundationallm.models.agents import AgentTool, ExternalAgentWorkflow
from foundationallm.plugins import PluginManager
from foundationallm.plugins import PluginManager, PluginManagerTypes

class ExternalWorkflowFactory:
class WorkflowFactory:
"""
Factory class for creating an external agent workflow instance based on the Agent workflow configuration.
"""
Expand All @@ -33,7 +33,7 @@ def get_workflow(
config: Configuration
) -> FoundationaLLMWorkflowBase:
"""
Creates an instance of an external agent workflow based on the agent workflow configuration.
Creates an instance of an agent workflow based on the agent workflow configuration.
Parameters
----------
Expand All @@ -47,10 +47,22 @@ def get_workflow(
The user identity of the user initiating the request.
config : Configuration
The application configuration for FoundationaLLM.
"""
workflow_plugin_manager = None
if workflow_config.package_name in self.plugin_manager.external_modules:
workflow_plugin_manager = self.plugin_manager.external_modules[workflow_config.package_name].plugin_manager
return workflow_plugin_manager.create_workflow(workflow_config, objects, tools, user_identity, config)
"""

if workflow_config.package_name == "FoundationaLLM":
# internal workflows
# TODO: Refactor internal workflows to use the plugin manager
raise LangChainException("Internal workflows are not supported by the plugin manager.")
else:
raise LangChainException(f"Package {workflow_config.package_name} not found in the list of external modules loaded by the package manager.")
workflow_plugin_manager = None

if workflow_config.package_name in self.plugin_manager.external_modules:
workflow_plugin_manager = next(( \
wm for wm \
in self.plugin_manager.external_modules[workflow_config.package_name].plugin_managers \
if wm.plugin_manager_type == PluginManagerTypes.WORKFLOWS), None)
if workflow_plugin_manager is None:
raise LangChainException(f"Workflow plugin manager not found for package {workflow_config.package_name}")
return workflow_plugin_manager.create_workflow(workflow_config, objects, tools, user_identity, config)
else:
raise LangChainException(f"Package {workflow_config.package_name} not found in the list of external modules loaded by the package manager.")
Loading

0 comments on commit 06323c9

Please sign in to comment.