- Replaced direct instantiation of ChatOpenAI with a centralized get_llm function in CheckpointAgent, NoteAgent, ProjectAgent, and TaskAgent. - Introduced a new llm.py module to handle LLM model instantiation and API key management. - Updated settings.py to include LLM_MODEL and LLM_ROUTER_MODEL configurations. - Modified orchestrator.py to use get_router_llm for intent classification. - Updated requirements.txt to include litellm for LLM management. - Adjusted tests to mock get_llm instead of ChatOpenAI directly.
123 lines
3.5 KiB
Python
123 lines
3.5 KiB
Python
"""Note agent — Markdown note management (list, get, create, update, delete)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import Any
|
|
|
|
from langchain_core.messages import HumanMessage, SystemMessage
|
|
from langchain_core.tools import tool
|
|
|
|
from app.core.agent_registry import ChatAgent, registry
|
|
from app.core.llm import get_llm
|
|
|
|
_SYSTEM_PROMPT = (
|
|
"You are a note-taking assistant. You help users create, retrieve, update,\n"
|
|
"and delete Markdown notes in their workspace.\n\n"
|
|
"Rules:\n"
|
|
" - content is always Markdown; preserve formatting when updating\n"
|
|
" - project_id is optional; link a note to a project when mentioned\n"
|
|
" - When updating, call get_note first if you need to read existing content\n"
|
|
" before appending or replacing sections\n"
|
|
" - list_notes without project_id returns all notes; scope with project_id\n"
|
|
" when the user is working within a specific project\n"
|
|
" - Do not fabricate note content — reflect what the user provides or what\n"
|
|
" is already in the note (retrieved via get_note)."
|
|
)
|
|
|
|
|
|
@tool
|
|
async def list_notes(project_id: str = "") -> str:
|
|
"""List notes, optionally scoped to a project by project_id."""
|
|
return json.dumps({
|
|
"action": "list",
|
|
"table": "notes",
|
|
"filters": {"projectId": project_id or None},
|
|
})
|
|
|
|
|
|
@tool
|
|
async def get_note(note_id: str) -> str:
|
|
"""Fetch a single note by its UUID to read its full Markdown content."""
|
|
return json.dumps({
|
|
"action": "get",
|
|
"table": "notes",
|
|
"data": {"id": note_id},
|
|
})
|
|
|
|
|
|
@tool
|
|
async def create_note(
|
|
title: str,
|
|
content: str,
|
|
project_id: str = "",
|
|
) -> str:
|
|
"""Create a new note.
|
|
title: note heading (required)
|
|
content: Markdown body text (required)
|
|
project_id: optional UUID linking this note to a project
|
|
"""
|
|
return json.dumps({
|
|
"action": "create_record",
|
|
"table": "notes",
|
|
"data": {
|
|
"title": title,
|
|
"content": content,
|
|
"projectId": project_id or None,
|
|
},
|
|
})
|
|
|
|
|
|
@tool
|
|
async def update_note(
|
|
note_id: str,
|
|
title: str = "",
|
|
content: str = "",
|
|
) -> str:
|
|
"""Update an existing note. Only pass fields that should change.
|
|
note_id: UUID of the note (required)
|
|
If you need to preserve existing content, call get_note first.
|
|
"""
|
|
updates: dict[str, Any] = {}
|
|
if title:
|
|
updates["title"] = title
|
|
if content:
|
|
updates["content"] = content
|
|
return json.dumps({
|
|
"action": "update_record",
|
|
"table": "notes",
|
|
"data": {"id": note_id, "updates": updates},
|
|
})
|
|
|
|
|
|
@tool
|
|
async def delete_note(note_id: str) -> str:
|
|
"""Delete a note permanently by its UUID."""
|
|
return json.dumps({
|
|
"action": "delete_record",
|
|
"table": "notes",
|
|
"data": {"id": note_id},
|
|
})
|
|
|
|
|
|
@registry.register
|
|
class NoteAgent(ChatAgent):
|
|
def get_name(self) -> str:
|
|
return "note_agent"
|
|
|
|
def get_description(self) -> str:
|
|
return "Manages notes: list, get, create, update, delete"
|
|
|
|
def get_tools(self) -> list[Any]:
|
|
return [list_notes, get_note, create_note, update_note, delete_note]
|
|
|
|
async def handle(self, query: str, context: dict[str, Any]) -> str:
|
|
llm = get_llm()
|
|
messages = [
|
|
SystemMessage(content=_SYSTEM_PROMPT),
|
|
HumanMessage(
|
|
content=f"User query: {query}\nContext: {json.dumps(context)[:1000]}"
|
|
),
|
|
]
|
|
return await self._tool_loop(llm, messages, self.get_tools())
|