diff --git a/templates/components/agents/python/deep_research/README-template.md b/templates/components/agents/python/deep_research/README-template.md index 20af4a87..ec1d5ab7 100644 --- a/templates/components/agents/python/deep_research/README-template.md +++ b/templates/components/agents/python/deep_research/README-template.md @@ -30,7 +30,7 @@ The workflow writes blog posts based on documents in the [data](./data) director After starting the server, go to [http://localhost:8000](http://localhost:8000) and send a message to the agent to write a blog post. E.g: "Write a post about AI investment in 2024" -To update the workflow, you can edit the [writer.py](./app/workflows/writer.py) file. +To update the workflow, you can edit the [deep_research.py](./app/workflows/deep_research.py) file. By default, the workflow retrieves 10 results from your documents. To customize the amount of information covered in the answer, you can adjust the `TOP_K` environment variable in the `.env` file. A higher value will retrieve more results from your documents, potentially providing more comprehensive answers. diff --git a/templates/components/agents/python/deep_research/app/workflows/deep_research.py b/templates/components/agents/python/deep_research/app/workflows/deep_research.py index bfcfa947..3e540158 100644 --- a/templates/components/agents/python/deep_research/app/workflows/deep_research.py +++ b/templates/components/agents/python/deep_research/app/workflows/deep_research.py @@ -18,14 +18,14 @@ from app.engine.index import IndexConfig, get_index from app.workflows.agents import plan_research, research, write_report +from app.workflows.events import SourceNodesEvent from app.workflows.models import ( CollectAnswersEvent, DataEvent, PlanResearchEvent, + ReportEvent, ResearchEvent, - WriteReportEvent, ) -from app.workflows.events import SourceNodesEvent logger = logging.getLogger("uvicorn") logger.setLevel(logging.INFO) @@ -43,16 +43,16 @@ def create_workflow( "Index is not found. Try run generation script to create the index first." ) - return WriterWorkflow( + return DeepResearchWorkflow( index=index, chat_history=chat_history, timeout=120.0, ) -class WriterWorkflow(Workflow): +class DeepResearchWorkflow(Workflow): """ - A workflow to research and write a post for a specific topic. + A workflow to research and analyze documents from multiple perspectives and write a comprehensive report. Requirements: - An indexed documents containing the knowledge base related to the topic @@ -61,7 +61,7 @@ class WriterWorkflow(Workflow): 1. Retrieve information from the knowledge base 2. Analyze the retrieved information and provide questions for answering 3. Answer the questions - 4. Write the post based on the research results + 4. Write the report based on the research results """ memory: SimpleComposableMemory @@ -104,7 +104,7 @@ def retrieve(self, ctx: Context, ev: StartEvent) -> PlanResearchEvent: ) ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "retrieve", "state": "inprogress", @@ -118,7 +118,7 @@ def retrieve(self, ctx: Context, ev: StartEvent) -> PlanResearchEvent: self.context_nodes.extend(nodes) ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "retrieve", "state": "done", @@ -139,14 +139,14 @@ def retrieve(self, ctx: Context, ev: StartEvent) -> PlanResearchEvent: @step async def analyze( self, ctx: Context, ev: PlanResearchEvent - ) -> ResearchEvent | WriteReportEvent | StopEvent: + ) -> ResearchEvent | ReportEvent | StopEvent: """ Analyze the retrieved information """ logger.info("Analyzing the retrieved information") ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "analyze", "state": "inprogress", @@ -169,7 +169,7 @@ async def analyze( content="No more idea to analyze. We should report the answers.", ) ) - ctx.send_event(WriteReportEvent()) + ctx.send_event(ReportEvent()) else: await ctx.set("n_questions", len(res.research_questions)) self.memory.put( @@ -183,7 +183,7 @@ async def analyze( question_id = str(uuid.uuid4()) ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "answer", "state": "pending", @@ -202,7 +202,7 @@ async def analyze( ) ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "analyze", "state": "done", @@ -218,7 +218,7 @@ async def answer(self, ctx: Context, ev: ResearchEvent) -> CollectAnswersEvent: """ ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "answer", "state": "inprogress", @@ -237,7 +237,7 @@ async def answer(self, ctx: Context, ev: ResearchEvent) -> CollectAnswersEvent: answer = f"Got error when answering the question: {ev.question}" ctx.write_event_to_stream( DataEvent( - type="writer_card", + type="deep_research_event", data={ "event": "answer", "state": "done", @@ -257,7 +257,7 @@ async def answer(self, ctx: Context, ev: ResearchEvent) -> CollectAnswersEvent: @step async def collect_answers( self, ctx: Context, ev: CollectAnswersEvent - ) -> WriteReportEvent: + ) -> ReportEvent: """ Collect answers to all questions """ @@ -285,7 +285,7 @@ async def collect_answers( return PlanResearchEvent() @step - async def report(self, ctx: Context, ev: WriteReportEvent) -> StopEvent: + async def report(self, ctx: Context, ev: ReportEvent) -> StopEvent: """ Report the answers """ diff --git a/templates/components/agents/python/deep_research/app/workflows/models.py b/templates/components/agents/python/deep_research/app/workflows/models.py index 66df537c..0fe25b47 100644 --- a/templates/components/agents/python/deep_research/app/workflows/models.py +++ b/templates/components/agents/python/deep_research/app/workflows/models.py @@ -22,12 +22,12 @@ class CollectAnswersEvent(Event): answer: str -class WriteReportEvent(Event): +class ReportEvent(Event): pass # Events that are streamed to the frontend and rendered there -class WriterEventData(BaseModel): +class DeepResearchEventData(BaseModel): event: Literal["retrieve", "analyze", "answer"] state: Literal["pending", "inprogress", "done", "error"] id: Optional[str] = None @@ -36,8 +36,8 @@ class WriterEventData(BaseModel): class DataEvent(Event): - type: Literal["writer_card"] - data: WriterEventData + type: Literal["deep_research_event"] + data: DeepResearchEventData def to_response(self): return self.model_dump() diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-message-content.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-message-content.tsx index db43ed9e..fa32f6f1 100644 --- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-message-content.tsx +++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-message-content.tsx @@ -5,8 +5,8 @@ import { useChatMessage, useChatUI, } from "@llamaindex/chat-ui"; +import { DeepResearchCard } from "./custom/deep-research-card"; import { Markdown } from "./custom/markdown"; -import { WriterCard } from "./custom/writer-card"; import { ToolAnnotations } from "./tools/chat-tools"; export function ChatMessageContent() { @@ -23,10 +23,10 @@ export function ChatMessageContent() { /> ), }, - // add the writer card + // add the deep research card { position: ContentPosition.CHAT_EVENTS, - component: , + component: , }, { // add the tool annotations after events diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/custom/writer-card.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/custom/deep-research-card.tsx similarity index 83% rename from templates/types/streaming/nextjs/app/components/ui/chat/custom/writer-card.tsx rename to templates/types/streaming/nextjs/app/components/ui/chat/custom/deep-research-card.tsx index d7ccd242..b9c42561 100644 --- a/templates/types/streaming/nextjs/app/components/ui/chat/custom/writer-card.tsx +++ b/templates/types/streaming/nextjs/app/components/ui/chat/custom/deep-research-card.tsx @@ -22,8 +22,8 @@ import { Markdown } from "./markdown"; // Streaming event types type EventState = "pending" | "inprogress" | "done" | "error"; -type WriterEvent = { - type: "writer_card"; +type DeepResearchEvent = { + type: "deep_research_event"; data: { event: "retrieve" | "analyze" | "answer"; state: EventState; @@ -42,7 +42,7 @@ type QuestionState = { isOpen: boolean; }; -type WriterState = { +type DeepResearchCardState = { retrieve: { state: EventState | null; }; @@ -52,7 +52,7 @@ type WriterState = { }; }; -interface WriterCardProps { +interface DeepResearchCardProps { message: Message; className?: string; } @@ -66,9 +66,9 @@ const stateIcon: Record = { // Transform the state based on the event without mutations const transformState = ( - state: WriterState, - event: WriterEvent, -): WriterState => { + state: DeepResearchCardState, + event: DeepResearchEvent, +): DeepResearchCardState => { switch (event.data.event) { case "answer": { const { id, question, answer } = event.data; @@ -119,8 +119,10 @@ const transformState = ( } }; -// Convert writer events to state -const writeEventsToState = (events: WriterEvent[] | undefined): WriterState => { +// Convert deep research events to state +const deepResearchEventsToState = ( + events: DeepResearchEvent[] | undefined, +): DeepResearchCardState => { if (!events?.length) { return { retrieve: { state: null }, @@ -128,26 +130,35 @@ const writeEventsToState = (events: WriterEvent[] | undefined): WriterState => { }; } - const initialState: WriterState = { + const initialState: DeepResearchCardState = { retrieve: { state: null }, analyze: { state: null, questions: [] }, }; return events.reduce( - (acc: WriterState, event: WriterEvent) => transformState(acc, event), + (acc: DeepResearchCardState, event: DeepResearchEvent) => + transformState(acc, event), initialState, ); }; -export function WriterCard({ message, className }: WriterCardProps) { - const writerEvents = message.annotations as WriterEvent[] | undefined; - const hasWriterEvents = writerEvents?.some( - (event) => event.type === "writer_card", +export function DeepResearchCard({ + message, + className, +}: DeepResearchCardProps) { + const deepResearchEvents = message.annotations as + | DeepResearchEvent[] + | undefined; + const hasDeepResearchEvents = deepResearchEvents?.some( + (event) => event.type === "deep_research_event", ); - const state = useMemo(() => writeEventsToState(writerEvents), [writerEvents]); + const state = useMemo( + () => deepResearchEventsToState(deepResearchEvents), + [deepResearchEvents], + ); - if (!hasWriterEvents) { + if (!hasDeepResearchEvents) { return null; }