step B.2 complete: all 23 tools use execute_on_client(); add embed() to llm
This commit is contained in:
@@ -97,11 +97,11 @@ Tools must use **camelCase** field names (Drizzle maps them to snake_case intern
|
|||||||
- **Outcome:** Any tool can `await execute_on_client(...)` to query/mutate the user's local DB.
|
- **Outcome:** Any tool can `await execute_on_client(...)` to query/mutate the user's local DB.
|
||||||
|
|
||||||
### Step B.2 — Rewrite all 23 tools to use `execute_on_client()`
|
### Step B.2 — Rewrite all 23 tools to use `execute_on_client()`
|
||||||
- [ ] Each tool: same `@tool` decorator, same parameters, same docstring. Replace `return json.dumps({...})` body with:
|
- [x] Each tool: same `@tool` decorator, same parameters, same docstring. Replace `return json.dumps({...})` body with:
|
||||||
1. Call `result = await execute_on_client(action=..., table=..., data/filters=...)`
|
1. Call `result = await execute_on_client(action=..., table=..., data/filters=...)`
|
||||||
2. Return human-readable string with confirmation + key data from `result`
|
2. Return human-readable string with confirmation + key data from `result`
|
||||||
|
|
||||||
- [ ] **`app/agents/task_agent.py` (8 tools):**
|
- [x] **`app/agents/task_agent.py` (8 tools):**
|
||||||
- `list_tasks(project_id, status, search, order_by)`:
|
- `list_tasks(project_id, status, search, order_by)`:
|
||||||
```python
|
```python
|
||||||
result = await execute_on_client(action="select", table="tasks", filters={
|
result = await execute_on_client(action="select", table="tasks", filters={
|
||||||
@@ -133,7 +133,7 @@ Tools must use **camelCase** field names (Drizzle maps them to snake_case intern
|
|||||||
- `add_task_comment(task_id, author, content)`: `execute_on_client(action="insert", table="taskComments", data={...})` → return confirmation
|
- `add_task_comment(task_id, author, content)`: `execute_on_client(action="insert", table="taskComments", data={...})` → return confirmation
|
||||||
- `delete_task_comment(comment_id)`: `execute_on_client(action="delete", table="taskComments", data={"id": comment_id})` → return confirmation
|
- `delete_task_comment(comment_id)`: `execute_on_client(action="delete", table="taskComments", data={"id": comment_id})` → return confirmation
|
||||||
|
|
||||||
- [ ] **`app/agents/project_agent.py` (6 tools):**
|
- [x] **`app/agents/project_agent.py` (6 tools):**
|
||||||
- `list_projects(client_id, include_archived)`: `execute_on_client(action="select", table="projects", filters={clientId, includeArchived})` → format + return
|
- `list_projects(client_id, include_archived)`: `execute_on_client(action="select", table="projects", filters={clientId, includeArchived})` → format + return
|
||||||
- `list_all_projects()`: `execute_on_client(action="select", table="projects")` → format + return
|
- `list_all_projects()`: `execute_on_client(action="select", table="projects")` → format + return
|
||||||
- `get_project(project_id)`: `execute_on_client(action="get", table="projects", data={"id": project_id})` → return project details or "not found"
|
- `get_project(project_id)`: `execute_on_client(action="get", table="projects", data={"id": project_id})` → return project details or "not found"
|
||||||
@@ -141,13 +141,13 @@ Tools must use **camelCase** field names (Drizzle maps them to snake_case intern
|
|||||||
- `update_project(project_id, ...)`: build updates → `execute_on_client(action="update", ...)` → return confirmation
|
- `update_project(project_id, ...)`: build updates → `execute_on_client(action="update", ...)` → return confirmation
|
||||||
- `delete_project(project_id)`: `execute_on_client(action="delete", ...)` → return confirmation
|
- `delete_project(project_id)`: `execute_on_client(action="delete", ...)` → return confirmation
|
||||||
|
|
||||||
- [ ] **`app/agents/checkpoint_agent.py` (4 tools):**
|
- [x] **`app/agents/checkpoint_agent.py` (4 tools):**
|
||||||
- `list_checkpoints(project_id)`: `execute_on_client(action="select", table="checkpoints", filters={projectId})` → format + return
|
- `list_checkpoints(project_id)`: `execute_on_client(action="select", table="checkpoints", filters={projectId})` → format + return
|
||||||
- `create_checkpoint(project_id, title, date, ...)`: `execute_on_client(action="insert", table="checkpoints", data={...})` → return confirmation + id
|
- `create_checkpoint(project_id, title, date, ...)`: `execute_on_client(action="insert", table="checkpoints", data={...})` → return confirmation + id
|
||||||
- `update_checkpoint(checkpoint_id, ...)`: build updates → `execute_on_client(action="update", ...)` → return confirmation
|
- `update_checkpoint(checkpoint_id, ...)`: build updates → `execute_on_client(action="update", ...)` → return confirmation
|
||||||
- `delete_checkpoint(checkpoint_id)`: `execute_on_client(action="delete", ...)` → return confirmation
|
- `delete_checkpoint(checkpoint_id)`: `execute_on_client(action="delete", ...)` → return confirmation
|
||||||
|
|
||||||
- [ ] **`app/agents/note_agent.py` (5 tools):**
|
- [x] **`app/agents/note_agent.py` (5 tools):**
|
||||||
- `list_notes(project_id)`: `execute_on_client(action="select", table="notes", filters={projectId})` → format + return
|
- `list_notes(project_id)`: `execute_on_client(action="select", table="notes", filters={projectId})` → format + return
|
||||||
- `get_note(note_id)`: `execute_on_client(action="get", table="notes", data={"id": note_id})` → return full content or "not found"
|
- `get_note(note_id)`: `execute_on_client(action="get", table="notes", data={"id": note_id})` → return full content or "not found"
|
||||||
- `create_note(title, content, project_id)`: `execute_on_client(action="insert", table="notes", data={...})` → then `execute_on_client(action="vector_upsert", data={id, projectId, content}, vector=await embed(content))` → return confirmation
|
- `create_note(title, content, project_id)`: `execute_on_client(action="insert", table="notes", data={...})` → then `execute_on_client(action="vector_upsert", data={id, projectId, content}, vector=await embed(content))` → return confirmation
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
@@ -10,6 +9,7 @@ from langchain_core.tools import tool
|
|||||||
|
|
||||||
from app.core.agent_registry import ChatAgent, registry
|
from app.core.agent_registry import ChatAgent, registry
|
||||||
from app.core.llm import get_llm
|
from app.core.llm import get_llm
|
||||||
|
from app.core.ws_context import execute_on_client
|
||||||
|
|
||||||
_SYSTEM_PROMPT = (
|
_SYSTEM_PROMPT = (
|
||||||
"You are a project checkpoint assistant. Checkpoints are milestone dates that\n"
|
"You are a project checkpoint assistant. Checkpoints are milestone dates that\n"
|
||||||
@@ -28,11 +28,16 @@ _SYSTEM_PROMPT = (
|
|||||||
@tool
|
@tool
|
||||||
async def list_checkpoints(project_id: str = "") -> str:
|
async def list_checkpoints(project_id: str = "") -> str:
|
||||||
"""List checkpoints. Provide project_id to scope to a specific project."""
|
"""List checkpoints. Provide project_id to scope to a specific project."""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "list",
|
action="select",
|
||||||
"table": "checkpoints",
|
table="checkpoints",
|
||||||
"filters": {"projectId": project_id or None},
|
filters={"projectId": project_id or None},
|
||||||
})
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return "No checkpoints found."
|
||||||
|
lines = [f"- {r['title']} (date: {r['date']}, id: {r['id']})" for r in rows]
|
||||||
|
return f"Found {len(rows)} checkpoint(s):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -50,17 +55,19 @@ async def create_checkpoint(
|
|||||||
is_ai_suggested: 1 if proactively suggested, 0 if user-requested
|
is_ai_suggested: 1 if proactively suggested, 0 if user-requested
|
||||||
is_approved: 0 until the user confirms
|
is_approved: 0 until the user confirms
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "create_record",
|
action="insert",
|
||||||
"table": "checkpoints",
|
table="checkpoints",
|
||||||
"data": {
|
data={
|
||||||
"projectId": project_id,
|
"projectId": project_id,
|
||||||
"title": title,
|
"title": title,
|
||||||
"date": date,
|
"date": date,
|
||||||
"isAiSuggested": is_ai_suggested,
|
"isAiSuggested": is_ai_suggested,
|
||||||
"isApproved": is_approved,
|
"isApproved": is_approved,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
return f"Checkpoint created: '{row['title']}' (id: {row['id']}, date: {row['date']})"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -82,21 +89,20 @@ async def update_checkpoint(
|
|||||||
updates["date"] = date
|
updates["date"] = date
|
||||||
if is_approved != -1:
|
if is_approved != -1:
|
||||||
updates["isApproved"] = is_approved
|
updates["isApproved"] = is_approved
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "update_record",
|
action="update",
|
||||||
"table": "checkpoints",
|
table="checkpoints",
|
||||||
"data": {"id": checkpoint_id, "updates": updates},
|
data={"id": checkpoint_id, "updates": updates},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
return f"Checkpoint updated: '{row['title']}' (id: {row['id']})"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def delete_checkpoint(checkpoint_id: str) -> str:
|
async def delete_checkpoint(checkpoint_id: str) -> str:
|
||||||
"""Delete a checkpoint permanently by its UUID."""
|
"""Delete a checkpoint permanently by its UUID."""
|
||||||
return json.dumps({
|
await execute_on_client(action="delete", table="checkpoints", data={"id": checkpoint_id})
|
||||||
"action": "delete_record",
|
return f"Checkpoint {checkpoint_id} deleted."
|
||||||
"table": "checkpoints",
|
|
||||||
"data": {"id": checkpoint_id},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
from langchain_core.tools import tool
|
from langchain_core.tools import tool
|
||||||
|
|
||||||
from app.core.agent_registry import ChatAgent, registry
|
from app.core.agent_registry import ChatAgent, registry
|
||||||
from app.core.llm import get_llm
|
from app.core.llm import embed, get_llm
|
||||||
|
from app.core.ws_context import execute_on_client
|
||||||
|
|
||||||
_SYSTEM_PROMPT = (
|
_SYSTEM_PROMPT = (
|
||||||
"You are a note-taking assistant. You help users create, retrieve, update,\n"
|
"You are a note-taking assistant. You help users create, retrieve, update,\n"
|
||||||
@@ -29,21 +29,26 @@ _SYSTEM_PROMPT = (
|
|||||||
@tool
|
@tool
|
||||||
async def list_notes(project_id: str = "") -> str:
|
async def list_notes(project_id: str = "") -> str:
|
||||||
"""List notes, optionally scoped to a project by project_id."""
|
"""List notes, optionally scoped to a project by project_id."""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "list",
|
action="select",
|
||||||
"table": "notes",
|
table="notes",
|
||||||
"filters": {"projectId": project_id or None},
|
filters={"projectId": project_id or None},
|
||||||
})
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return "No notes found."
|
||||||
|
lines = [f"- {r['title']} (id: {r['id']})" for r in rows]
|
||||||
|
return f"Found {len(rows)} note(s):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def get_note(note_id: str) -> str:
|
async def get_note(note_id: str) -> str:
|
||||||
"""Fetch a single note by its UUID to read its full Markdown content."""
|
"""Fetch a single note by its UUID to read its full Markdown content."""
|
||||||
return json.dumps({
|
result = await execute_on_client(action="get", table="notes", data={"id": note_id})
|
||||||
"action": "get",
|
row = result.get("row")
|
||||||
"table": "notes",
|
if not row:
|
||||||
"data": {"id": note_id},
|
return f"Note {note_id} not found."
|
||||||
})
|
return f"Note '{row['title']}' (id: {row['id']}):\n\n{row['content']}"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -57,15 +62,24 @@ async def create_note(
|
|||||||
content: Markdown body text (required)
|
content: Markdown body text (required)
|
||||||
project_id: optional UUID linking this note to a project
|
project_id: optional UUID linking this note to a project
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "create_record",
|
action="insert",
|
||||||
"table": "notes",
|
table="notes",
|
||||||
"data": {
|
data={
|
||||||
"title": title,
|
"title": title,
|
||||||
"content": content,
|
"content": content,
|
||||||
"projectId": project_id or None,
|
"projectId": project_id or None,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
# Index the note content in the vector store.
|
||||||
|
vector = await embed(content)
|
||||||
|
await execute_on_client(
|
||||||
|
action="vector_upsert",
|
||||||
|
data={"id": row["id"], "projectId": row.get("projectId"), "content": content},
|
||||||
|
vector=vector,
|
||||||
|
)
|
||||||
|
return f"Note created: '{row['title']}' (id: {row['id']})."
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -83,21 +97,28 @@ async def update_note(
|
|||||||
updates["title"] = title
|
updates["title"] = title
|
||||||
if content:
|
if content:
|
||||||
updates["content"] = content
|
updates["content"] = content
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "update_record",
|
action="update",
|
||||||
"table": "notes",
|
table="notes",
|
||||||
"data": {"id": note_id, "updates": updates},
|
data={"id": note_id, "updates": updates},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
# Re-index if content changed.
|
||||||
|
if content:
|
||||||
|
vector = await embed(content)
|
||||||
|
await execute_on_client(
|
||||||
|
action="vector_upsert",
|
||||||
|
data={"id": note_id, "projectId": row.get("projectId"), "content": content},
|
||||||
|
vector=vector,
|
||||||
|
)
|
||||||
|
return f"Note updated: '{row['title']}' (id: {row['id']})."
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def delete_note(note_id: str) -> str:
|
async def delete_note(note_id: str) -> str:
|
||||||
"""Delete a note permanently by its UUID."""
|
"""Delete a note permanently by its UUID."""
|
||||||
return json.dumps({
|
await execute_on_client(action="delete", table="notes", data={"id": note_id})
|
||||||
"action": "delete_record",
|
return f"Note {note_id} deleted."
|
||||||
"table": "notes",
|
|
||||||
"data": {"id": note_id},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
@@ -10,6 +9,7 @@ from langchain_core.tools import tool
|
|||||||
|
|
||||||
from app.core.agent_registry import ChatAgent, registry
|
from app.core.agent_registry import ChatAgent, registry
|
||||||
from app.core.llm import get_llm
|
from app.core.llm import get_llm
|
||||||
|
from app.core.ws_context import execute_on_client
|
||||||
|
|
||||||
_SYSTEM_PROMPT = (
|
_SYSTEM_PROMPT = (
|
||||||
"You are a project management assistant. You help users create, find,\n"
|
"You are a project management assistant. You help users create, find,\n"
|
||||||
@@ -36,14 +36,19 @@ async def list_projects(
|
|||||||
"""List projects, optionally filtered by client_id.
|
"""List projects, optionally filtered by client_id.
|
||||||
include_archived: 1 to include archived projects, 0 for active only (default).
|
include_archived: 1 to include archived projects, 0 for active only (default).
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "list",
|
action="select",
|
||||||
"table": "projects",
|
table="projects",
|
||||||
"filters": {
|
filters={
|
||||||
"clientId": client_id or None,
|
"clientId": client_id or None,
|
||||||
"includeArchived": bool(include_archived),
|
"includeArchived": bool(include_archived),
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return "No projects found."
|
||||||
|
lines = [f"- {r['name']} (status: {r['status']}, id: {r['id']})" for r in rows]
|
||||||
|
return f"Found {len(rows)} project(s):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -51,20 +56,25 @@ async def list_all_projects() -> str:
|
|||||||
"""List every project regardless of client or status.
|
"""List every project regardless of client or status.
|
||||||
Use only when the user wants a complete cross-client overview.
|
Use only when the user wants a complete cross-client overview.
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(action="select", table="projects")
|
||||||
"action": "list_all",
|
rows = result.get("rows", [])
|
||||||
"table": "projects",
|
if not rows:
|
||||||
})
|
return "No projects found."
|
||||||
|
lines = [f"- {r['name']} (status: {r['status']}, id: {r['id']})" for r in rows]
|
||||||
|
return f"All projects ({len(rows)}):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def get_project(project_id: str) -> str:
|
async def get_project(project_id: str) -> str:
|
||||||
"""Fetch a single project by its UUID."""
|
"""Fetch a single project by its UUID."""
|
||||||
return json.dumps({
|
result = await execute_on_client(action="get", table="projects", data={"id": project_id})
|
||||||
"action": "get",
|
row = result.get("row")
|
||||||
"table": "projects",
|
if not row:
|
||||||
"data": {"id": project_id},
|
return f"Project {project_id} not found."
|
||||||
})
|
return (
|
||||||
|
f"Project: '{row['name']}' (id: {row['id']}, status: {row['status']}, "
|
||||||
|
f"clientId: {row.get('clientId', 'none')})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -76,14 +86,13 @@ async def create_project(
|
|||||||
name: human-readable project name (required)
|
name: human-readable project name (required)
|
||||||
client_id: optional UUID of the owning client
|
client_id: optional UUID of the owning client
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "create_record",
|
action="insert",
|
||||||
"table": "projects",
|
table="projects",
|
||||||
"data": {
|
data={"name": name, "clientId": client_id or None},
|
||||||
"name": name,
|
)
|
||||||
"clientId": client_id or None,
|
row = result["row"]
|
||||||
},
|
return f"Project created: '{row['name']}' (id: {row['id']})"
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -108,11 +117,13 @@ async def update_project(
|
|||||||
updates["status"] = status
|
updates["status"] = status
|
||||||
if ai_summary:
|
if ai_summary:
|
||||||
updates["aiSummary"] = ai_summary
|
updates["aiSummary"] = ai_summary
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "update_record",
|
action="update",
|
||||||
"table": "projects",
|
table="projects",
|
||||||
"data": {"id": project_id, "updates": updates},
|
data={"id": project_id, "updates": updates},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
return f"Project updated: '{row['name']}' (id: {row['id']}, status: {row['status']})"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -121,11 +132,8 @@ async def delete_project(project_id: str) -> str:
|
|||||||
IMPORTANT: prefer update_project(status='archived') unless the user
|
IMPORTANT: prefer update_project(status='archived') unless the user
|
||||||
has explicitly confirmed they want permanent deletion.
|
has explicitly confirmed they want permanent deletion.
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
await execute_on_client(action="delete", table="projects", data={"id": project_id})
|
||||||
"action": "delete_record",
|
return f"Project {project_id} permanently deleted."
|
||||||
"table": "projects",
|
|
||||||
"data": {"id": project_id},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@registry.register
|
@registry.register
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from langchain_core.messages import HumanMessage, SystemMessage
|
from langchain_core.messages import HumanMessage, SystemMessage
|
||||||
@@ -10,6 +10,7 @@ from langchain_core.tools import tool
|
|||||||
|
|
||||||
from app.core.agent_registry import ChatAgent, registry
|
from app.core.agent_registry import ChatAgent, registry
|
||||||
from app.core.llm import get_llm
|
from app.core.llm import get_llm
|
||||||
|
from app.core.ws_context import execute_on_client
|
||||||
|
|
||||||
_SYSTEM_PROMPT = (
|
_SYSTEM_PROMPT = (
|
||||||
"You are a task management assistant for a project workspace.\n"
|
"You are a task management assistant for a project workspace.\n"
|
||||||
@@ -41,16 +42,24 @@ async def list_tasks(
|
|||||||
) -> str:
|
) -> str:
|
||||||
"""List tasks, optionally filtered by project_id, status (todo|in_progress|done),
|
"""List tasks, optionally filtered by project_id, status (todo|in_progress|done),
|
||||||
a search string, or an order_by field name (dueDate|priority|createdAt)."""
|
a search string, or an order_by field name (dueDate|priority|createdAt)."""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "list",
|
action="select",
|
||||||
"table": "tasks",
|
table="tasks",
|
||||||
"filters": {
|
filters={
|
||||||
"projectId": project_id or None,
|
"projectId": project_id or None,
|
||||||
"status": status or None,
|
"status": status or None,
|
||||||
"search": search or None,
|
"search": search or None,
|
||||||
"orderBy": order_by or None,
|
"orderBy": order_by or None,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return "No tasks found matching the given filters."
|
||||||
|
lines = [
|
||||||
|
f"- {r['title']} (status: {r['status']}, priority: {r['priority']}, id: {r['id']})"
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
return f"Found {len(rows)} task(s):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -76,10 +85,10 @@ async def create_task(
|
|||||||
is_ai_suggested: 1 if proactively suggested, 0 if user-requested
|
is_ai_suggested: 1 if proactively suggested, 0 if user-requested
|
||||||
is_approved: 0 until the user confirms; 1 when confirmed
|
is_approved: 0 until the user confirms; 1 when confirmed
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "create_record",
|
action="insert",
|
||||||
"table": "tasks",
|
table="tasks",
|
||||||
"data": {
|
data={
|
||||||
"title": title,
|
"title": title,
|
||||||
"description": description or None,
|
"description": description or None,
|
||||||
"status": status,
|
"status": status,
|
||||||
@@ -90,7 +99,12 @@ async def create_task(
|
|||||||
"isAiSuggested": is_ai_suggested,
|
"isAiSuggested": is_ai_suggested,
|
||||||
"isApproved": is_approved,
|
"isApproved": is_approved,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
return (
|
||||||
|
f"Task created: '{row['title']}' "
|
||||||
|
f"(id: {row['id']}, status: {row['status']}, priority: {row['priority']})"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -127,30 +141,41 @@ async def update_task(
|
|||||||
updates["projectId"] = project_id
|
updates["projectId"] = project_id
|
||||||
if is_approved != -1:
|
if is_approved != -1:
|
||||||
updates["isApproved"] = is_approved
|
updates["isApproved"] = is_approved
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "update_record",
|
action="update",
|
||||||
"table": "tasks",
|
table="tasks",
|
||||||
"data": {"id": task_id, "updates": updates},
|
data={"id": task_id, "updates": updates},
|
||||||
})
|
)
|
||||||
|
row = result["row"]
|
||||||
|
return f"Task updated: '{row['title']}' (id: {row['id']}, status: {row['status']})"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def delete_task(task_id: str) -> str:
|
async def delete_task(task_id: str) -> str:
|
||||||
"""Delete a task permanently by its UUID."""
|
"""Delete a task permanently by its UUID."""
|
||||||
return json.dumps({
|
await execute_on_client(action="delete", table="tasks", data={"id": task_id})
|
||||||
"action": "delete_record",
|
return f"Task {task_id} deleted."
|
||||||
"table": "tasks",
|
|
||||||
"data": {"id": task_id},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def list_tasks_due_today() -> str:
|
async def list_tasks_due_today() -> str:
|
||||||
"""List all tasks whose due date falls on today's date."""
|
"""List all tasks whose due date falls on today's date."""
|
||||||
return json.dumps({
|
now = datetime.now(tz=timezone.utc)
|
||||||
"action": "list_due_today",
|
start_ms = int(datetime(now.year, now.month, now.day, tzinfo=timezone.utc).timestamp() * 1000)
|
||||||
"table": "tasks",
|
end_ms = start_ms + 86_400_000 - 1 # last ms of today
|
||||||
})
|
result = await execute_on_client(
|
||||||
|
action="select",
|
||||||
|
table="tasks",
|
||||||
|
filters={"dueDateFrom": start_ms, "dueDateTo": end_ms},
|
||||||
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return "No tasks are due today."
|
||||||
|
lines = [
|
||||||
|
f"- {r['title']} (priority: {r['priority']}, status: {r['status']}, id: {r['id']})"
|
||||||
|
for r in rows
|
||||||
|
]
|
||||||
|
return f"Tasks due today ({len(rows)}):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
# ── Task comment tools ────────────────────────────────────────────────
|
# ── Task comment tools ────────────────────────────────────────────────
|
||||||
@@ -159,11 +184,16 @@ async def list_tasks_due_today() -> str:
|
|||||||
@tool
|
@tool
|
||||||
async def list_task_comments(task_id: str) -> str:
|
async def list_task_comments(task_id: str) -> str:
|
||||||
"""List all comments on a task by its UUID."""
|
"""List all comments on a task by its UUID."""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "list",
|
action="select",
|
||||||
"table": "taskComments",
|
table="taskComments",
|
||||||
"filters": {"taskId": task_id},
|
filters={"taskId": task_id},
|
||||||
})
|
)
|
||||||
|
rows = result.get("rows", [])
|
||||||
|
if not rows:
|
||||||
|
return f"No comments found for task {task_id}."
|
||||||
|
lines = [f"- [{r['author']}]: {r['content']} (id: {r['id']})" for r in rows]
|
||||||
|
return f"Found {len(rows)} comment(s):\n" + "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
@@ -173,25 +203,20 @@ async def add_task_comment(task_id: str, author: str, content: str) -> str:
|
|||||||
author: name or ID of the comment author
|
author: name or ID of the comment author
|
||||||
content: comment text
|
content: comment text
|
||||||
"""
|
"""
|
||||||
return json.dumps({
|
result = await execute_on_client(
|
||||||
"action": "create_record",
|
action="insert",
|
||||||
"table": "taskComments",
|
table="taskComments",
|
||||||
"data": {
|
data={"taskId": task_id, "author": author, "content": content},
|
||||||
"taskId": task_id,
|
)
|
||||||
"author": author,
|
row = result["row"]
|
||||||
"content": content,
|
return f"Comment added by {row['author']} on task {row['taskId']} (comment id: {row['id']})."
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
async def delete_task_comment(comment_id: str) -> str:
|
async def delete_task_comment(comment_id: str) -> str:
|
||||||
"""Delete a task comment by its UUID."""
|
"""Delete a task comment by its UUID."""
|
||||||
return json.dumps({
|
await execute_on_client(action="delete", table="taskComments", data={"id": comment_id})
|
||||||
"action": "delete_record",
|
return f"Comment {comment_id} deleted."
|
||||||
"table": "taskComments",
|
|
||||||
"data": {"id": comment_id},
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# ── Agent ─────────────────────────────────────────────────────────────
|
# ── Agent ─────────────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Switch providers by changing **LLM_MODEL** / **LLM_ROUTER_MODEL** in ``.env``
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from openai import AsyncOpenAI
|
||||||
|
|
||||||
from langchain_openai import ChatOpenAI
|
from langchain_openai import ChatOpenAI
|
||||||
from litellm import get_supported_openai_params # noqa: F401 – validates install
|
from litellm import get_supported_openai_params # noqa: F401 – validates install
|
||||||
|
|
||||||
@@ -66,3 +68,13 @@ def get_router_llm(
|
|||||||
) -> ChatOpenAI:
|
) -> ChatOpenAI:
|
||||||
"""Return the lighter model used for intent classification / routing."""
|
"""Return the lighter model used for intent classification / routing."""
|
||||||
return get_llm(model=settings.LLM_ROUTER_MODEL, temperature=temperature)
|
return get_llm(model=settings.LLM_ROUTER_MODEL, temperature=temperature)
|
||||||
|
|
||||||
|
|
||||||
|
async def embed(text: str) -> list[float]:
|
||||||
|
"""Return a 1536-dim embedding vector for *text* using text-embedding-3-small."""
|
||||||
|
client = AsyncOpenAI(api_key=settings.OPENAI_API_KEY)
|
||||||
|
response = await client.embeddings.create(
|
||||||
|
model="text-embedding-3-small",
|
||||||
|
input=text,
|
||||||
|
)
|
||||||
|
return response.data[0].embedding
|
||||||
|
|||||||
Reference in New Issue
Block a user