Skip to content

feat: introduce plan and route with supervisor #140

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Sep 2, 2024

Conversation

muralov
Copy link
Collaborator

@muralov muralov commented Aug 20, 2024

Description
Introduces first implementation of Plan and Route features of Supervisor Agent.

Changes proposed in this pull request:

  • Implements supervisor agent
  • Introduces plan and route feature for supervisor agent
  • Introduces common node to answer general questions
  • Introduces finalizer node to generate final response
  • Introduces generate LangGraph diagram script
  • Implements unit tests

Related issue(s)
#112

* change it to /conversations/{conversation_id}/messages
* Refactor some model code
* Build plan & route logic with langgraph for k8s and kyma agents
@muralov muralov force-pushed the build-plan-and-route branch from 767247e to 296474c Compare August 20, 2024 10:05
@muralov muralov changed the title Build plan and route feat: introduce plan and route with supervisor Aug 20, 2024
* Implement a langgraph node which summarizes the agent conversation as a final response
* Upgrade langgraph
Main Kyma graph will have all the workflow of the companion. It'll have init node too.
* Write unit tests
* Improve supervisor prompt to consider subtask statuses
* Implement script to generate langgraph diagramm
* Create tests for all agents
* Create tests for util functions
* Create tests for kyma graph
* Update the existing tests
* As we have only kyma and k8s questions deny irrelevant questions
* Add coversation/id/messages route rest tests
* Improve plan & route with k8s and kyma terminology to route properly
* Add common node to answer general questions
* Improve error handling
@muralov muralov marked this pull request as ready for review August 29, 2024 08:02
@muralov muralov requested a review from a team as a code owner August 29, 2024 08:02
@muralov muralov requested a review from the1bit August 29, 2024 08:02
@muralov muralov force-pushed the build-plan-and-route branch from 2aab769 to 71feb75 Compare August 29, 2024 08:11
@muralov muralov requested review from mfaizanse and removed request for the1bit August 29, 2024 08:14
@muralov muralov force-pushed the build-plan-and-route branch from 71feb75 to ec98aee Compare August 29, 2024 08:25

from agents.graph import KymaGraph

load_dotenv()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you write comments on how to run this script and what env variables are needed to run this script?


messages: Annotated[Sequence[BaseMessage], operator.add]
next: str | None
subtasks: list[SubTask] | None = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: sub_tasks?

Copy link
Collaborator Author

@muralov muralov Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's leave as it is as the word subtask really exists :)

class Plan(BaseModel):
"""Plan to follow in future"""

subtasks: list[SubTask] = Field(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: sub_tasks?

@@ -0,0 +1,233 @@
import functools
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this file supposed to be merged to main?
If not, please delete it.
If yes, please move to /examples or /hack directory.


model: Model
_name: str = SUPERVISOR
parser = PydanticOutputParser(pydantic_object=Plan) # type: ignore
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this parser used anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, below, you can search for it with state.subtasks = self.parser.parse(plan.content).subtasks

@Teneroy Teneroy self-requested a review August 29, 2024 09:20
@muralov muralov linked an issue Aug 29, 2024 that may be closed by this pull request
4 tasks
],
}

def common_node(self, state: AgentState) -> dict[str, Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what is the purpose of this node?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it answer the general questions. We'll move this to planner if possible in the next PR.

PLANNER = "Planner"


def should_continue(state: MessagesState) -> Literal["action", "__end__"]:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have should_exit in the utils, and should_continue here? Shouldn't it be moved to utils as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll delete it

class CustomJSONEncoder(json.JSONEncoder):
"""Custom JSON encoder."""

def default(self, obj): # noqa D102
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intermediate steps have to be streamed correctly according to an agreement in our API documentation. Currently, we receive everything in our output

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. There are mainly two reasons I haven't done it yet:

  • first, didn't have enough time
  • second, we need to add status code (error or success) for the chunks as anytime error may occur during streaming. The status code in the begining is almost always 200 due asynchronous behavior of FastAPI routes.

For that reason, I'd suggest to have follow-up ticket for this? We can think how to add the code and implement it in the follow-up ticket.
I'll also document this in the issue.

def common_node(self, state: AgentState) -> dict[str, Any]:
"""Breaks down the given user query into sub-tasks."""

prompt = ChatPromptTemplate.from_messages(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing as the commend above about the common node. Consider using history during the planning. Consider merging those two nodes

Copy link
Collaborator Author

@muralov muralov Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need think whether we should consider the conversation or not for the planning. I am currently not sure.
I will document the points in the issue and improve them further in the next sprints.
Let's keep it simple for the start?

I thought we discussed this already, I've avoided this re-planning by separating out the.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if common questions, don't plan and directly send to Finalizer!

from langfuse.callback import CallbackHandler

handler = CallbackHandler(
secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move to some constant variables


prompt = ChatPromptTemplate.from_messages(
[
MessagesPlaceholder(variable_name="messages"),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supervisor agent should have an ability to fail properly in case other agents can't provide relevant information. Therefore it should probably analyze provided information and make a decision whether this information is sufficient and it can generate a comprehensive response or it should fail. I suspect, additional prompting should be done in the finalizer. Here is an example where an agent fails in doing that:
request_response1.txt

Copy link
Collaborator Author

@muralov muralov Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case there 4 subtasks are created how to analyse the failing pod. We should discuss and optimize the planner accordingly in the next sprints. For that we need to first discuss whether we need to have detailed plan or keep the original sentences.

With this ticket, we know how we can do the planning and routing now. I'd suggest to optimize it step-by-step.

I'll document this too.


result = agent.invoke(state)
return {"messages": [HumanMessage(content=result["output"], name=name)]}
logger = get_logger(__name__)


class SupervisorAgent:
Copy link

@Teneroy Teneroy Aug 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider renaming to task executor. Then we will merge planner, task executor, replan, and finalizer into the subgraph called SupervisorAgent in the future

Copy link
Collaborator Author

@muralov muralov Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave this as SupervisorAgent as it eventually will have more functionality. Planning is just temporarily separated out for the start. It will be part of SupervisorAgent too soon.
It is currently not executing any task, it is looking at the conversation and subtasks and managing the communication to all other members of the supervisor team.

@muralov muralov force-pushed the build-plan-and-route branch from fdf49a6 to fd842aa Compare August 30, 2024 13:26
}


DEFAULT_NUMBER_OF_MESSAGES = 10
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets move this to the common file?


logger = get_logger(__name__)

REDIS_URL = f"{os.getenv('REDIS_URL')}/0"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, this should be part of a separate module Config.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I already have this in mind for the next PR. I will create a TODO for this too.

class SingletonMeta(type):
"""Singleton metaclass."""

_instances: dict[type, object] = {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why it needs to be a dict? Can it be a single _instance: object?

Copy link
Collaborator Author

@muralov muralov Aug 30, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this meta class have class field _instances which can be used by the other classes too. It means it stores single instances for multiple classes.

@kyma-bot kyma-bot merged commit edab178 into kyma-project:main Sep 2, 2024
14 of 15 checks passed
@mfaizanse mfaizanse deleted the build-plan-and-route branch September 2, 2024 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create Initial Version of Supervisor Agent for Kyma Companion
4 participants