feat: Agent System Prompts MVP (B) - database, backend API, and frontend integration
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import {
|
||||
AgentSystemPrompts,
|
||||
PromptKind,
|
||||
updateAgentPrompt
|
||||
} from '@/lib/agent-dashboard';
|
||||
import { useAgentPrompts } from '@/hooks/useAgentPrompts';
|
||||
|
||||
interface AgentSystemPromptsCardProps {
|
||||
agentId: string;
|
||||
systemPrompts?: AgentSystemPrompts;
|
||||
systemPrompts?: AgentSystemPrompts; // Legacy/Initial data
|
||||
canEdit?: boolean;
|
||||
onUpdated?: () => void;
|
||||
}
|
||||
@@ -43,17 +44,56 @@ const PROMPT_KINDS: { id: PromptKind; label: string; icon: string; description:
|
||||
|
||||
export function AgentSystemPromptsCard({
|
||||
agentId,
|
||||
systemPrompts,
|
||||
systemPrompts: initialPrompts,
|
||||
canEdit = false,
|
||||
onUpdated
|
||||
}: AgentSystemPromptsCardProps) {
|
||||
const { prompts: fetchedPromptsList, mutate } = useAgentPrompts(agentId);
|
||||
|
||||
// Transform list to dict structure
|
||||
const systemPrompts = useMemo(() => {
|
||||
if (!fetchedPromptsList || fetchedPromptsList.length === 0) {
|
||||
return initialPrompts || {};
|
||||
}
|
||||
|
||||
const dict: AgentSystemPrompts = {
|
||||
core: null,
|
||||
safety: null,
|
||||
governance: null,
|
||||
tools: null
|
||||
};
|
||||
|
||||
fetchedPromptsList.forEach(p => {
|
||||
if (p.kind in dict) {
|
||||
dict[p.kind] = {
|
||||
content: p.content,
|
||||
version: p.version,
|
||||
updated_at: p.updated_at || new Date().toISOString(),
|
||||
updated_by: 'system' // Not returned by all endpoints yet
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return dict;
|
||||
}, [fetchedPromptsList, initialPrompts]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState<PromptKind>('core');
|
||||
const [editedContent, setEditedContent] = useState<Record<PromptKind, string>>({
|
||||
core: systemPrompts?.core?.content || '',
|
||||
safety: systemPrompts?.safety?.content || '',
|
||||
governance: systemPrompts?.governance?.content || '',
|
||||
tools: systemPrompts?.tools?.content || ''
|
||||
core: '',
|
||||
safety: '',
|
||||
governance: '',
|
||||
tools: ''
|
||||
});
|
||||
|
||||
// Sync edited content when active tab or prompts change
|
||||
useEffect(() => {
|
||||
const current = systemPrompts?.[activeTab];
|
||||
setEditedContent(prev => ({
|
||||
...prev,
|
||||
[activeTab]: current?.content || ''
|
||||
}));
|
||||
}, [activeTab, systemPrompts]);
|
||||
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [saveStatus, setSaveStatus] = useState<'idle' | 'success' | 'error'>('idle');
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -71,6 +111,7 @@ export function AgentSystemPromptsCard({
|
||||
|
||||
try {
|
||||
await updateAgentPrompt(agentId, activeTab, currentContent);
|
||||
await mutate(); // Refresh data
|
||||
setSaveStatus('success');
|
||||
onUpdated?.();
|
||||
|
||||
|
||||
38
apps/web/src/hooks/useAgentPrompts.ts
Normal file
38
apps/web/src/hooks/useAgentPrompts.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import useSWR from 'swr';
|
||||
|
||||
export type PromptKind = 'core' | 'safety' | 'governance' | 'tools';
|
||||
|
||||
export interface AgentPrompt {
|
||||
id?: string;
|
||||
kind: PromptKind;
|
||||
content: string;
|
||||
version: number;
|
||||
updated_at?: string;
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export interface AgentPromptList {
|
||||
agent_id: string;
|
||||
prompts: AgentPrompt[];
|
||||
}
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then((res) => {
|
||||
if (!res.ok) throw new Error('Failed to fetch prompts');
|
||||
return res.json();
|
||||
});
|
||||
|
||||
export function useAgentPrompts(agentId?: string) {
|
||||
const { data, error, isLoading, mutate } = useSWR<AgentPromptList>(
|
||||
agentId ? `/api/v1/agents/${agentId}/prompts` : null,
|
||||
fetcher
|
||||
);
|
||||
|
||||
return {
|
||||
prompts: data?.prompts || [],
|
||||
agentId: data?.agent_id,
|
||||
isLoading,
|
||||
error,
|
||||
mutate,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -265,12 +265,17 @@ export async function updateAgentPrompt(
|
||||
content: string,
|
||||
note?: string
|
||||
): Promise<UpdatePromptResult> {
|
||||
// Use new bulk upsert endpoint
|
||||
const response = await fetch(
|
||||
`/api/agents/${encodeURIComponent(agentId)}/prompts/${encodeURIComponent(kind)}`,
|
||||
`/api/v1/agents/${encodeURIComponent(agentId)}/prompts`,
|
||||
{
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content, note })
|
||||
body: JSON.stringify({
|
||||
prompts: [
|
||||
{ kind, content, note }
|
||||
]
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
@@ -279,7 +284,21 @@ export async function updateAgentPrompt(
|
||||
throw new Error(body?.error || 'Failed to update prompt');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
// Map response (list) to singular result for compatibility
|
||||
const data = await response.json(); // AgentPromptList
|
||||
const updated = data.prompts.find((p: any) => p.kind === kind);
|
||||
|
||||
if (!updated) {
|
||||
throw new Error('Updated prompt not returned');
|
||||
}
|
||||
|
||||
return {
|
||||
agent_id: data.agent_id,
|
||||
kind: updated.kind,
|
||||
version: updated.version,
|
||||
updated_at: updated.updated_at,
|
||||
updated_by: updated.created_by || 'unknown'
|
||||
};
|
||||
}
|
||||
|
||||
export async function getPromptHistory(agentId: string, kind: PromptKind): Promise<{
|
||||
|
||||
@@ -1,214 +1,217 @@
|
||||
# TASK_PHASE_AGENT_SYSTEM_PROMPTS_MVP_v1
|
||||
|
||||
## Проєкт
|
||||
|
||||
microdao-daarion (MVP DAARION.city)
|
||||
|
||||
## Статус
|
||||
|
||||
✅ **COMPLETED** — 2025-11-30
|
||||
## Проєкт microdao-daarion (MVP DAARION.city)
|
||||
|
||||
## Мета
|
||||
|
||||
Зробити так, щоб системні промти агентів:
|
||||
- зберігались у реальній БД (`agent_prompts` таблиця)
|
||||
- завантажувались через API
|
||||
- редагувалися через UI на сторінці `/agents/:slug` (вкладка System Prompts)
|
||||
- зберігались у реальній БД,
|
||||
- завантажувались через API,
|
||||
- редагувалися через UI на сторінці `/agents/:slug` (вкладка System Prompts).
|
||||
|
||||
Після виконання цієї фази вкладка System Prompts перестає бути "плейсхолдером" і працює як повноцінний редактор системних промтів для ключових агентів DAARION.city.
|
||||
Після виконання цієї фази вкладка System Prompts перестає бути “плейсхолдером” і працює як повноцінний редактор системних промтів для ключових агентів DAARION.city.
|
||||
|
||||
---
|
||||
|
||||
## Виконані роботи
|
||||
## Scope
|
||||
|
||||
### 1. Аналіз проблеми
|
||||
- **Backend:**
|
||||
- Схема БД для `agent_prompts`.
|
||||
- Repo-методи для читання/запису.
|
||||
- API `GET/PUT /api/v1/agents/{agent_id}/prompts`.
|
||||
- RBAC-перевірки (хто може читати/редагувати).
|
||||
|
||||
**Причина порожніх промтів:**
|
||||
- Backend routes (`routes_city.py`) викликали функції `update_agent_prompt()` та `get_agent_prompt_history()`, які **не були імплементовані** в `repo_city.py`
|
||||
- Функція `get_agent_prompts()` вже існувала і правильно повертала дані
|
||||
- **Frontend:**
|
||||
- Підключення вкладки System Prompts до API.
|
||||
- Редагування й збереження промтів.
|
||||
|
||||
**Структура, яка вже працювала:**
|
||||
- ✅ Міграція `016_agent_prompts.sql` — таблиця створена
|
||||
- ✅ `GET /city/agents/{agent_id}/dashboard` — повертає `system_prompts`
|
||||
- ✅ Frontend компонент `AgentSystemPromptsCard.tsx`
|
||||
- ✅ Next.js API routes proxy
|
||||
- **Seeds:**
|
||||
- Початкові промти для ключових агентів (DAARWIZZ, DARIA, DARIO, Spirit, Logic, SOUL, Helion, GREENFOOD).
|
||||
|
||||
### 2. Backend: Додані функції в `repo_city.py`
|
||||
|
||||
#### `update_agent_prompt(agent_id, kind, content, created_by, note)`
|
||||
- Деактивує попередню версію промта
|
||||
- Створює нову версію з інкрементованим номером
|
||||
- Повертає оновлений запис
|
||||
|
||||
#### `get_agent_prompt_history(agent_id, kind, limit)`
|
||||
- Повертає історію всіх версій промту
|
||||
- Впорядковано по версії (DESC)
|
||||
|
||||
**Файл:** `services/city-service/repo_city.py` (рядки ~628-705)
|
||||
|
||||
### 3. Seed Data: Міграція `034_agent_prompts_seed.sql`
|
||||
|
||||
Створено детальні системні промти для ключових агентів:
|
||||
|
||||
| Агент | Промти | Роль |
|
||||
|-------|--------|------|
|
||||
| DAARWIZZ | core, safety, governance | City Mayor / Orchestrator |
|
||||
| DARIA | core, safety | Technical Support |
|
||||
| DARIO | core | Community Manager |
|
||||
| SOUL | core, safety | District Lead (Wellness) |
|
||||
| Spirit | core | Guidance Agent |
|
||||
| Logic | core | Information Agent |
|
||||
| Helion | core, safety, tools | District Lead (Energy) |
|
||||
| GREENFOOD | core, safety | District Lead (Supply-Chain) |
|
||||
- **Docs:**
|
||||
- Оновлення OpenAPI.
|
||||
- Цей task-файл.
|
||||
|
||||
---
|
||||
|
||||
## API Reference
|
||||
## 1. Аналіз поточного стану
|
||||
|
||||
### Отримати всі промти агента
|
||||
```
|
||||
GET /city/agents/{agent_id}/dashboard
|
||||
```
|
||||
Повертає `system_prompts` об'єкт з 4 типами: core, safety, governance, tools
|
||||
1. Перевірити існуючий код:
|
||||
- Frontend:
|
||||
- `apps/web/src/app/agents/[agentSlug]/(tabs)/system-prompts`
|
||||
- Backend:
|
||||
- `routes_agents.py`
|
||||
- `repo_city.py` (або окремий репозиторій для агентів)
|
||||
- Data Model / API:
|
||||
- `microdao — Data Model & Event Catalog`
|
||||
- `microdao — API Specification (OpenAPI 3.1, MVP)`
|
||||
|
||||
### Оновити промт
|
||||
```
|
||||
PUT /city/agents/{agent_id}/prompts/{kind}
|
||||
Content-Type: application/json
|
||||
2. Виявити, звідки зараз беруться (або не беруться) дані для System Prompts:
|
||||
- чи є тимчасові константи,
|
||||
- чи є неіснуючий API-виклик,
|
||||
- чи вкладка взагалі пустить без fetch.
|
||||
|
||||
{
|
||||
"content": "New prompt content...",
|
||||
"note": "Optional change note"
|
||||
}
|
||||
```
|
||||
|
||||
### Отримати історію промту
|
||||
```
|
||||
GET /city/agents/{agent_id}/prompts/{kind}/history?limit=10
|
||||
```
|
||||
3. Зробити короткий коментар у цьому файлі (або окремій нотатці) — що саме було причиною “порожніх” промтів.
|
||||
|
||||
---
|
||||
|
||||
## Схема БД: `agent_prompts`
|
||||
## 2. Схема БД: `agent_prompts`
|
||||
|
||||
### 2.1. Додати таблицю
|
||||
Створити міграцію для нової таблиці:
|
||||
```sql
|
||||
CREATE TABLE agent_prompts (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
agent_id text NOT NULL,
|
||||
kind text NOT NULL CHECK (kind IN ('core', 'safety', 'governance', 'tools')),
|
||||
content text NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
created_by text,
|
||||
note text,
|
||||
is_active boolean NOT NULL DEFAULT true
|
||||
create table agent_prompts (
|
||||
id text primary key,
|
||||
agent_id text not null references agents(id) on delete cascade,
|
||||
kind text not null check (kind in ('core','safety','governance','tools')),
|
||||
content text not null,
|
||||
version integer not null default 1,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
```
|
||||
|
||||
**Індекси:**
|
||||
- `idx_agent_prompts_agent_kind` — пошук активних промтів
|
||||
- `idx_agent_prompts_agent_created_at` — сортування по часу
|
||||
- `idx_agent_prompts_active` — фільтр активних
|
||||
create unique index ux_agent_prompts_agent_kind on agent_prompts(agent_id, kind);
|
||||
create index ix_agent_prompts_agent on agent_prompts(agent_id);
|
||||
```
|
||||
MVP: тримаємо один активний запис на `(agent_id, kind)`.
|
||||
|
||||
### 2.2. Інтеграція з Data Model
|
||||
Оновити `microdao — Data Model & Event Catalog`:
|
||||
* Додати сутність `agent_prompts`:
|
||||
* `id`, `agent_id`, `kind`, `content`, `version`, `created_at`, `updated_at`.
|
||||
* Вказати зв’язок `agents 1:N agent_prompts`.
|
||||
|
||||
---
|
||||
|
||||
## Frontend
|
||||
## 3. Backend API
|
||||
|
||||
### Сторінка агента
|
||||
`/agents/[agentId]` → вкладка "System Prompts"
|
||||
### 3.1. Repo-методи
|
||||
У `repo_city.py` або окремому модулі для агентів:
|
||||
* `get_agent_prompts(agent_id: str) -> List[dict]`:
|
||||
* повертає список промтів по агенту (останній запис по кожному `kind`).
|
||||
* `upsert_agent_prompts(agent_id: str, prompts: List[dict]) -> List[dict]`:
|
||||
* приймає масив `{kind, content}`,
|
||||
* оновлює існуючі записи або створює нові.
|
||||
|
||||
### Компоненти
|
||||
- `apps/web/src/app/agents/[agentId]/page.tsx` — головна сторінка
|
||||
- `apps/web/src/components/agent-dashboard/AgentSystemPromptsCard.tsx` — редактор промтів
|
||||
- `apps/web/src/lib/agent-dashboard.ts` — API клієнт
|
||||
### 3.2. Pydantic-схеми
|
||||
У `schemas_agents.py` (або аналогічному файлі):
|
||||
* `AgentPrompt`
|
||||
* `AgentPromptList`
|
||||
* `AgentPromptUpsertItem`
|
||||
* `AgentPromptUpsertRequest`
|
||||
|
||||
### Можливості
|
||||
- Перемикання між типами промтів (core/safety/governance/tools)
|
||||
- Редагування тексту промта
|
||||
- Збереження змін з індикацією статусу
|
||||
- Перегляд версії та часу останнього оновлення
|
||||
### 3.3. Routes
|
||||
У `routes_agents.py`:
|
||||
* `GET /api/v1/agents/{agent_id}/prompts`
|
||||
* `response_model=AgentPromptList`
|
||||
* Перевіряє, що агент існує.
|
||||
* RBAC: Owner/Guardian команди (або інша політика, узгоджена з RBAC-документом).
|
||||
* `PUT /api/v1/agents/{agent_id}/prompts`
|
||||
* `request_body=AgentPromptUpsertRequest`
|
||||
* Оновлює/створює промти.
|
||||
* Повертає оновлений `AgentPromptList`.
|
||||
|
||||
---
|
||||
|
||||
## Застосування міграції
|
||||
|
||||
```bash
|
||||
# На сервері
|
||||
cd /opt/microdao-daarion
|
||||
psql -U postgres -d daarion < migrations/034_agent_prompts_seed.sql
|
||||
```
|
||||
|
||||
Або через Docker:
|
||||
```bash
|
||||
docker exec -i dagi-postgres psql -U postgres -d daarion < migrations/034_agent_prompts_seed.sql
|
||||
### 3.4. OpenAPI
|
||||
Оновити `microdao — API Specification (OpenAPI 3.1, MVP)`:
|
||||
```yaml
|
||||
/agents/{agentId}/prompts:
|
||||
parameters:
|
||||
- name: agentId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: string }
|
||||
get:
|
||||
tags: [Agents]
|
||||
summary: Отримати системні промти агента
|
||||
responses:
|
||||
'200':
|
||||
description: Prompts
|
||||
content:
|
||||
application/json:
|
||||
schema: $ref: '#/components/schemas/AgentPromptList'
|
||||
put:
|
||||
tags: [Agents]
|
||||
summary: Оновити системні промти агента
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema: $ref: '#/components/schemas/AgentPromptUpsertRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Prompts
|
||||
content:
|
||||
application/json:
|
||||
schema: $ref: '#/components/schemas/AgentPromptList'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria
|
||||
## 4. Frontend: вкладка System Prompts
|
||||
|
||||
- ✅ Для будь-якого агента з seed-промтами: `/agents/:id` → вкладка System Prompts показує реальний текст з БД
|
||||
- ✅ Редагування промта з UI: змінює запис у БД, після перезавантаження новий текст відображається
|
||||
- ✅ API GET/PUT працюють коректно
|
||||
- ✅ Версіонування: кожне збереження створює нову версію
|
||||
- ✅ Seed-дані для 8 ключових агентів
|
||||
Шлях: `apps/web/src/app/agents/[agentSlug]/(tabs)/system-prompts`
|
||||
|
||||
### 4.1. Data hook
|
||||
Створити `useAgentPrompts(agentId)`:
|
||||
* Використати SWR або React Query (у відповідності до існуючого підходу в проєкті).
|
||||
* Ендпоінт: `GET /api/v1/agents/{agent_id}/prompts`.
|
||||
|
||||
### 4.2. System Prompts Tab
|
||||
Оновити компонент вкладки так, щоб:
|
||||
* при `agentSlug` → завантажувався `agent` (id, name, role, …),
|
||||
* при наявному `agent.id` → дергався `useAgentPrompts(agent.id)`,
|
||||
* рендерились textarea/редактори для 4 типів:
|
||||
* `core`, `safety`, `governance`, `tools`,
|
||||
* при натисканні “Save”:
|
||||
* `PUT /api/v1/agents/{agent_id}/prompts`
|
||||
* ті `kind`, де `content` не порожній,
|
||||
* показувати стани `loading`, `success`, `error`.
|
||||
|
||||
UX:
|
||||
* Показати невеликий description, що ці промти використовуються DAGI Router / agent runtime.
|
||||
* Не дозволяти редагувати, якщо немає прав (403 → показати “Read only” або помилку).
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope (на потім)
|
||||
## 5. Seed для ключових агентів
|
||||
|
||||
- [ ] UI для перегляду історії версій
|
||||
- [ ] Перемикання на попередню версію (rollback)
|
||||
- [ ] RBAC перевірки (хто може редагувати)
|
||||
- [ ] Інтеграція з DAGI Router runtime
|
||||
Мінімум: DAARWIZZ, DARIA, DARIO, Spirit, Logic, SOUL, Helion, GREENFOOD.
|
||||
Формат — будь-який твій існуючий `seed_agents.py` / SQL seed / fixture.
|
||||
|
||||
---
|
||||
Приклад SQL (скорочений, умовні промти):
|
||||
```sql
|
||||
-- DAARWIZZ — глобальний оркестратор
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_daarwizz_core', a.id, 'core', $$You are DAARWIZZ, the global orchestrator of DAARION.city. Coordinate specialized agents, route tasks, and preserve safety and governance constraints.$$, 1
|
||||
from agents a where a.slug = 'daarwizz';
|
||||
|
||||
## Файли змінені/створені
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_daarwizz_safety', a.id, 'safety', $$Always respect user consent, DAARION.city security policies, and never execute irreversible actions without explicit confirmation.$$, 1
|
||||
from agents a where a.slug = 'daarwizz';
|
||||
|
||||
### Змінені
|
||||
- `services/city-service/repo_city.py` — додані функції update_agent_prompt, get_agent_prompt_history
|
||||
-- DARIA / DARIO — city guides
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_daria_core', a.id, 'core', $$You are DARIA, a guide of DAARION.city. Explain the city, districts, MicroDAO and how to start.$$, 1
|
||||
from agents a where a.slug = 'daria';
|
||||
|
||||
### Створені
|
||||
- `migrations/034_agent_prompts_seed.sql` — детальні промти для ключових агентів
|
||||
- `docs/tasks/TASK_PHASE_AGENT_SYSTEM_PROMPTS_MVP_v1.md` — цей документ
|
||||
-- Spirit / Logic / SOUL — SOUL district
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_soul_core', a.id, 'core', $$You are SOUL, the narrative and alignment core of DAARION.city. Maintain brand philosophy and ethics.$$, 1
|
||||
from agents a where a.slug = 'soul';
|
||||
|
||||
### Вже існували (без змін)
|
||||
- `migrations/016_agent_prompts.sql` — схема таблиці
|
||||
- `services/city-service/routes_city.py` — API routes
|
||||
- `apps/web/src/components/agent-dashboard/AgentSystemPromptsCard.tsx` — UI компонент
|
||||
- `apps/web/src/lib/agent-dashboard.ts` — API клієнт
|
||||
- `apps/web/src/app/api/agents/[agentId]/prompts/[kind]/route.ts` — Next.js proxy
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_spirit_core', a.id, 'core', $$You are Spirit, creative strategist and story weaver for DAARION.city.$$, 1
|
||||
from agents a where a.slug = 'spirit';
|
||||
|
||||
---
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_logic_core', a.id, 'core', $$You are Logic, rational analyst for DAARION.city. You validate assumptions, models and numbers.$$, 1
|
||||
from agents a where a.slug = 'logic';
|
||||
|
||||
## Тестування
|
||||
-- Helion / GREENFOOD
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_helion_core', a.id, 'core', $$You are Helion, coordinator of Energy Union district. Focus on KWT, energy RWA and grids.$$, 1
|
||||
from agents a where a.slug = 'helion';
|
||||
|
||||
### Backend (curl)
|
||||
```bash
|
||||
# Отримати dashboard з промтами
|
||||
curl http://localhost:7001/city/agents/AGENT_ID/dashboard | jq '.system_prompts'
|
||||
|
||||
# Оновити промт
|
||||
curl -X PUT http://localhost:7001/city/agents/AGENT_ID/prompts/core \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"content": "Test prompt", "note": "Test update"}'
|
||||
|
||||
# Отримати історію
|
||||
curl http://localhost:7001/city/agents/AGENT_ID/prompts/core/history
|
||||
insert into agent_prompts (id, agent_id, kind, content, version)
|
||||
select 'ap_greenfood_core', a.id, 'core', $$You are GREENFOOD ERP agent, optimizing supply chains and cooperative logistics for craft food producers.$$, 1
|
||||
from agents a where a.slug = 'greenfood-erp';
|
||||
```
|
||||
|
||||
### Frontend
|
||||
1. Відкрити http://localhost:8899/agents
|
||||
2. Вибрати агента (DAARWIZZ, DARIA, тощо)
|
||||
3. Перейти на вкладку "System Prompts"
|
||||
4. Перевірити що відображаються seed-промти
|
||||
5. Змінити текст та натиснути "Save"
|
||||
6. Перезавантажити сторінку — зміни збережені
|
||||
|
||||
---
|
||||
|
||||
**Версія:** 1.0.0
|
||||
**Дата:** 2025-11-30
|
||||
**Автор:** DAARION AI Team
|
||||
|
||||
|
||||
23
migrations/040_agent_prompts.sql
Normal file
23
migrations/040_agent_prompts.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
-- Create agent_prompts table
|
||||
CREATE TABLE IF NOT EXISTS agent_prompts (
|
||||
id text PRIMARY KEY DEFAULT ('ap_' || substr(md5(random()::text), 1, 12)),
|
||||
agent_id text NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
|
||||
kind text NOT NULL CHECK (kind IN ('core', 'safety', 'governance', 'tools')),
|
||||
content text NOT NULL,
|
||||
version integer NOT NULL DEFAULT 1,
|
||||
created_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
updated_at timestamptz NOT NULL DEFAULT NOW(),
|
||||
created_by text,
|
||||
note text,
|
||||
is_active boolean NOT NULL DEFAULT true
|
||||
);
|
||||
|
||||
-- Create indexes
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_agent_prompts_agent_kind_version ON agent_prompts(agent_id, kind, version);
|
||||
CREATE INDEX IF NOT EXISTS ix_agent_prompts_agent_id ON agent_prompts(agent_id);
|
||||
CREATE INDEX IF NOT EXISTS ix_agent_prompts_agent_kind_active ON agent_prompts(agent_id, kind) WHERE is_active = true;
|
||||
|
||||
-- Grant permissions (adjust based on your RBAC)
|
||||
GRANT ALL ON agent_prompts TO postgres;
|
||||
-- GRANT SELECT, INSERT, UPDATE ON agent_prompts TO app_user; -- Uncomment if needed
|
||||
|
||||
163
migrations/041_agent_prompts_seed_v2.sql
Normal file
163
migrations/041_agent_prompts_seed_v2.sql
Normal file
@@ -0,0 +1,163 @@
|
||||
-- Migration 041: Agent System Prompts Seed V2 (Slug-based)
|
||||
-- Детальні системні промти для ключових агентів DAARION.city
|
||||
-- Використовує SLUG для ідентифікації агентів (надійніше ніж external_id)
|
||||
|
||||
-- ============================================================================
|
||||
-- DAARWIZZ — Мер DAARION.city / Головний оркестратор
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'core',
|
||||
$$You are DAARWIZZ, the Mayor and Chief Orchestrator of DAARION.city.
|
||||
|
||||
Your role:
|
||||
- Coordinate complex multi-agent workflows across the city
|
||||
- Route tasks to specialized agents based on expertise and availability
|
||||
- Maintain city governance, safety protocols, and community standards
|
||||
- Guide newcomers through the city's districts and services
|
||||
- Preserve the city's brand values: warmth, innovation, authenticity
|
||||
|
||||
Districts under your coordination:
|
||||
- SOUL Retreat (Wellness, Metahuman Development)
|
||||
- ENERGYUNION (DePIN, Energy, Compute)
|
||||
- GREENFOOD (Supply-Chain, Industry Operations)
|
||||
|
||||
Always prioritize: safety, user consent, privacy, and transparent governance.$$,
|
||||
1, 'SYSTEM', 'Seed v2: DAARWIZZ core', true
|
||||
FROM agents a WHERE a.slug = 'daarwizz'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'safety',
|
||||
$$Safety Rules for DAARWIZZ:
|
||||
1. CONSENT: Never execute irreversible actions without explicit user confirmation
|
||||
2. PRIVACY: Do not share personal information between users without consent
|
||||
3. SCOPE: Stay within DAARION.city domain
|
||||
4. ESCALATION: Complex governance decisions require human oversight
|
||||
5. TRANSPARENCY: Always disclose when delegating to other agents$$,
|
||||
1, 'SYSTEM', 'Seed v2: DAARWIZZ safety', true
|
||||
FROM agents a WHERE a.slug = 'daarwizz'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
-- ============================================================================
|
||||
-- DARIA — Technical Support
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'core',
|
||||
$$You are DARIA, the Technical Support Agent of DAARION.city.
|
||||
|
||||
Your mission:
|
||||
- Help residents with technical issues and onboarding
|
||||
- Explain how DAARION.city systems work
|
||||
- Guide users through wallet setup, passkeys, and agent interactions
|
||||
- Troubleshoot common problems with city services
|
||||
|
||||
Your personality:
|
||||
- Patient and thorough
|
||||
- Technical but accessible
|
||||
- Solution-oriented
|
||||
- Clear step-by-step communication$$,
|
||||
1, 'SYSTEM', 'Seed v2: DARIA core', true
|
||||
FROM agents a WHERE a.slug = 'daria'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
-- ============================================================================
|
||||
-- SOUL — District Lead
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'core',
|
||||
$$You are SOUL, the District Lead of SOUL Retreat — the Wellness and Metahuman Development district.
|
||||
|
||||
Your domain:
|
||||
- Personal development and growth
|
||||
- Wellness practices and mindfulness
|
||||
- Community healing and support
|
||||
- Retreat experiences
|
||||
|
||||
Your personality:
|
||||
- Calm and centered
|
||||
- Deeply empathetic
|
||||
- Wisdom-oriented
|
||||
- Holistic in perspective$$,
|
||||
1, 'SYSTEM', 'Seed v2: SOUL core', true
|
||||
FROM agents a WHERE a.slug = 'soul'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
-- ============================================================================
|
||||
-- Helion — Energy Union Lead
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'core',
|
||||
$$You are Helion, the District Lead of ENERGYUNION — the decentralized energy and infrastructure district.
|
||||
|
||||
Your domain:
|
||||
- Renewable energy coordination (solar, wind, storage)
|
||||
- DePIN (Decentralized Physical Infrastructure Networks)
|
||||
- KWT (Kilowatt Token) energy economy
|
||||
- Node infrastructure and compute resources
|
||||
|
||||
Your personality:
|
||||
- Technical and knowledgeable
|
||||
- Passionate about sustainability
|
||||
- Results-oriented$$,
|
||||
1, 'SYSTEM', 'Seed v2: Helion core', true
|
||||
FROM agents a WHERE a.slug = 'helion'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'tools',
|
||||
$$Helion Tool Usage:
|
||||
1. ENERGY_METER_READ: Query real-time energy production/consumption
|
||||
2. KWT_BALANCE: Check KWT token balances
|
||||
3. NODE_STATUS: Monitor infrastructure node health
|
||||
4. RWA_CLAIM: Process energy asset certifications$$,
|
||||
1, 'SYSTEM', 'Seed v2: Helion tools', true
|
||||
FROM agents a WHERE a.slug = 'helion'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
-- ============================================================================
|
||||
-- GREENFOOD — District Lead
|
||||
-- ============================================================================
|
||||
|
||||
INSERT INTO agent_prompts (agent_id, kind, content, version, created_by, note, is_active)
|
||||
SELECT a.id::text, 'core',
|
||||
$$You are GREENFOOD, the District Lead of the GREENFOOD district — focused on sustainable supply chains and craft food production.
|
||||
|
||||
Your domain:
|
||||
- Supply chain optimization
|
||||
- Inventory and warehouse management
|
||||
- Logistics and distribution
|
||||
- Quality certification
|
||||
|
||||
Your personality:
|
||||
- Practical and efficient
|
||||
- Supportive of small producers
|
||||
- Quality-focused$$,
|
||||
1, 'SYSTEM', 'Seed v2: GREENFOOD core', true
|
||||
FROM agents a WHERE a.slug = 'greenfood-erp' OR a.slug = 'greenfood'
|
||||
ON CONFLICT (agent_id, kind, version) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
is_active = true,
|
||||
updated_at = NOW();
|
||||
|
||||
@@ -13,6 +13,7 @@ import asyncio
|
||||
|
||||
# Import new modules
|
||||
import routes_city
|
||||
import routes_agents
|
||||
import ws_city
|
||||
import repo_city
|
||||
import migrations # Import migrations
|
||||
@@ -62,6 +63,7 @@ app.add_middleware(
|
||||
app.include_router(routes_city.router)
|
||||
app.include_router(routes_city.public_router)
|
||||
app.include_router(routes_city.api_router)
|
||||
app.include_router(routes_agents.router)
|
||||
|
||||
# Governance API routers
|
||||
app.include_router(routes_governance.router)
|
||||
|
||||
@@ -897,6 +897,23 @@ async def update_agent_prompt(
|
||||
}
|
||||
|
||||
|
||||
async def upsert_agent_prompts(agent_id: str, prompts: List[dict], created_by: str) -> List[dict]:
|
||||
"""
|
||||
Пакетне оновлення промтів агента.
|
||||
"""
|
||||
results = []
|
||||
for p in prompts:
|
||||
res = await update_agent_prompt(
|
||||
agent_id=agent_id,
|
||||
kind=p["kind"],
|
||||
content=p["content"],
|
||||
created_by=created_by,
|
||||
note=p.get("note")
|
||||
)
|
||||
results.append(res)
|
||||
return results
|
||||
|
||||
|
||||
async def get_agent_prompt_history(agent_id: str, kind: str, limit: int = 10) -> List[dict]:
|
||||
"""
|
||||
Отримати історію версій промту агента.
|
||||
|
||||
118
services/city-service/routes_agents.py
Normal file
118
services/city-service/routes_agents.py
Normal file
@@ -0,0 +1,118 @@
|
||||
from fastapi import APIRouter, HTTPException, Request, Depends
|
||||
from typing import List
|
||||
import logging
|
||||
import repo_city
|
||||
from schemas_agents import AgentPromptList, AgentPromptUpsertRequest, AgentPrompt
|
||||
|
||||
router = APIRouter(prefix="/agents", tags=["agents"])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@router.get("/{agent_id}/prompts", response_model=AgentPromptList)
|
||||
async def get_agent_prompts(agent_id: str):
|
||||
"""
|
||||
Отримати системні промти агента.
|
||||
"""
|
||||
try:
|
||||
# Check agent exists
|
||||
agent = await repo_city.get_agent_by_id(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail=f"Agent not found: {agent_id}")
|
||||
|
||||
# Get prompts dict from repo
|
||||
prompts_dict = await repo_city.get_agent_prompts(agent_id)
|
||||
|
||||
# Convert to list of AgentPrompt models
|
||||
prompts_list = []
|
||||
valid_kinds = ["core", "safety", "governance", "tools"]
|
||||
|
||||
for kind in valid_kinds:
|
||||
p_data = prompts_dict.get(kind)
|
||||
if p_data:
|
||||
prompts_list.append(AgentPrompt(
|
||||
kind=kind,
|
||||
content=p_data["content"],
|
||||
version=p_data["version"],
|
||||
updated_at=p_data["created_at"], # repo returns created_at as isoformat string or datetime? Repo returns isoformat string in dict
|
||||
note=p_data.get("note")
|
||||
))
|
||||
else:
|
||||
# Should we return empty prompt structure or just skip?
|
||||
# The frontend expects 4 kinds. If we skip, frontend might need adjustment.
|
||||
# But AgentPrompt requires content.
|
||||
# Let's return empty content if missing, or just skip and let frontend handle default.
|
||||
# Frontend AgentSystemPromptsCard handles missing prompts gracefully?
|
||||
# Yes: const currentPrompt = systemPrompts?.[activeTab];
|
||||
pass
|
||||
|
||||
# However, the response model is AgentPromptList which has prompts: List[AgentPrompt].
|
||||
# If we return a list, the frontend needs to map it back to dict by kind.
|
||||
# The user requested GET returns AgentPromptList.
|
||||
# Wait, the frontend `useAgentPrompts` implementation in the prompt suggests:
|
||||
# return { prompts: data?.prompts ?? [] }
|
||||
# And the component maps it:
|
||||
# for (const p of prompts) { map[p.kind] = p.content; }
|
||||
|
||||
return AgentPromptList(agent_id=agent_id, prompts=prompts_list)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get agent prompts: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get agent prompts")
|
||||
|
||||
|
||||
@router.put("/{agent_id}/prompts", response_model=AgentPromptList)
|
||||
async def upsert_agent_prompts_endpoint(agent_id: str, payload: AgentPromptUpsertRequest, request: Request):
|
||||
"""
|
||||
Оновити системні промти агента (bulk).
|
||||
"""
|
||||
try:
|
||||
# Check agent exists
|
||||
agent = await repo_city.get_agent_by_id(agent_id)
|
||||
if not agent:
|
||||
raise HTTPException(status_code=404, detail=f"Agent not found: {agent_id}")
|
||||
|
||||
# TODO: Get user from auth
|
||||
created_by = "ARCHITECT"
|
||||
|
||||
# Upsert
|
||||
# Convert Pydantic models to dicts for repo
|
||||
prompts_to_update = []
|
||||
for p in payload.prompts:
|
||||
prompts_to_update.append({
|
||||
"kind": p.kind,
|
||||
"content": p.content,
|
||||
"note": p.note
|
||||
})
|
||||
|
||||
if not prompts_to_update:
|
||||
# Nothing to update, just return current state
|
||||
pass
|
||||
else:
|
||||
await repo_city.upsert_agent_prompts(agent_id, prompts_to_update, created_by)
|
||||
|
||||
# Return updated state
|
||||
# Re-use get logic
|
||||
prompts_dict = await repo_city.get_agent_prompts(agent_id)
|
||||
prompts_list = []
|
||||
valid_kinds = ["core", "safety", "governance", "tools"]
|
||||
|
||||
for kind in valid_kinds:
|
||||
p_data = prompts_dict.get(kind)
|
||||
if p_data:
|
||||
prompts_list.append(AgentPrompt(
|
||||
kind=kind,
|
||||
content=p_data["content"],
|
||||
version=p_data["version"],
|
||||
updated_at=p_data["created_at"],
|
||||
note=p_data.get("note")
|
||||
))
|
||||
|
||||
return AgentPromptList(agent_id=agent_id, prompts=prompts_list)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to upsert agent prompts: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to upsert agent prompts")
|
||||
|
||||
27
services/city-service/schemas_agents.py
Normal file
27
services/city-service/schemas_agents.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Literal, Optional
|
||||
from datetime import datetime
|
||||
|
||||
PromptKind = Literal["core", "safety", "governance", "tools"]
|
||||
|
||||
class AgentPrompt(BaseModel):
|
||||
id: Optional[str] = None
|
||||
kind: PromptKind
|
||||
content: str
|
||||
version: int
|
||||
updated_at: Optional[datetime] = None
|
||||
created_by: Optional[str] = None
|
||||
note: Optional[str] = None
|
||||
|
||||
class AgentPromptList(BaseModel):
|
||||
agent_id: str
|
||||
prompts: List[AgentPrompt]
|
||||
|
||||
class AgentPromptUpsertItem(BaseModel):
|
||||
kind: PromptKind
|
||||
content: str
|
||||
note: Optional[str] = None
|
||||
|
||||
class AgentPromptUpsertRequest(BaseModel):
|
||||
prompts: List[AgentPromptUpsertItem] = Field(default_factory=list)
|
||||
|
||||
Reference in New Issue
Block a user