feat: Agent System Prompts MVP (B) - database, backend API, and frontend integration

This commit is contained in:
Apple
2025-11-30 14:04:48 -08:00
parent bca81dc719
commit 1830109a95
10 changed files with 624 additions and 173 deletions

View File

@@ -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?.();

View 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,
};
}

View File

@@ -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<{

View File

@@ -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

View 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

View 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();

View File

@@ -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)

View File

@@ -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]:
"""
Отримати історію версій промту агента.

View 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")

View 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)