Skip to content

Commit 1c6cd25

Browse files
committed
feat: add support for Tasks in API and integrate UI
1 parent ac7a27f commit 1c6cd25

File tree

15 files changed

+380
-78
lines changed

15 files changed

+380
-78
lines changed

examples/chat/chat.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
from pydantic import BaseModel, ConfigDict, Field
2828

29+
from ragbits.agents.tools.todo import Task, TaskStatus
2930
from ragbits.chat.interface import ChatInterface
3031
from ragbits.chat.interface.forms import FeedbackConfig, UserSettings
3132
from ragbits.chat.interface.types import ChatContext, ChatResponse, LiveUpdateType
@@ -141,10 +142,25 @@ async def chat(
141142
),
142143
]
143144

145+
parentTask = Task(id="task_id_1", description="Example task with a subtask")
146+
subtaskTask = Task(
147+
id="task_id_2", description="Example subtask", status=TaskStatus.IN_PROGRESS, parent_id="task_id_1"
148+
)
149+
144150
for live_update in example_live_updates:
145151
yield live_update
146152
await asyncio.sleep(2)
147153

154+
yield self.create_todo_item_response(parentTask)
155+
yield self.create_todo_item_response(subtaskTask)
156+
157+
await asyncio.sleep(1)
158+
subtaskTask.status = TaskStatus.COMPLETED
159+
parentTask.status = TaskStatus.COMPLETED
160+
161+
yield self.create_todo_item_response(parentTask)
162+
yield self.create_todo_item_response(subtaskTask)
163+
148164
streaming_result = self.llm.generate_streaming([*history, {"role": "user", "content": message}])
149165
async for chunk in streaming_result:
150166
yield self.create_text_response(chunk)

packages/ragbits-agents/src/ragbits/agents/tools/todo.py

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,35 @@
22

33
import uuid
44
from dataclasses import dataclass, field
5-
from datetime import datetime
65
from enum import Enum
76
from typing import Any, Literal
87

8+
from pydantic import BaseModel
9+
910

1011
class TaskStatus(str, Enum):
1112
"""Task status options."""
13+
1214
PENDING = "pending"
1315
IN_PROGRESS = "in_progress"
1416
COMPLETED = "completed"
1517

1618

17-
@dataclass
18-
class Task:
19+
class Task(BaseModel):
1920
"""Simple task representation."""
21+
2022
id: str
2123
description: str
2224
status: TaskStatus = TaskStatus.PENDING
2325
order: int = 0
2426
summary: str | None = None
27+
parent_id: str | None = None
2528

2629

2730
@dataclass
2831
class TodoList:
2932
"""Simple todo list for one agent run."""
33+
3034
tasks: list[Task] = field(default_factory=list)
3135
current_index: int = 0
3236

@@ -36,14 +40,15 @@ def get_current_task(self) -> Task | None:
3640
return self.tasks[self.current_index]
3741
return None
3842

39-
def advance_to_next(self):
43+
def advance_to_next(self) -> None:
4044
"""Move to next task."""
4145
self.current_index += 1
4246

4347

4448
# Storage - just one todo list per agent run
4549
_current_todo: TodoList | None = None
4650

51+
4752
def todo_manager(
4853
action: Literal["create", "get_current", "start_task", "complete_task", "get_final_summary"],
4954
tasks: list[str] | None = None,
@@ -67,18 +72,14 @@ def todo_manager(
6772

6873
_current_todo = TodoList()
6974
for i, desc in enumerate(tasks):
70-
task = Task(
71-
id=str(uuid.uuid4()),
72-
description=desc.strip(),
73-
order=i
74-
)
75+
task = Task(id=str(uuid.uuid4()), description=desc.strip(), order=i)
7576
_current_todo.tasks.append(task)
7677

7778
return {
7879
"action": "create",
7980
"tasks": [{"id": t.id, "description": t.description, "order": t.order} for t in _current_todo.tasks],
8081
"total_count": len(_current_todo.tasks),
81-
"message": f"Created {len(tasks)} tasks"
82+
"message": f"Created {len(tasks)} tasks",
8283
}
8384

8485
if not _current_todo:
@@ -91,14 +92,14 @@ def todo_manager(
9192
"action": "get_current",
9293
"current_task": None,
9394
"all_completed": True,
94-
"message": "All tasks completed!"
95+
"message": "All tasks completed!",
9596
}
9697

9798
return {
9899
"action": "get_current",
99100
"current_task": {"id": current.id, "description": current.description, "status": current.status.value},
100101
"progress": f"{_current_todo.current_index + 1}/{len(_current_todo.tasks)}",
101-
"message": f"Current task: {current.description}"
102+
"message": f"Current task: {current.description}",
102103
}
103104

104105
elif action == "start_task":
@@ -110,7 +111,7 @@ def todo_manager(
110111
return {
111112
"action": "start_task",
112113
"task": {"id": current.id, "description": current.description, "status": current.status.value},
113-
"message": f"Started task: {current.description}"
114+
"message": f"Started task: {current.description}",
114115
}
115116

116117
elif action == "complete_task":
@@ -137,18 +138,14 @@ def todo_manager(
137138
"next_task": {"id": next_task.id, "description": next_task.description} if next_task else None,
138139
"progress": f"{completed_count}/{len(_current_todo.tasks)}",
139140
"all_completed": next_task is None,
140-
"message": f"Completed: {current.description}"
141+
"message": f"Completed: {current.description}",
141142
}
142143

143144
elif action == "get_final_summary":
144145
completed_tasks = [t for t in _current_todo.tasks if t.status == TaskStatus.COMPLETED]
145146

146147
if not completed_tasks:
147-
return {
148-
"action": "get_final_summary",
149-
"final_summary": "",
150-
"message": "No completed tasks found."
151-
}
148+
return {"action": "get_final_summary", "final_summary": "", "message": "No completed tasks found."}
152149

153150
# Create comprehensive final summary
154151
final_content = []
@@ -167,7 +164,7 @@ def todo_manager(
167164
"action": "get_final_summary",
168165
"final_summary": final_summary,
169166
"total_completed": len(completed_tasks),
170-
"message": f"Final summary with {len(completed_tasks)} completed tasks."
167+
"message": f"Final summary with {len(completed_tasks)} completed tasks.",
171168
}
172169

173170
else:

packages/ragbits-chat/src/ragbits/chat/interface/_interface.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections.abc import AsyncGenerator, Callable
1010
from typing import Any
1111

12+
from ragbits.agents.tools.todo import Task
1213
from ragbits.chat.interface.ui_customization import UICustomization
1314
from ragbits.core.audit.metrics import record_metric
1415
from ragbits.core.audit.metrics.base import MetricType
@@ -251,6 +252,10 @@ def create_usage_response(usage: Usage) -> ChatResponse:
251252
content={model: MessageUsage.from_usage(usage) for model, usage in usage.model_breakdown.items()},
252253
)
253254

255+
@staticmethod
256+
def create_todo_item_response(task: Task) -> ChatResponse:
257+
return ChatResponse(type=ChatResponseType.TODO_ITEM, content=task)
258+
254259
@staticmethod
255260
def _sign_state(state: dict[str, Any]) -> str:
256261
"""

packages/ragbits-chat/src/ragbits/chat/interface/types.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from pydantic import BaseModel, ConfigDict, Field
55

6+
from ragbits.agents.tools.todo import Task
67
from ragbits.chat.auth.types import User
78
from ragbits.chat.interface.forms import UserSettings
89
from ragbits.chat.interface.ui_customization import UICustomization
@@ -122,6 +123,7 @@ class ChatResponseType(str, Enum):
122123
CHUNKED_CONTENT = "chunked_content"
123124
CLEAR_MESSAGE = "clear_message"
124125
USAGE = "usage"
126+
TODO_ITEM = "todo_item"
125127

126128

127129
class ChatContext(BaseModel):
@@ -140,7 +142,16 @@ class ChatResponse(BaseModel):
140142

141143
type: ChatResponseType
142144
content: (
143-
str | Reference | StateUpdate | LiveUpdate | list[str] | Image | dict[str, MessageUsage] | ChunkedContent | None
145+
str
146+
| Reference
147+
| StateUpdate
148+
| LiveUpdate
149+
| list[str]
150+
| Image
151+
| dict[str, MessageUsage]
152+
| ChunkedContent
153+
| None
154+
| Task
144155
)
145156

146157
def as_text(self) -> str | None:
@@ -217,6 +228,12 @@ def as_usage(self) -> dict[str, MessageUsage] | None:
217228
"""
218229
return cast(dict[str, MessageUsage], self.content) if self.type == ChatResponseType.USAGE else None
219230

231+
def as_task(self) -> Task | None:
232+
"""
233+
Return the content as Task if this is an todo_item response, else None.
234+
"""
235+
return cast(Task, self.content) if self.type == ChatResponseType.TODO_ITEM else None
236+
220237

221238
class ChatMessageRequest(BaseModel):
222239
"""Client-side chat request interface."""

packages/ragbits-chat/src/ragbits/chat/providers/model_provider.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from pydantic import BaseModel
1212

13+
from ragbits.agents.tools.todo import Task, TaskStatus
1314
from ragbits.chat.interface.types import AuthType
1415

1516

@@ -82,6 +83,7 @@ def get_models(self) -> dict[str, type[BaseModel | Enum]]:
8283
"FeedbackType": FeedbackType,
8384
"LiveUpdateType": LiveUpdateType,
8485
"MessageRole": MessageRole,
86+
"TaskStatus": TaskStatus,
8587
# Core data models
8688
"ChatContext": ChatContext,
8789
"ChunkedContent": ChunkedContent,
@@ -93,6 +95,7 @@ def get_models(self) -> dict[str, type[BaseModel | Enum]]:
9395
"FeedbackItem": FeedbackItem,
9496
"Image": Image,
9597
"MessageUsage": MessageUsage,
98+
"Task": Task,
9699
# Configuration models
97100
"HeaderCustomization": HeaderCustomization,
98101
"UICustomization": UICustomization,
@@ -151,6 +154,8 @@ def get_categories(self) -> dict[str, list[str]]:
151154
"JWTToken",
152155
"User",
153156
"MessageUsage",
157+
"Task",
158+
"TaskStatus",
154159
],
155160
"configuration": [
156161
"HeaderCustomization",

scripts/generate_typescript_from_json_schema.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ def _generate_chat_response_union_type() -> str:
201201
("ImageChatResponse", "image", "Image"),
202202
("ClearMessageResponse", "clear_message", "never"),
203203
("MessageUsageChatResponse", "usage", "Record<string, MessageUsage>"),
204+
("TodoItemChatResonse", "todo_item", "Task"),
204205
]
205206

206207
internal_response_interfaces = [

typescript/@ragbits/api-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"repository": {
66
"type": "git",
77
"url": "https://github.com/deepsense-ai/ragbits"
8-
},
8+
},
99
"main": "dist/index.cjs",
1010
"module": "dist/index.js",
1111
"types": "dist/index.d.ts",

typescript/@ragbits/api-client/src/autogen.types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const ChatResponseType = {
2323
ChunkedContent: 'chunked_content',
2424
ClearMessage: 'clear_message',
2525
Usage: 'usage',
26+
TodoItem: 'todo_item',
2627
} as const
2728

2829
export type ChatResponseType = TypeFrom<typeof ChatResponseType>
@@ -58,6 +59,17 @@ export const MessageRole = {
5859

5960
export type MessageRole = TypeFrom<typeof MessageRole>
6061

62+
/**
63+
* Represents the TaskStatus enum
64+
*/
65+
export const TaskStatus = {
66+
Pending: 'pending',
67+
InProgress: 'in_progress',
68+
Completed: 'completed',
69+
} as const
70+
71+
export type TaskStatus = TypeFrom<typeof TaskStatus>
72+
6173
/**
6274
* Represents the AuthType enum
6375
*/
@@ -170,6 +182,21 @@ export interface MessageUsage {
170182
total_tokens: number
171183
}
172184

185+
/**
186+
* Simple task representation.
187+
*/
188+
export interface Task {
189+
id: string
190+
description: string
191+
/**
192+
* Task status options.
193+
*/
194+
status: 'pending' | 'in_progress' | 'completed'
195+
order: number
196+
summary: string | null
197+
parent_id: string | null
198+
}
199+
173200
/**
174201
* Customization for the header section of the UI.
175202
*/
@@ -465,6 +492,11 @@ export interface MessageUsageChatResponse {
465492
content: Record<string, MessageUsage>
466493
}
467494

495+
export interface TodoItemChatResonse {
496+
type: 'todo_item'
497+
content: Task
498+
}
499+
468500
export interface ChunkedChatResponse {
469501
type: 'chunked_content'
470502
content: ChunkedContent
@@ -484,3 +516,4 @@ export type ChatResponse =
484516
| ImageChatResponse
485517
| ClearMessageResponse
486518
| MessageUsageChatResponse
519+
| TodoItemChatResonse

0 commit comments

Comments
 (0)