Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHECKLIST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Demo2 CHECKLIST

## 过关前自检

- [ ] `TaskStore.add()` 能返回带 `id` 的任务
- [ ] `get_all()` 返回列表
- [ ] `get()` 对不存在任务返回 `None`
- [ ] `update()` 能修改字段
- [ ] `delete()` 能正确删除任务
- [ ] `PriorityQueue.peek()` 不移除元素
- [ ] 高优先级任务先于低优先级弹出
- [ ] `ToolRegistry.register()` 能登记工具
- [ ] `call()` 能执行已注册工具
- [ ] 低权限调用高权限工具时会报错
- [ ] `pytest demo2_task_agent/tests/test_task_agent.py -q` 通过
- [ ] push 到 `demo2-starter` 后,Actions 通过
25 changes: 25 additions & 0 deletions FAQ.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Demo2 FAQ

## 1. `TaskStore` 需要上数据库吗?

不用。
这一关先把内存存储和数据结构设计清楚更重要。

## 2. 优先级队列一定要用堆吗?

不一定。
只要能稳定体现“高优先级先出”的行为,先通过测试再优化实现。

## 3. 权限校验做多复杂合适?

这关不需要做企业级权限系统。
能区分 `read/write/admin` 这类级别就够了。

## 4. 为什么有时候三个模块都写了,测试还是不过?

常见原因是三个模块之间的数据格式不统一。
先确认任务对象结构是否一致。

## 5. 怎么排查 `PriorityQueue` 的顺序问题?

先只放 2 到 3 个任务手动测试,观察 `peek()` 和 `pop()` 的结果是否符合预期。
21 changes: 21 additions & 0 deletions HINTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Demo2 HINTS

只给思路,不给答案。

## 你可以先想清楚的点

- 这一关其实是 3 个独立模块:任务存储、优先级队列、工具注册
- 先把数据结构设计统一,后面实现会顺很多
- `ToolRegistry` 的核心不是花哨,而是“注册 + 权限校验 + 调用”

## 容易卡住的地方

- 任务对象字段尽量固定:`id/title/priority/due_date`
- 队列的重点是“高优先级先出来”,不一定非要复杂实现
- 权限校验不需要设计太重,满足测试即可

## 实现顺序建议

1. `TaskStore`
2. `PriorityQueue`
3. `ToolRegistry`
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@
<strong>Agent</strong> · <strong>ML</strong> · <strong>FastAPI</strong> · <strong>RAG</strong> · <strong>SSE</strong> · <strong>CI Unlock Flow</strong>
</p>

## 当前分支

你现在位于 `demo2-starter`。

- Demo1 可以作为已完成参考继续复用
- 当前需要自己完成 `demo2_task_agent/` 的工具注册、任务存储和优先级队列
- 通过本关后,push 到 `demo2-starter` 会自动解锁 Demo3
- 完整答案仍然保留在 `main` 分支

## 学习入口

- 先读 [TODO.md](TODO.md)
- 卡住时看 [HINTS.md](HINTS.md)
- 易错点和排查看 [FAQ.md](FAQ.md)
- 提交前对照 [CHECKLIST.md](CHECKLIST.md)
- 完成后回看 [REFLECTION.md](REFLECTION.md)
- 再看 `demo2_task_agent/tests/test_task_agent.py`
- 当前关最重要的三个文件是:`tool_registry.py`、`task_store.py`、`priority_queue.py`

## 建议实现步骤

1. 先实现 `TaskStore`。
2. 再实现 `PriorityQueue`。
3. 最后实现 `ToolRegistry` 的权限校验。

## 快速导航

- [在线演示](#在线演示)
Expand Down
13 changes: 13 additions & 0 deletions REFLECTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Demo2 REFLECTION

学完这一关,你应该能说清楚这些事:

- 工具注册表为什么是 Agent 工程化里很重要的一层
- 为什么需要权限控制,而不是直接随意调用函数
- 任务 CRUD 和优先级队列如何配合形成任务系统
- 为什么多模块之间统一数据结构很关键

如果你已经完成本关,说明你已经具备:

- 搭建一个简单任务型 Agent 底座的能力
- 为后续服务化和路由集成做准备的能力
31 changes: 31 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Demo2 TODO

当前目标:补齐任务型 Agent 的基础组件。

## 你需要实现的文件

- `demo2_task_agent/tool_registry.py`
- `demo2_task_agent/task_store.py`
- `demo2_task_agent/priority_queue.py`

## 建议实现步骤

1. 先实现 `TaskStore.add/get/get_all/update/delete`。
2. 给任务对象设计一个统一结构,至少包含 `id/title/priority/due_date`。
3. 实现 `PriorityQueue.push/pop/peek`,确保高优先级先出。
4. 最后实现 `ToolRegistry.register/call`。
5. 在 `call()` 里加最小权限判断逻辑。
6. 跑测试确认三个模块都能协同工作。

## 完成标准

- `pytest demo2_task_agent/tests/test_task_agent.py -q` 通过
- 推送到 `demo2-starter` 后,GitHub Actions 成功运行
- Issues 页面出现 “Demo3 已解锁”

## 卡住时看哪里

- 已完成的 `demo1_cli_agent/`
- 当前分支的 `README.md`
- `docs/demo_specs.md`
- 完整答案在 `main` 分支
28 changes: 11 additions & 17 deletions demo2_task_agent/priority_queue.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
from __future__ import annotations

import heapq
from typing import List, Tuple


class PriorityQueue:
_priority_rank = {"high": 0, "mid": 1, "medium": 1, "low": 2}
"""Demo2 starter: use heapq or another structure to sort tasks."""

def __init__(self) -> None:
self._heap: List[Tuple[int, int, dict]] = []
self._counter = 0
self._items = []

def push(self, task: dict) -> None:
rank = self._priority_rank.get(task.get("priority", "low"), 2)
heapq.heappush(self._heap, (rank, self._counter, task))
self._counter += 1
def push(self, task):
"""TODO: insert task according to priority."""
raise NotImplementedError("Implement PriorityQueue.push for Demo2")

def pop(self) -> dict:
return heapq.heappop(self._heap)[2]
def pop(self):
"""TODO: pop the highest-priority task."""
raise NotImplementedError("Implement PriorityQueue.pop for Demo2")

def peek(self) -> dict:
return self._heap[0][2]
def peek(self):
"""TODO: read the current highest-priority task without removing it."""
raise NotImplementedError("Implement PriorityQueue.peek for Demo2")
51 changes: 18 additions & 33 deletions demo2_task_agent/task_store.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
from __future__ import annotations

from dataclasses import dataclass, asdict
from typing import Dict, List, Optional
from uuid import uuid4


@dataclass
class Task:
id: str
title: str
priority: str
due_date: str


class TaskStore:
"""Demo2 starter: implement an in-memory task store."""

def __init__(self) -> None:
self._tasks: Dict[str, Task] = {}
self._tasks = {}

def add(self, title: str, priority: str, due_date: str) -> dict:
task = Task(id=str(uuid4()), title=title, priority=priority, due_date=due_date)
self._tasks[task.id] = task
return asdict(task)
def add(self, title, priority, due_date):
"""TODO: create a task dict with a unique id and store it."""
raise NotImplementedError("Implement TaskStore.add for Demo2")

def get_all(self) -> List[dict]:
return [asdict(task) for task in self._tasks.values()]
def get_all(self):
"""TODO: return every task as a list."""
raise NotImplementedError("Implement TaskStore.get_all for Demo2")

def get(self, task_id: str) -> Optional[dict]:
task = self._tasks.get(task_id)
return asdict(task) if task else None
def get(self, task_id):
"""TODO: return one task or None."""
raise NotImplementedError("Implement TaskStore.get for Demo2")

def update(self, task_id: str, **kwargs) -> dict:
task = self._tasks[task_id]
for field in ("title", "priority", "due_date"):
if field in kwargs and kwargs[field] is not None:
setattr(task, field, kwargs[field])
return asdict(task)
def update(self, task_id, **kwargs):
"""TODO: update title / priority / due_date."""
raise NotImplementedError("Implement TaskStore.update for Demo2")

def delete(self, task_id: str) -> bool:
return self._tasks.pop(task_id, None) is not None
def delete(self, task_id):
"""TODO: remove the task and return whether it existed."""
raise NotImplementedError("Implement TaskStore.delete for Demo2")
52 changes: 15 additions & 37 deletions demo2_task_agent/tool_registry.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, Dict


class ToolRegistryError(Exception):
pass


class PermissionDeniedError(ToolRegistryError):
pass


class ToolNotFoundError(ToolRegistryError):
pass


@dataclass
class ToolSpec:
func: Callable[..., Any]
permission: str


class ToolRegistry:
_permission_order = {"read": 1, "write": 2, "admin": 3}
"""Demo2 starter: build your own registry with permission checks."""

def __init__(self) -> None:
self._tools: Dict[str, ToolSpec] = {}

def register(self, name: str, func: Callable[..., Any], permission: str) -> None:
self._tools[name] = ToolSpec(func=func, permission=permission)

def call(self, name: str, args: Dict[str, Any], ctx_permission: str) -> Any:
if name not in self._tools:
raise ToolNotFoundError(name)
spec = self._tools[name]
if self._permission_order.get(ctx_permission, 0) < self._permission_order.get(spec.permission, 0):
raise PermissionDeniedError(f"{ctx_permission} cannot call {name}")
return spec.func(**args)
self._tools = {}

def register(self, name, func, permission):
"""TODO: save tool metadata into the registry."""
raise NotImplementedError("Implement ToolRegistry.register for Demo2")

def call(self, name, args, ctx_permission):
"""
TODO:
1. check whether the tool exists
2. validate permission level
3. execute the tool with args
"""
raise NotImplementedError("Implement ToolRegistry.call for Demo2")
26 changes: 0 additions & 26 deletions demo3_ml_visual/data_loader.py

This file was deleted.

24 changes: 0 additions & 24 deletions demo3_ml_visual/feature_engineer.py

This file was deleted.

20 changes: 0 additions & 20 deletions demo3_ml_visual/main.py

This file was deleted.

11 changes: 0 additions & 11 deletions demo3_ml_visual/model_io.py

This file was deleted.

Loading