refactor: finalize agent schemas and profile model
- Remove deprecated calendar UI tests - Update profile model with phone field support - Update agent schemas with runtime_models and forwarded_props - Update system_agent with new agent configuration
This commit is contained in:
@@ -1,51 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/calendar_time_utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('calendar_time_utils', () {
|
|
||||||
test('returns week start on sunday', () {
|
|
||||||
final date = DateTime(2026, 2, 11);
|
|
||||||
final weekStart = weekStartFor(date);
|
|
||||||
|
|
||||||
expect(weekStart.year, 2026);
|
|
||||||
expect(weekStart.month, 2);
|
|
||||||
expect(weekStart.day, 8);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('shows current marker only for selected today', () {
|
|
||||||
final now = DateTime(2026, 2, 11, 15, 28);
|
|
||||||
|
|
||||||
expect(shouldShowCurrentMarker(DateTime(2026, 2, 11), now), isTrue);
|
|
||||||
expect(shouldShowCurrentMarker(DateTime(2026, 2, 10), now), isFalse);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('formats hour minute with zero pad', () {
|
|
||||||
expect(formatHm(DateTime(2026, 2, 11, 7, 5)), '07:05');
|
|
||||||
expect(formatHm(DateTime(2026, 2, 11, 15, 28)), '15:28');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('parses and formats ymd date string', () {
|
|
||||||
final parsed = parseYmd('2026-02-11');
|
|
||||||
|
|
||||||
expect(parsed, isNotNull);
|
|
||||||
expect(parsed!.year, 2026);
|
|
||||||
expect(parsed.month, 2);
|
|
||||||
expect(parsed.day, 11);
|
|
||||||
expect(formatYmd(parsed), '2026-02-11');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns null for invalid ymd date string', () {
|
|
||||||
expect(parseYmd('2026/02/11'), isNull);
|
|
||||||
expect(parseYmd('bad-input'), isNull);
|
|
||||||
expect(parseYmd(null), isNull);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('builds all dates for month', () {
|
|
||||||
final dates = monthDatesFor(DateTime(2026, 2, 9));
|
|
||||||
|
|
||||||
expect(dates.length, 28);
|
|
||||||
expect(formatYmd(dates.first), '2026-02-01');
|
|
||||||
expect(formatYmd(dates.last), '2026-02-28');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:social_app/features/calendar/data/models/schedule_item_model.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/widgets/create_event_sheet.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('编辑日程时支持非默认提醒值', (tester) async {
|
|
||||||
final event = ScheduleItemModel(
|
|
||||||
id: 'evt_1',
|
|
||||||
ownerId: 'user_1',
|
|
||||||
title: '测试日程',
|
|
||||||
startAt: DateTime(2026, 3, 18, 10, 0),
|
|
||||||
endAt: DateTime(2026, 3, 18, 11, 0),
|
|
||||||
metadata: ScheduleMetadata(reminderMinutes: 20),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: Scaffold(body: CreateEventSheet(editingEvent: event)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
await tester.tap(find.text('进阶'));
|
|
||||||
await tester.pumpAndSettle();
|
|
||||||
|
|
||||||
expect(find.text('开始前20分钟'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('时间自动对齐逻辑', () {
|
|
||||||
test('开始时间改变后,结束时间早于开始时间应自动对齐', () {
|
|
||||||
DateTime startTime = DateTime(2026, 3, 11, 10, 0);
|
|
||||||
DateTime endTime = DateTime(2026, 3, 11, 9, 0);
|
|
||||||
|
|
||||||
final newStartTime = DateTime(2026, 3, 11, 14, 30);
|
|
||||||
|
|
||||||
if (endTime.isBefore(newStartTime)) {
|
|
||||||
endTime = newStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(endTime.hour, 14);
|
|
||||||
expect(endTime.minute, 30);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('结束时间晚于开始时间则不需要对齐', () {
|
|
||||||
DateTime startTime = DateTime(2026, 3, 11, 10, 0);
|
|
||||||
DateTime endTime = DateTime(2026, 3, 11, 12, 0);
|
|
||||||
|
|
||||||
final newStartTime = DateTime(2026, 3, 11, 14, 30);
|
|
||||||
|
|
||||||
if (endTime.isBefore(newStartTime)) {
|
|
||||||
endTime = newStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(endTime.hour, 14);
|
|
||||||
expect(endTime.minute, 30);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('结束时间等于开始时间也需要对齐', () {
|
|
||||||
DateTime startTime = DateTime(2026, 3, 11, 10, 0);
|
|
||||||
DateTime endTime = DateTime(2026, 3, 11, 10, 0);
|
|
||||||
|
|
||||||
final newStartTime = DateTime(2026, 3, 11, 14, 30);
|
|
||||||
|
|
||||||
if (endTime.isBefore(newStartTime)) {
|
|
||||||
endTime = newStartTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(endTime.hour, 14);
|
|
||||||
expect(endTime.minute, 30);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:social_app/features/calendar/data/models/schedule_item_model.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/dayweek/day_event_layout_engine.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/dayweek/day_timeline_metrics.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/dayweek/day_view_scale.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('DayEventLayoutEngine', () {
|
|
||||||
const engine = DayEventLayoutEngine();
|
|
||||||
const scale = DayViewScale(hourHeight: 60);
|
|
||||||
|
|
||||||
test('maps event top and height by exact minutes', () {
|
|
||||||
final event = _event(
|
|
||||||
id: 'a',
|
|
||||||
start: DateTime(2026, 3, 18, 10, 15),
|
|
||||||
end: DateTime(2026, 3, 18, 11, 45),
|
|
||||||
);
|
|
||||||
|
|
||||||
final layouts = engine.layout(
|
|
||||||
events: [event],
|
|
||||||
scale: scale,
|
|
||||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
|
||||||
eventAreaWidth: 200,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(layouts, hasLength(1));
|
|
||||||
expect(layouts.first.startMinutes, 615);
|
|
||||||
expect(layouts.first.endMinutes, 705);
|
|
||||||
expect(layouts.first.top, 615);
|
|
||||||
expect(layouts.first.geometryHeight, 90);
|
|
||||||
expect(layouts.first.visualHeight, 90);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('splits overlapped events into columns', () {
|
|
||||||
final e1 = _event(
|
|
||||||
id: 'a',
|
|
||||||
start: DateTime(2026, 3, 18, 9, 0),
|
|
||||||
end: DateTime(2026, 3, 18, 10, 0),
|
|
||||||
);
|
|
||||||
final e2 = _event(
|
|
||||||
id: 'b',
|
|
||||||
start: DateTime(2026, 3, 18, 9, 30),
|
|
||||||
end: DateTime(2026, 3, 18, 10, 30),
|
|
||||||
);
|
|
||||||
|
|
||||||
final layouts = engine.layout(
|
|
||||||
events: [e1, e2],
|
|
||||||
scale: scale,
|
|
||||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
|
||||||
eventAreaWidth: 200,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(layouts, hasLength(2));
|
|
||||||
expect(layouts[0].columnCount, 2);
|
|
||||||
expect(layouts[1].columnCount, 2);
|
|
||||||
expect(layouts[0].width, closeTo(98, 0.001));
|
|
||||||
expect(layouts[1].width, closeTo(98, 0.001));
|
|
||||||
expect(layouts[0].left, DayTimelineMetrics.eventAreaLeft());
|
|
||||||
expect(
|
|
||||||
layouts[1].left,
|
|
||||||
closeTo(DayTimelineMetrics.eventAreaLeft() + 102, 0.001),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('uses 1 pixel minimum visual height but preserves geometry', () {
|
|
||||||
final event = _event(
|
|
||||||
id: 'a',
|
|
||||||
start: DateTime(2026, 3, 18, 9, 0),
|
|
||||||
end: DateTime(2026, 3, 18, 9, 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
final tinyScale = const DayViewScale(
|
|
||||||
hourHeight: DayViewScale.minHourHeight,
|
|
||||||
);
|
|
||||||
final layouts = engine.layout(
|
|
||||||
events: [event],
|
|
||||||
scale: tinyScale,
|
|
||||||
eventAreaLeft: DayTimelineMetrics.eventAreaLeft(),
|
|
||||||
eventAreaWidth: 200,
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(layouts, hasLength(1));
|
|
||||||
expect(
|
|
||||||
layouts.first.geometryHeight,
|
|
||||||
closeTo(tinyScale.pixelsForMinutes(1), 0.001),
|
|
||||||
);
|
|
||||||
expect(layouts.first.visualHeight, greaterThanOrEqualTo(1));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ScheduleItemModel _event({
|
|
||||||
required String id,
|
|
||||||
required DateTime start,
|
|
||||||
required DateTime end,
|
|
||||||
}) {
|
|
||||||
return ScheduleItemModel(
|
|
||||||
id: id,
|
|
||||||
ownerId: 'owner',
|
|
||||||
title: 'event-$id',
|
|
||||||
startAt: start,
|
|
||||||
endAt: end,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/dayweek/day_view_scale.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('DayViewScale', () {
|
|
||||||
test('maps minutes to pixels and back', () {
|
|
||||||
const scale = DayViewScale(hourHeight: 60);
|
|
||||||
|
|
||||||
expect(scale.pixelsForMinutes(30), 30);
|
|
||||||
expect(scale.pixelsForMinutes(75), 75);
|
|
||||||
expect(scale.minutesForPixels(90), 90);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('clamps zoom height at boundaries', () {
|
|
||||||
const scale = DayViewScale(hourHeight: 34);
|
|
||||||
|
|
||||||
final zoomIn = scale.zoomByFactor(20);
|
|
||||||
final zoomOut = scale.zoomByFactor(0.01);
|
|
||||||
|
|
||||||
expect(zoomIn.hourHeight, DayViewScale.maxHourHeight);
|
|
||||||
expect(zoomOut.hourHeight, DayViewScale.minHourHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('ignores invalid zoom factor', () {
|
|
||||||
const scale = DayViewScale(hourHeight: 34);
|
|
||||||
|
|
||||||
expect(scale.zoomByFactor(0).hourHeight, 34);
|
|
||||||
expect(scale.zoomByFactor(-1).hourHeight, 34);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:social_app/core/theme/design_tokens.dart';
|
|
||||||
import 'package:social_app/features/calendar/data/models/schedule_item_model.dart';
|
|
||||||
import 'package:social_app/features/calendar/ui/utils/event_color_resolver.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('returns gray for archived status regardless of custom color', () {
|
|
||||||
final color = resolveEventColor(
|
|
||||||
status: ScheduleStatus.archived,
|
|
||||||
colorHex: '#EF4444',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(color, AppColors.slate400);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns parsed color for active status', () {
|
|
||||||
final color = resolveEventColor(
|
|
||||||
status: ScheduleStatus.active,
|
|
||||||
colorHex: '#3B82F6',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(color.value, const Color(0xFF3B82F6).value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from sqlalchemy import ForeignKey, String, Text
|
from sqlalchemy import String, Text
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
|
||||||
@@ -43,9 +43,3 @@ class Profile(TimestampMixin, SoftDeleteMixin, Base):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
server_default="{}",
|
server_default="{}",
|
||||||
)
|
)
|
||||||
referred_by: Mapped[uuid.UUID | None] = mapped_column(
|
|
||||||
UUID(as_uuid=True),
|
|
||||||
ForeignKey("profiles.id", ondelete="SET NULL"),
|
|
||||||
nullable=True,
|
|
||||||
index=True,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,15 +1,39 @@
|
|||||||
|
from schemas.agent.consumer_registry import AgentConsumerBinding, ConsumerRegistry
|
||||||
from schemas.agent.forwarded_props import (
|
from schemas.agent.forwarded_props import (
|
||||||
ClientTimeContext,
|
ClientTimeContext,
|
||||||
|
ForwardedPropsPayload,
|
||||||
|
parse_forwarded_props_agent_type,
|
||||||
parse_forwarded_props_client_time,
|
parse_forwarded_props_client_time,
|
||||||
)
|
)
|
||||||
|
from schemas.agent.pipeline_spec import (
|
||||||
|
ContextPolicy,
|
||||||
|
ContextWindowMode,
|
||||||
|
ExecutorKind,
|
||||||
|
PipelineSpec,
|
||||||
|
StageSpec,
|
||||||
|
)
|
||||||
from schemas.agent.runtime_models import (
|
from schemas.agent.runtime_models import (
|
||||||
AgentOutput,
|
AgentOutput,
|
||||||
|
ConstraintItem,
|
||||||
|
ExecutionMode,
|
||||||
|
KeyEntity,
|
||||||
|
NormalizedTaskInput,
|
||||||
|
ResultTyping,
|
||||||
ResultType,
|
ResultType,
|
||||||
|
RouterAgentOutput,
|
||||||
|
RouterUiDecision,
|
||||||
RunStatus,
|
RunStatus,
|
||||||
|
TaskType,
|
||||||
|
TaskTyping,
|
||||||
ToolAgentOutput,
|
ToolAgentOutput,
|
||||||
ToolStatus,
|
ToolStatus,
|
||||||
|
UiMode,
|
||||||
|
WorkerAgentOutputLite,
|
||||||
|
WorkerAgentOutputRich,
|
||||||
|
resolve_worker_output_model,
|
||||||
)
|
)
|
||||||
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
|
from schemas.agent.system_agent import AgentType, SystemAgentLLMConfig
|
||||||
|
from schemas.agent.visibility import SystemVisibilityBit, VisibilityMask, bit_mask
|
||||||
from schemas.agent.ui_hints import (
|
from schemas.agent.ui_hints import (
|
||||||
UiHintAction,
|
UiHintAction,
|
||||||
UiHintIntent,
|
UiHintIntent,
|
||||||
@@ -21,16 +45,41 @@ from schemas.agent.ui_hints import (
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"AgentType",
|
"AgentType",
|
||||||
"AgentOutput",
|
"AgentOutput",
|
||||||
|
"AgentConsumerBinding",
|
||||||
|
"ConstraintItem",
|
||||||
|
"ConsumerRegistry",
|
||||||
|
"ContextPolicy",
|
||||||
|
"ContextWindowMode",
|
||||||
|
"ExecutionMode",
|
||||||
|
"ExecutorKind",
|
||||||
|
"ForwardedPropsPayload",
|
||||||
|
"KeyEntity",
|
||||||
|
"NormalizedTaskInput",
|
||||||
|
"PipelineSpec",
|
||||||
|
"ResultTyping",
|
||||||
"ClientTimeContext",
|
"ClientTimeContext",
|
||||||
"ResultType",
|
"ResultType",
|
||||||
|
"RouterAgentOutput",
|
||||||
|
"RouterUiDecision",
|
||||||
"RunStatus",
|
"RunStatus",
|
||||||
|
"TaskType",
|
||||||
|
"TaskTyping",
|
||||||
"SystemAgentLLMConfig",
|
"SystemAgentLLMConfig",
|
||||||
|
"SystemVisibilityBit",
|
||||||
|
"StageSpec",
|
||||||
"ToolAgentOutput",
|
"ToolAgentOutput",
|
||||||
"ToolStatus",
|
"ToolStatus",
|
||||||
|
"UiMode",
|
||||||
"UiHintAction",
|
"UiHintAction",
|
||||||
"UiHintIntent",
|
"UiHintIntent",
|
||||||
"UiHintSection",
|
"UiHintSection",
|
||||||
"UiHintStatus",
|
"UiHintStatus",
|
||||||
"UiHintsPayload",
|
"UiHintsPayload",
|
||||||
|
"VisibilityMask",
|
||||||
|
"WorkerAgentOutputLite",
|
||||||
|
"WorkerAgentOutputRich",
|
||||||
|
"bit_mask",
|
||||||
|
"parse_forwarded_props_agent_type",
|
||||||
"parse_forwarded_props_client_time",
|
"parse_forwarded_props_client_time",
|
||||||
|
"resolve_worker_output_model",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import re
|
import re
|
||||||
from typing import Any
|
|
||||||
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
|
||||||
|
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
@@ -63,16 +62,34 @@ class ClientTimeContext(BaseModel):
|
|||||||
class ForwardedPropsPayload(BaseModel):
|
class ForwardedPropsPayload(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
agent_type: str = Field(..., min_length=1, max_length=64)
|
||||||
client_time: ClientTimeContext | None = None
|
client_time: ClientTimeContext | None = None
|
||||||
|
|
||||||
|
@field_validator("agent_type")
|
||||||
|
@classmethod
|
||||||
|
def validate_agent_type(cls, value: str) -> str:
|
||||||
|
normalized = value.strip().lower()
|
||||||
|
if not normalized:
|
||||||
|
raise ValueError("invalid forwarded_props.agent_type")
|
||||||
|
return normalized
|
||||||
|
|
||||||
|
|
||||||
|
def parse_forwarded_props(forwarded_props: object) -> ForwardedPropsPayload:
|
||||||
|
if not isinstance(forwarded_props, dict):
|
||||||
|
raise ValueError("invalid RunAgentInput.forwardedProps")
|
||||||
|
try:
|
||||||
|
return ForwardedPropsPayload.model_validate(forwarded_props)
|
||||||
|
except ValidationError as exc:
|
||||||
|
raise ValueError("invalid RunAgentInput.forwardedProps") from exc
|
||||||
|
|
||||||
|
|
||||||
def parse_forwarded_props_client_time(
|
def parse_forwarded_props_client_time(
|
||||||
forwarded_props: Any,
|
forwarded_props: object,
|
||||||
) -> ClientTimeContext | None:
|
) -> ClientTimeContext | None:
|
||||||
if not isinstance(forwarded_props, dict):
|
payload = parse_forwarded_props(forwarded_props)
|
||||||
return None
|
|
||||||
try:
|
|
||||||
payload = ForwardedPropsPayload.model_validate(forwarded_props)
|
|
||||||
except ValidationError as exc:
|
|
||||||
raise ValueError("invalid RunAgentInput.forwardedProps") from exc
|
|
||||||
return payload.client_time
|
return payload.client_time
|
||||||
|
|
||||||
|
|
||||||
|
def parse_forwarded_props_agent_type(forwarded_props: object) -> str:
|
||||||
|
payload = parse_forwarded_props(forwarded_props)
|
||||||
|
return payload.agent_type
|
||||||
|
|||||||
@@ -8,6 +8,22 @@ from pydantic import BaseModel, ConfigDict, Field
|
|||||||
from schemas.agent.ui_hints import UiHintsPayload
|
from schemas.agent.ui_hints import UiHintsPayload
|
||||||
|
|
||||||
|
|
||||||
|
class TaskType(str, Enum):
|
||||||
|
KNOWLEDGE = "knowledge"
|
||||||
|
RECOMMENDATION = "recommendation"
|
||||||
|
PLANNING = "planning"
|
||||||
|
SCHEDULING = "scheduling"
|
||||||
|
REMINDER_MANAGEMENT = "reminder_management"
|
||||||
|
TODO_MANAGEMENT = "todo_management"
|
||||||
|
COMMUNICATION_DRAFTING = "communication_drafting"
|
||||||
|
INFORMATION_ORGANIZATION = "information_organization"
|
||||||
|
STATUS_TRACKING = "status_tracking"
|
||||||
|
TRANSACTION_ASSIST = "transaction_assist"
|
||||||
|
ACTION_EXECUTION = "action_execution"
|
||||||
|
TROUBLESHOOTING = "troubleshooting"
|
||||||
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
class ResultType(str, Enum):
|
class ResultType(str, Enum):
|
||||||
DIRECT_ANSWER = "direct_answer"
|
DIRECT_ANSWER = "direct_answer"
|
||||||
OPTIONS_WITH_RECOMMENDATION = "options_with_recommendation"
|
OPTIONS_WITH_RECOMMENDATION = "options_with_recommendation"
|
||||||
@@ -26,6 +42,31 @@ class ResultType(str, Enum):
|
|||||||
UNKNOWN = "unknown"
|
UNKNOWN = "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
class TaskTyping(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
primary: TaskType
|
||||||
|
secondary: list[TaskType] = Field(default_factory=list, max_length=3)
|
||||||
|
|
||||||
|
|
||||||
|
class ResultTyping(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
primary: ResultType
|
||||||
|
secondary: list[ResultType] = Field(default_factory=list, max_length=3)
|
||||||
|
|
||||||
|
|
||||||
|
class ExecutionMode(str, Enum):
|
||||||
|
ONESTEP = "onestep"
|
||||||
|
TOOL_ASSISTED = "tool_assisted"
|
||||||
|
MULTISTEP = "multistep"
|
||||||
|
|
||||||
|
|
||||||
|
class UiMode(str, Enum):
|
||||||
|
NONE = "none"
|
||||||
|
RICH = "rich"
|
||||||
|
|
||||||
|
|
||||||
class RunStatus(str, Enum):
|
class RunStatus(str, Enum):
|
||||||
SUCCESS = "success"
|
SUCCESS = "success"
|
||||||
PARTIAL_SUCCESS = "partial_success"
|
PARTIAL_SUCCESS = "partial_success"
|
||||||
@@ -38,13 +79,55 @@ class ToolStatus(str, Enum):
|
|||||||
PARTIAL = "partial"
|
PARTIAL = "partial"
|
||||||
|
|
||||||
|
|
||||||
|
class KeyEntity(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
name: str
|
||||||
|
type: str
|
||||||
|
value: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class ConstraintItem(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
key: str
|
||||||
|
value: str
|
||||||
|
required: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class NormalizedTaskInput(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
user_text: str
|
||||||
|
multimodal_summary: list[str] = Field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
|
class RouterUiDecision(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
ui_mode: UiMode
|
||||||
|
ui_decision_reason: str
|
||||||
|
|
||||||
|
|
||||||
|
class RouterAgentOutput(BaseModel):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
normalized_task_input: NormalizedTaskInput
|
||||||
|
key_entities: list[KeyEntity] = Field(default_factory=list)
|
||||||
|
constraints: list[ConstraintItem] = Field(default_factory=list)
|
||||||
|
task_typing: TaskTyping
|
||||||
|
execution_mode: ExecutionMode
|
||||||
|
result_typing: ResultTyping
|
||||||
|
ui: RouterUiDecision
|
||||||
|
|
||||||
|
|
||||||
class ErrorInfo(BaseModel):
|
class ErrorInfo(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
code: str = Field(..., description="Stable error code for programmatic handling.")
|
code: str
|
||||||
message: str = Field(..., description="Human-readable error message.")
|
message: str
|
||||||
retryable: bool = Field(default=False)
|
retryable: bool = False
|
||||||
details: dict[str, Any] | None = Field(default=None)
|
details: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
class ToolAgentOutput(BaseModel):
|
class ToolAgentOutput(BaseModel):
|
||||||
@@ -58,13 +141,29 @@ class ToolAgentOutput(BaseModel):
|
|||||||
error: ErrorInfo | None = None
|
error: ErrorInfo | None = None
|
||||||
|
|
||||||
|
|
||||||
class AgentOutput(BaseModel):
|
class WorkerAgentOutputLite(BaseModel):
|
||||||
model_config = ConfigDict(extra="forbid")
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
status: RunStatus = Field(default=RunStatus.SUCCESS)
|
status: RunStatus = RunStatus.SUCCESS
|
||||||
answer: str
|
answer: str
|
||||||
key_points: list[str] = Field(default_factory=list)
|
key_points: list[str] = Field(default_factory=list)
|
||||||
result_type: ResultType = Field(default=ResultType.UNKNOWN)
|
result_type: ResultType = ResultType.UNKNOWN
|
||||||
suggested_actions: list[str] = Field(default_factory=list)
|
suggested_actions: list[str] = Field(default_factory=list)
|
||||||
error: ErrorInfo | None = None
|
error: ErrorInfo | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class WorkerAgentOutputRich(WorkerAgentOutputLite):
|
||||||
ui_hints: UiHintsPayload | None = None
|
ui_hints: UiHintsPayload | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AgentOutput(WorkerAgentOutputRich):
|
||||||
|
model_config = ConfigDict(extra="forbid")
|
||||||
|
|
||||||
|
|
||||||
|
WorkerAgentOutput = WorkerAgentOutputLite | WorkerAgentOutputRich
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_worker_output_model(ui_mode: UiMode) -> type[WorkerAgentOutputLite]:
|
||||||
|
if ui_mode == UiMode.RICH:
|
||||||
|
return WorkerAgentOutputRich
|
||||||
|
return WorkerAgentOutputLite
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ from enum import Enum
|
|||||||
|
|
||||||
from pydantic import BaseModel, Field, field_validator
|
from pydantic import BaseModel, Field, field_validator
|
||||||
|
|
||||||
from core.agentscope.tools.tool_config import ToolGroup
|
from core.agentscope.tools.tool_config import AgentTool, parse_agent_tool
|
||||||
|
|
||||||
|
|
||||||
class AgentType(str, Enum):
|
class AgentType(str, Enum):
|
||||||
|
ROUTER = "router"
|
||||||
WORKER = "worker"
|
WORKER = "worker"
|
||||||
MEMORY = "memory"
|
MEMORY = "memory"
|
||||||
|
|
||||||
@@ -29,24 +30,22 @@ class SystemAgentLLMConfig(BaseModel):
|
|||||||
context_messages: ContextMessagesConfig = Field(
|
context_messages: ContextMessagesConfig = Field(
|
||||||
default_factory=ContextMessagesConfig
|
default_factory=ContextMessagesConfig
|
||||||
)
|
)
|
||||||
enabled_tool_groups: list[ToolGroup] = Field(default_factory=list, max_length=8)
|
visibility_consumer_bit: int = Field(default=16, ge=16, le=63)
|
||||||
|
enabled_tools: list[AgentTool] = Field(default_factory=list, max_length=32)
|
||||||
|
|
||||||
@field_validator("enabled_tool_groups", mode="before")
|
@field_validator("enabled_tools", mode="before")
|
||||||
@classmethod
|
@classmethod
|
||||||
def _normalize_enabled_tool_groups(cls, value: object) -> list[ToolGroup]:
|
def _normalize_enabled_tools(cls, value: object) -> list[AgentTool]:
|
||||||
if value is None:
|
if value is None:
|
||||||
return []
|
return []
|
||||||
if not isinstance(value, list):
|
if not isinstance(value, list):
|
||||||
raise ValueError("enabled_tool_groups must be a list")
|
raise ValueError("enabled_tools must be a list")
|
||||||
normalized: list[ToolGroup] = []
|
normalized: list[AgentTool] = []
|
||||||
for item in value:
|
for item in value:
|
||||||
if isinstance(item, ToolGroup):
|
raw_item = str(item or "").strip()
|
||||||
group = item
|
if not raw_item:
|
||||||
else:
|
|
||||||
raw_group = str(item or "").strip().lower()
|
|
||||||
if not raw_group:
|
|
||||||
continue
|
continue
|
||||||
group = ToolGroup(raw_group)
|
tool = parse_agent_tool(raw_item)
|
||||||
if group not in normalized:
|
if tool not in normalized:
|
||||||
normalized.append(group)
|
normalized.append(tool)
|
||||||
return normalized
|
return normalized
|
||||||
|
|||||||
Reference in New Issue
Block a user