-
Notifications
You must be signed in to change notification settings - Fork 375
[Feature] Support CrewAI for BYO agents #920
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
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
cc594a4
feat: setup a2a server and agent executor for crewai
supreme-gg-gg 46c9d5c
feat: provide example crewai research agent
supreme-gg-gg 6698b99
feat: update pyproject makefile and uv lock for crewai package and sa…
supreme-gg-gg 233e1f6
feat: add more event types and include flow method exec events
supreme-gg-gg fe7faa9
feat: add example for creawi flow setup
supreme-gg-gg 7d196f3
fix: cleanup naming and readme
supreme-gg-gg 7001c5f
add listners and upgrade crewai deps
supreme-gg-gg 04cf1fb
update crewai examples and dockerfile
supreme-gg-gg a0a8d3d
remove init in crewai package
supreme-gg-gg 409a122
install only kagent-adk and kagent-core in python app dockerfile
supreme-gg-gg 6fcbfe2
update dockerfile for langgraph to use uv base image
supreme-gg-gg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| # KAgent CrewAI Integration | ||
|
|
||
| This package provides CrewAI integration for KAgent with A2A (Agent-to-Agent) server support. | ||
|
|
||
| ## Features | ||
|
|
||
| - **A2A Server Integration**: Compatible with KAgent's Agent-to-Agent protocol | ||
| - **Event Streaming**: Real-time streaming of crew execution events | ||
| - **FastAPI Integration**: Ready-to-deploy web server for agent execution | ||
|
|
||
| ## Quick Start | ||
|
|
||
| This package supports both CrewAI Crews and Flows. To get started, define your CrewAI crew or flow as you normally would, then replace the `kickoff` command with the `KAgentApp` which will handle A2A requests and execution. | ||
|
|
||
| ```python | ||
| from kagent.crewai import KAgentApp | ||
| # This is the crew or flow you defined | ||
| from research_crew.crew import ResearchCrew | ||
|
|
||
| app = KAgentApp(crew=ResearchCrew().crew(), agent_card={ | ||
| "name": "my-crewai-agent", | ||
| "description": "A CrewAI agent with KAgent integration", | ||
| "version": "0.1.0", | ||
| "capabilities": {"streaming": True}, | ||
| "defaultInputModes": ["text"], | ||
| "defaultOutputModes": ["text"] | ||
| }) | ||
|
|
||
| fastapi_app = app.build() | ||
| uvicorn.run(fastapi_app, host="0.0.0.0", port=8080) | ||
| ``` | ||
|
|
||
| ## Architecture | ||
|
|
||
| The package mirrors the structure of `kagent-adk` and `kagent-langgraph` but uses CrewAI for multi-agent orchestration: | ||
|
|
||
| - **CrewAIAgentExecutor**: Executes CrewAI workflows within A2A protocol | ||
| - **KAgentApp**: FastAPI application builder with A2A integration | ||
| - **Event Converters**: Translates CrewAI events into A2A events for streaming. | ||
|
|
||
| ## Deployment | ||
|
|
||
| The uses the same deployment approach as other KAgent A2A applications (ADK / LangGraph). You can refer to `samples/crewai/` for examples. | ||
|
|
||
| ## Note | ||
|
|
||
| Due to the current design of the package, your tasks in CrewAI should expect a `input` parameter which contains the input text if available. We will support JSON input for more native CrewAI integration in the future. You can check out an example in `samples/crewai/research-crew/src/research_crew/config/tasks.yaml`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| [build-system] | ||
| requires = ["hatchling"] | ||
| build-backend = "hatchling.build" | ||
|
|
||
| [project] | ||
| name = "kagent-crewai" | ||
| version = "0.1.0" | ||
| description = "CrewAI integration for KAgent with A2A server support" | ||
| readme = "README.md" | ||
| requires-python = ">=3.13" | ||
| dependencies = [ | ||
| "crewai[tools]>=0.193.2,<1.0.0", | ||
| "httpx>=0.25.0", | ||
| "fastapi>=0.100.0", | ||
| "pydantic>=2.0.0", | ||
| "typing-extensions>=4.0.0", | ||
| "uvicorn>=0.20.0", | ||
| "a2a-sdk>=0.3.1", | ||
| "kagent-core", | ||
| ] | ||
|
|
||
| [project.optional-dependencies] | ||
| dev = [ | ||
| "pytest>=7.0.0", | ||
| "pytest-asyncio>=0.21.0", | ||
| "black>=23.0.0", | ||
| "ruff>=0.1.0", | ||
| ] | ||
|
|
||
| [tool.uv.sources] | ||
| kagent-core = {workspace = true} | ||
|
|
||
| [tool.hatch.build.targets.wheel] | ||
| packages = ["src/kagent"] | ||
|
|
||
| [tool.ruff] | ||
| extend = "../../pyproject.toml" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| import faulthandler | ||
| import logging | ||
| from typing import Union | ||
|
|
||
| import httpx | ||
| from a2a.server.apps import A2AStarletteApplication | ||
| from a2a.server.request_handlers import DefaultRequestHandler | ||
| from a2a.types import AgentCard | ||
| from fastapi import FastAPI, Request | ||
| from fastapi.responses import PlainTextResponse | ||
|
|
||
| from crewai import Crew, Flow | ||
| from kagent.core import KAgentConfig, configure_tracing | ||
| from kagent.core.a2a import KAgentRequestContextBuilder, KAgentTaskStore | ||
|
|
||
| from ._executor import CrewAIAgentExecutor, CrewAIAgentExecutorConfig | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def def_health_check(request: Request) -> PlainTextResponse: | ||
| return PlainTextResponse("OK") | ||
|
|
||
|
|
||
| def thread_dump(request: Request) -> PlainTextResponse: | ||
| import io | ||
|
|
||
| buf = io.StringIO() | ||
| faulthandler.dump_traceback(file=buf) | ||
| buf.seek(0) | ||
| return PlainTextResponse(buf.read()) | ||
|
|
||
|
|
||
| class KAgentApp: | ||
| def __init__( | ||
| self, | ||
| *, | ||
| crew: Union[Crew, Flow], | ||
| agent_card: AgentCard, | ||
| config: KAgentConfig = KAgentConfig(), | ||
| executor_config: CrewAIAgentExecutorConfig | None = None, | ||
| tracing: bool = True, | ||
| ): | ||
| self._crew = crew | ||
| self.agent_card = AgentCard.model_validate(agent_card) | ||
| self.config = config | ||
| self.executor_config = executor_config or CrewAIAgentExecutorConfig() | ||
| self.tracing = tracing | ||
|
|
||
| def build(self) -> FastAPI: | ||
| http_client = httpx.AsyncClient(base_url=self.config.url) | ||
|
|
||
| agent_executor = CrewAIAgentExecutor( | ||
| crew=self._crew, | ||
| app_name=self.config.app_name, | ||
| config=self.executor_config, | ||
| ) | ||
|
|
||
| task_store = KAgentTaskStore(http_client) | ||
| request_context_builder = KAgentRequestContextBuilder(task_store=task_store) | ||
| request_handler = DefaultRequestHandler( | ||
| agent_executor=agent_executor, | ||
| task_store=task_store, | ||
| request_context_builder=request_context_builder, | ||
| ) | ||
|
|
||
| a2a_app = A2AStarletteApplication( | ||
| agent_card=self.agent_card, | ||
| http_handler=request_handler, | ||
| ) | ||
|
|
||
| faulthandler.enable() | ||
| app = FastAPI( | ||
| title=f"KAgent CrewAI: {self.config.app_name}", | ||
| description=f"CrewAI agent with KAgent integration: {self.agent_card.description}", | ||
| version=self.agent_card.version, | ||
| ) | ||
|
|
||
| if self.tracing: | ||
| configure_tracing(app) | ||
|
|
||
| app.add_route("/health", methods=["GET"], route=def_health_check) | ||
| app.add_route("/thread_dump", methods=["GET"], route=thread_dump) | ||
| a2a_app.add_routes_to_app(app) | ||
|
|
||
| return app |
151 changes: 151 additions & 0 deletions
151
python/packages/kagent-crewai/src/kagent/crewai/_executor.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| import logging | ||
| import uuid | ||
| from datetime import datetime, timezone | ||
| from typing import Union, override | ||
|
|
||
| from a2a.server.agent_execution import AgentExecutor | ||
| from a2a.server.agent_execution.context import RequestContext | ||
| from a2a.server.events.event_queue import EventQueue | ||
| from a2a.types import ( | ||
| Artifact, | ||
| DataPart, | ||
| Message, | ||
| Part, | ||
| Role, | ||
| TaskArtifactUpdateEvent, | ||
| TaskState, | ||
| TaskStatus, | ||
| TaskStatusUpdateEvent, | ||
| TextPart, | ||
| ) | ||
| from pydantic import BaseModel | ||
|
|
||
| from crewai import Crew, Flow | ||
|
|
||
| from ._listeners import A2ACrewAIListener | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| class CrewAIAgentExecutorConfig(BaseModel): | ||
| execution_timeout: float = 300.0 | ||
|
|
||
|
|
||
| class CrewAIAgentExecutor(AgentExecutor): | ||
| def __init__( | ||
| self, | ||
| *, | ||
| crew: Union[Crew, Flow], | ||
| app_name: str, | ||
| config: CrewAIAgentExecutorConfig | None = None, | ||
| ): | ||
| super().__init__() | ||
| self._crew = crew | ||
| self.app_name = app_name | ||
| self._config = config or CrewAIAgentExecutorConfig() | ||
|
|
||
| @override | ||
| async def cancel(self, context: RequestContext, event_queue: EventQueue): | ||
| raise NotImplementedError("Cancellation is not implemented") | ||
|
|
||
| @override | ||
| async def execute( | ||
| self, | ||
| context: RequestContext, | ||
| event_queue: EventQueue, | ||
| ): | ||
| if not context.message: | ||
| raise ValueError("A2A request must have a message") | ||
|
|
||
| if not context.current_task: | ||
| await event_queue.enqueue_event( | ||
| TaskStatusUpdateEvent( | ||
| task_id=context.task_id, | ||
| status=TaskStatus( | ||
| state=TaskState.submitted, | ||
| message=context.message, | ||
| timestamp=datetime.now(timezone.utc).isoformat(), | ||
| ), | ||
| context_id=context.context_id, | ||
| final=False, | ||
| ) | ||
| ) | ||
|
|
||
| await event_queue.enqueue_event( | ||
| TaskStatusUpdateEvent( | ||
| task_id=context.task_id, | ||
| status=TaskStatus( | ||
| state=TaskState.working, | ||
| timestamp=datetime.now(timezone.utc).isoformat(), | ||
| ), | ||
| context_id=context.context_id, | ||
| final=False, | ||
| metadata={ | ||
| "app_name": self.app_name, | ||
| "session_id": context.context_id, | ||
| }, | ||
| ) | ||
| ) | ||
|
|
||
| # This listener will capture and convert CrewAI events and enqueue them to A2A event queue | ||
| A2ACrewAIListener(context, event_queue, self.app_name) | ||
|
|
||
| try: | ||
| inputs = None | ||
| if context.message and context.message.parts: | ||
| for part in context.message.parts: | ||
| if isinstance(part, DataPart): | ||
| inputs = part.root.data | ||
| break | ||
| if inputs is None: | ||
| user_input = context.get_user_input() | ||
| inputs = {"input": user_input} if user_input else {} | ||
|
|
||
| if isinstance(self._crew, Flow): | ||
| flow_class = type(self._crew) | ||
| flow_instance = flow_class() | ||
| result = await flow_instance.kickoff_async(inputs=inputs) | ||
| else: | ||
| result = await self._crew.kickoff_async(inputs=inputs) | ||
|
|
||
| await event_queue.enqueue_event( | ||
| TaskArtifactUpdateEvent( | ||
| task_id=context.task_id, | ||
| last_chunk=True, | ||
| context_id=context.context_id, | ||
| artifact=Artifact( | ||
| artifact_id=str(uuid.uuid4()), | ||
| parts=[Part(TextPart(text=str(result.raw)))], | ||
| ), | ||
| ) | ||
| ) | ||
| await event_queue.enqueue_event( | ||
| TaskStatusUpdateEvent( | ||
| task_id=context.task_id, | ||
| status=TaskStatus( | ||
| state=TaskState.completed, | ||
| timestamp=datetime.now(timezone.utc).isoformat(), | ||
| ), | ||
| context_id=context.context_id, | ||
| final=True, | ||
| ) | ||
| ) | ||
|
|
||
| except Exception as e: | ||
| logger.error(f"Error during CrewAI execution: {e}", exc_info=True) | ||
| await event_queue.enqueue_event( | ||
| TaskStatusUpdateEvent( | ||
| task_id=context.task_id, | ||
| status=TaskStatus( | ||
| state=TaskState.failed, | ||
| timestamp=datetime.now(timezone.utc).isoformat(), | ||
| message=Message( | ||
| message_id=str(uuid.uuid4()), | ||
| role=Role.agent, | ||
| parts=[Part(TextPart(text=str(e)))], | ||
| ), | ||
| ), | ||
| context_id=context.context_id, | ||
| final=True, | ||
| ) | ||
| ) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.