fix(ci): align SDK dependency and validate acceptance migrations

This commit is contained in:
Hyu
2026-10-11 00:59:51 +08:00
parent e3e5f88809
commit 353b452453
81 changed files with 3527 additions and 1818 deletions
+6 -2
View File
@@ -452,7 +452,9 @@ class AgentService:
if runner_id:
descriptor = await self.ap.runner_registry.get(context, runner_id)
config['runner_config'][runner_id] = await self.ap.llm_model_service.apply_default_model(
context, descriptor.config_schema, config['runner_config'][runner_id],
context,
descriptor.config_schema,
config['runner_config'][runner_id],
)
patterns = agent_data.get('supported_event_patterns', AGENT_DEFAULT_EVENT_PATTERNS)
new_uuid = str(uuid.uuid4())
@@ -566,7 +568,9 @@ class AgentService:
if parameters is None:
parameters = self.ap.pipeline_service._get_default_values_from_schema(descriptor.config_schema)
if draft:
parameters = await self.ap.llm_model_service.apply_default_model(context, descriptor.config_schema, parameters)
parameters = await self.ap.llm_model_service.apply_default_model(
context, descriptor.config_schema, parameters
)
if not isinstance(parameters, dict):
raise ValueError('Processor parameters must be an object')
for field in descriptor.config_schema:
+24 -7
View File
@@ -198,6 +198,7 @@ class LLMModelsService:
async def get_starred_model(self, context: TenantContext) -> str | None:
from ....entity.persistence.metadata import WorkspaceMetadata
result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(WorkspaceMetadata.value).where(
WorkspaceMetadata.workspace_uuid == require_workspace_uuid(context),
@@ -212,6 +213,7 @@ class LLMModelsService:
async def set_starred_model(self, context: TenantContext, model_uuid: str | None) -> None:
from ....entity.persistence.metadata import WorkspaceMetadata
workspace_uuid = require_workspace_uuid(context)
if model_uuid and await self.get_llm_model(context, model_uuid) is None:
raise ValueError('Model not found in this Workspace')
@@ -222,11 +224,16 @@ class LLMModelsService:
else:
from sqlalchemy.dialects.sqlite import insert
statement = insert(WorkspaceMetadata).values(
workspace_uuid=workspace_uuid, key='starred_llm_model', value=model_uuid or '',
workspace_uuid=workspace_uuid,
key='starred_llm_model',
value=model_uuid or '',
)
await self.ap.persistence_mgr.execute_async(
statement.on_conflict_do_update(
index_elements=['workspace_uuid', 'key'],
set_={'value': model_uuid or ''},
)
)
await self.ap.persistence_mgr.execute_async(statement.on_conflict_do_update(
index_elements=['workspace_uuid', 'key'], set_={'value': model_uuid or ''},
))
async def get_default_model(self, context: TenantContext) -> str | None:
starred = await self.get_starred_model(context)
@@ -245,9 +252,18 @@ class LLMModelsService:
async def apply_default_model(self, context: TenantContext, schema: list[dict], parameters: dict) -> dict:
import copy
fields = [field for field in schema if field.get('type') in (
'llm-model-selector', 'select-llm-model', 'model-fallback-selector',
) and field.get('name')]
fields = [
field
for field in schema
if field.get('type')
in (
'llm-model-selector',
'select-llm-model',
'model-fallback-selector',
)
and field.get('name')
]
if not fields:
return parameters
model_uuid = await self.get_default_model(context)
@@ -264,6 +280,7 @@ class LLMModelsService:
elif not result.get(name):
result[name] = model_uuid
return result
async def get_llm_models(self, context: TenantContext, include_secret: bool = False) -> list[dict]:
"""Get all LLM models with provider info"""
result = await self.ap.persistence_mgr.execute_async(
+144 -53
View File
@@ -132,6 +132,7 @@ def _workspace_transaction(method):
result = await method(self, context, *args, **kwargs)
if method.__name__ in {'record_message', 'update_message_status'}:
from ....utils.inflight import inflight_hub
inflight_hub.notify(workspace_uuid)
return result
@@ -1430,10 +1431,13 @@ class MonitoringService(ExecutionDetailsMixin):
pipeline_timing = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(AgentRun.started_at, AgentRun.finished_at)
.select_from(Message)
.join(AgentRun, sqlalchemy.and_(
AgentRun.workspace_id == workspace_uuid,
AgentRun.run_id == Message.run_id,
))
.join(
AgentRun,
sqlalchemy.and_(
AgentRun.workspace_id == workspace_uuid,
AgentRun.run_id == Message.run_id,
),
)
.where(
*pipeline_conditions,
Message.status == 'success',
@@ -1443,10 +1447,7 @@ class MonitoringService(ExecutionDetailsMixin):
.order_by(Message.timestamp.desc())
.limit(self._detail_limit())
)
durations.extend(
(row[1] - row[0]).total_seconds() * 1000
for row in pipeline_timing.all() if row[0] and row[1]
)
durations.extend((row[1] - row[0]).total_seconds() * 1000 for row in pipeline_timing.all() if row[0] and row[1])
durations.sort()
pipeline_status_result = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(
@@ -1679,21 +1680,39 @@ class MonitoringService(ExecutionDetailsMixin):
workspace = require_workspace_uuid(context)
active = await self.get_executions(context, statuses=['running', 'queued'], limit=100, include_summary=False)
recent = await self.get_executions(
context, start_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=30),
limit=100, include_summary=False,
context,
start_time=datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(seconds=30),
limit=100,
include_summary=False,
)
items = {(r['source'], r['id']): r for r in recent['items'] + active['items'] if r['source'] != 'event'}
# Keep the actual terminal state of older runs that just left the active set.
for kind, model, column, scope in [
('agent', persistence_agent_run.AgentRun, persistence_agent_run.AgentRun.run_id, persistence_agent_run.AgentRun.workspace_id),
('pipeline', persistence_monitoring.MonitoringMessage, persistence_monitoring.MonitoringMessage.id, persistence_monitoring.MonitoringMessage.workspace_uuid),
(
'agent',
persistence_agent_run.AgentRun,
persistence_agent_run.AgentRun.run_id,
persistence_agent_run.AgentRun.workspace_id,
),
(
'pipeline',
persistence_monitoring.MonitoringMessage,
persistence_monitoring.MonitoringMessage.id,
persistence_monitoring.MonitoringMessage.workspace_uuid,
),
]:
missing = [r['id'] for r in (previous or [])[:100] if r['source'] == kind and (kind, r['id']) not in items]
if missing:
records = await self._execution_rows(workspace, sqlalchemy.select(model).where(scope == workspace, column.in_(missing)))
records = await self._execution_rows(
workspace, sqlalchemy.select(model).where(scope == workspace, column.in_(missing))
)
names = await self._agent_names(workspace, records) if kind == 'agent' else {}
for record in records:
row = self._serialize_agent_execution(record, names) if kind == 'agent' else self._serialize_pipeline_execution(record)
row = (
self._serialize_agent_execution(record, names)
if kind == 'agent'
else self._serialize_pipeline_execution(record)
)
items[(kind, row['id'])] = row
rows = list(items.values())
Run = persistence_agent_run.AgentRun
@@ -1701,18 +1720,28 @@ class MonitoringService(ExecutionDetailsMixin):
agent_ids = [r['id'] for r in rows if r['source'] == 'agent']
pipeline_ids = [r['id'] for r in rows if r['source'] == 'pipeline']
linked = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(Run.run_id, Message.id.label('message_id')).outerjoin(
Message, sqlalchemy.and_(Message.workspace_uuid == workspace, Message.run_id == Run.run_id)
).where(Run.workspace_id == workspace, sqlalchemy.or_(Run.run_id.in_(agent_ids), Message.id.in_(pipeline_ids)))
sqlalchemy.select(Run.run_id, Message.id.label('message_id'))
.outerjoin(Message, sqlalchemy.and_(Message.workspace_uuid == workspace, Message.run_id == Run.run_id))
.where(
Run.workspace_id == workspace, sqlalchemy.or_(Run.run_id.in_(agent_ids), Message.id.in_(pipeline_ids))
)
)
identities = {r.run_id: ('pipeline', r.message_id) if r.message_id in pipeline_ids else ('agent', r.run_id) for r in linked.all()}
identities = {
r.run_id: ('pipeline', r.message_id) if r.message_id in pipeline_ids else ('agent', r.run_id)
for r in linked.all()
}
inflight_hub.track(workspace, identities)
Event = persistence_agent_run.AgentRunEvent
latest = sqlalchemy.select(Event.run_id, sqlalchemy.func.max(Event.sequence).label('seq')).where(
Event.run_id.in_(identities)
).group_by(Event.run_id).subquery()
latest = (
sqlalchemy.select(Event.run_id, sqlalchemy.func.max(Event.sequence).label('seq'))
.where(Event.run_id.in_(identities))
.group_by(Event.run_id)
.subquery()
)
phases = await self.ap.persistence_mgr.execute_async(
sqlalchemy.select(Event.run_id, Event.type).join(latest, sqlalchemy.and_(Event.run_id == latest.c.run_id, Event.sequence == latest.c.seq))
sqlalchemy.select(Event.run_id, Event.type).join(
latest, sqlalchemy.and_(Event.run_id == latest.c.run_id, Event.sequence == latest.c.seq)
)
)
for run_id, event_type in phases.all():
row = items.get(identities[run_id])
@@ -1951,7 +1980,10 @@ class MonitoringService(ExecutionDetailsMixin):
if end_time:
query = query.where(source.c.timestamp <= end_time)
return await self._session_projection_page(
workspace_uuid, query.order_by(source.c.timestamp.desc(), source.c.id.desc()), limit, offset,
workspace_uuid,
query.order_by(source.c.timestamp.desc(), source.c.id.desc()),
limit,
offset,
)
conditions = [persistence_monitoring.MonitoringMessage.workspace_uuid == workspace_uuid]
@@ -2204,47 +2236,99 @@ class MonitoringService(ExecutionDetailsMixin):
workspace = require_workspace_uuid(context)
execute = self.ap.persistence_mgr.execute_async
source = self._session_message_source(workspace, [bot_id])
exists = (await execute(sqlalchemy.select(source.c.session_id).where(
source.c.session_id == session_id,
).limit(1))).first()
exists = (
await execute(
sqlalchemy.select(source.c.session_id)
.where(
source.c.session_id == session_id,
)
.limit(1)
)
).first()
if exists is None:
session = persistence_monitoring.MonitoringSession
exists = (await execute(sqlalchemy.select(session.session_id).where(
session.workspace_uuid == workspace, session.bot_id == bot_id, session.session_id == session_id,
).limit(1))).first()
exists = (
await execute(
sqlalchemy.select(session.session_id)
.where(
session.workspace_uuid == workspace,
session.bot_id == bot_id,
session.session_id == session_id,
)
.limit(1)
)
).first()
if exists is None:
raise LookupError('Session not found')
# Legacy sessions use uppercase PERSON/GROUP; envelope IDs use lowercase.
conversation = re.sub(r'^(PERSON|GROUP)_', lambda match: match[1].lower() + '_', session_id)
run = persistence_agent_run.AgentRun
busy = (await execute(sqlalchemy.select(run.run_id).where(
run.workspace_id == workspace, run.bot_id == bot_id, run.conversation_id == conversation,
run.status.in_(['created', 'queued', 'claimed', 'running']),
).limit(1))).first()
busy = (
await execute(
sqlalchemy.select(run.run_id)
.where(
run.workspace_id == workspace,
run.bot_id == bot_id,
run.conversation_id == conversation,
run.status.in_(['created', 'queued', 'claimed', 'running']),
)
.limit(1)
)
).first()
message = persistence_monitoring.MonitoringMessage
pending = (await execute(sqlalchemy.select(message.id).where(
message.workspace_uuid == workspace, message.bot_id == bot_id,
message.session_id.in_([session_id, conversation]), message.status == 'pending',
).limit(1))).first()
pending = (
await execute(
sqlalchemy.select(message.id)
.where(
message.workspace_uuid == workspace,
message.bot_id == bot_id,
message.session_id.in_([session_id, conversation]),
message.status == 'pending',
)
.limit(1)
)
).first()
interaction = persistence_agent_interaction.AgentInteraction
waiting = (await execute(sqlalchemy.select(interaction.id).where(
interaction.workspace_id == workspace, interaction.bot_id == bot_id,
interaction.conversation_id == conversation, interaction.status.in_(['pending', 'submitted']),
sqlalchemy.or_(interaction.expires_at.is_(None), interaction.expires_at > datetime.datetime.utcnow()),
).limit(1))).first()
waiting = (
await execute(
sqlalchemy.select(interaction.id)
.where(
interaction.workspace_id == workspace,
interaction.bot_id == bot_id,
interaction.conversation_id == conversation,
interaction.status.in_(['pending', 'submitted']),
sqlalchemy.or_(
interaction.expires_at.is_(None), interaction.expires_at > datetime.datetime.utcnow()
),
)
.limit(1)
)
).first()
if busy or pending or waiting:
raise RuntimeError('Session is processing a task; wait until it finishes')
await execute(sqlalchemy.delete(RunnerState).where(
RunnerState.workspace_id == workspace, RunnerState.bot_id == bot_id,
RunnerState.conversation_id == conversation, RunnerState.scope == 'conversation',
))
await execute(
sqlalchemy.delete(RunnerState).where(
RunnerState.workspace_id == workspace,
RunnerState.bot_id == bot_id,
RunnerState.conversation_id == conversation,
RunnerState.scope == 'conversation',
)
)
generation = str(uuid.uuid4())
await execute(sqlalchemy.insert(RunnerState).values(
runner_id=HOST_RESET_RUNNER, binding_identity=HOST_RESET_RUNNER, scope='conversation',
scope_key=reset_scope(workspace, bot_id, conversation), state_key='generation',
value_json=json.dumps(generation), workspace_id=workspace, bot_id=bot_id,
conversation_id=conversation, created_at=datetime.datetime.utcnow(),
))
await execute(
sqlalchemy.insert(RunnerState).values(
runner_id=HOST_RESET_RUNNER,
binding_identity=HOST_RESET_RUNNER,
scope='conversation',
scope_key=reset_scope(workspace, bot_id, conversation),
state_key='generation',
value_json=json.dumps(generation),
workspace_id=workspace,
bot_id=bot_id,
conversation_id=conversation,
created_at=datetime.datetime.utcnow(),
)
)
return {'reset': True, 'session_id': session_id}
async def get_sessions(
@@ -2266,7 +2350,14 @@ class MonitoringService(ExecutionDetailsMixin):
workspace_uuid = require_workspace_uuid(context)
if bot_ids and not pipeline_ids and not execution_statuses and mode == 'all':
return await self.get_bot_conversation_sessions(
workspace_uuid, bot_ids, start_time, end_time, user_query, is_active, limit, offset,
workspace_uuid,
bot_ids,
start_time,
end_time,
user_query,
is_active,
limit,
offset,
)
conditions = [persistence_monitoring.MonitoringSession.workspace_uuid == workspace_uuid]
@@ -104,16 +104,24 @@ class ExecutionDetailsMixin:
message = models.MonitoringMessage
conversation = sa.func.coalesce(
EventLog.conversation_id,
sa.select(AgentRun.conversation_id).where(
sa.select(AgentRun.conversation_id)
.where(
AgentRun.workspace_id == workspace,
AgentRun.bot_id == EventLog.bot_id,
AgentRun.event_id == EventLog.event_id,
).order_by(AgentRun.id).limit(1).scalar_subquery(),
)
.order_by(AgentRun.id)
.limit(1)
.scalar_subquery(),
sa.case(
(self._execution_json_text(EventLog.input_json, 'chat_type') == 'group',
sa.literal('group_') + self._execution_json_text(EventLog.input_json, 'chat_id')),
(self._execution_json_text(EventLog.input_json, 'chat_type') == 'private',
sa.literal('person_') + self._execution_json_text(EventLog.input_json, ('sender', 'id'))),
(
self._execution_json_text(EventLog.input_json, 'chat_type') == 'group',
sa.literal('group_') + self._execution_json_text(EventLog.input_json, 'chat_id'),
),
(
self._execution_json_text(EventLog.input_json, 'chat_type') == 'private',
sa.literal('person_') + self._execution_json_text(EventLog.input_json, ('sender', 'id')),
),
),
)
values = {
@@ -122,8 +130,12 @@ class ExecutionDetailsMixin:
'timestamp': sa.func.coalesce(EventLog.event_time, EventLog.created_at),
'bot_id': EventLog.bot_id,
'bot_name': sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, 'bot_name'), ''),
'pipeline_id': sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_uuid')), ''),
'pipeline_name': sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_type')), ''),
'pipeline_id': sa.func.coalesce(
self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_uuid')), ''
),
'pipeline_name': sa.func.coalesce(
self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_type')), ''
),
'message_content': sa.func.coalesce(EventLog.input_json, EventLog.input_summary, ''),
'session_id': conversation,
'status': sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, 'status'), 'success'),
@@ -137,45 +149,62 @@ class ExecutionDetailsMixin:
}
columns = list(message.__table__.columns)
stored = sa.select(*columns).where(message.workspace_uuid == workspace, message.bot_id.in_(bot_ids))
incoming = sa.select(*[
values.get(column.name, sa.literal(None)).label(column.name) for column in columns
]).where(
incoming = sa.select(
*[values.get(column.name, sa.literal(None)).label(column.name) for column in columns]
).where(
EventLog.workspace_id == workspace,
EventLog.bot_id.in_(bot_ids),
EventLog.source == 'platform',
EventLog.event_type == 'message.received',
conversation.is_not(None),
conversation != '',
~sa.exists(sa.select(message.id).where(
message.workspace_uuid == workspace,
message.bot_id == EventLog.bot_id,
sa.func.coalesce(message.role, 'user') != 'assistant',
sa.or_(message.event_id == EventLog.event_id, message.id == EventLog.event_id),
)),
~sa.exists(
sa.select(message.id).where(
message.workspace_uuid == workspace,
message.bot_id == EventLog.bot_id,
sa.func.coalesce(message.role, 'user') != 'assistant',
sa.or_(message.event_id == EventLog.event_id, message.id == EventLog.event_id),
)
),
)
return sa.union_all(stored, incoming).subquery()
async def _session_projection_page(self, workspace, statement, limit, offset):
count = (await self._execution_query(workspace, sa.select(sa.func.count()).select_from(statement.subquery()))).scalar_one()
count = (
await self._execution_query(workspace, sa.select(sa.func.count()).select_from(statement.subquery()))
).scalar_one()
rows = (await self._execution_query(workspace, statement.limit(limit).offset(offset))).mappings().all()
return [
{key: value.isoformat() if isinstance(value, datetime.datetime) else value for key, value in row.items()}
for row in rows
], count
async def get_bot_conversation_sessions(self, workspace, bot_ids, start_time, end_time, user_query, is_active, limit, offset):
async def get_bot_conversation_sessions(
self, workspace, bot_ids, start_time, end_time, user_query, is_active, limit, offset
):
"""Read session summaries across processor types without mutating history."""
messages = self._session_message_source(workspace, bot_ids)
key = [messages.c.workspace_uuid, messages.c.bot_id, messages.c.session_id]
ranked = sa.select(
*messages.c,
sa.func.row_number().over(partition_by=key, order_by=[
sa.case((messages.c.role == 'user', 0), else_=1), messages.c.timestamp.desc(), messages.c.id.desc(),
]).label('position'),
sa.func.min(messages.c.timestamp).over(partition_by=key).label('first_seen'),
sa.func.max(messages.c.timestamp).over(partition_by=key).label('last_seen'),
sa.func.count().over(partition_by=key).label('count'),
).where(messages.c.session_id != '').subquery()
ranked = (
sa.select(
*messages.c,
sa.func.row_number()
.over(
partition_by=key,
order_by=[
sa.case((messages.c.role == 'user', 0), else_=1),
messages.c.timestamp.desc(),
messages.c.id.desc(),
],
)
.label('position'),
sa.func.min(messages.c.timestamp).over(partition_by=key).label('first_seen'),
sa.func.max(messages.c.timestamp).over(partition_by=key).label('last_seen'),
sa.func.count().over(partition_by=key).label('count'),
)
.where(messages.c.session_id != '')
.subquery()
)
session = models.MonitoringSession
fields = list(session.__table__.columns)
projected = {
@@ -184,15 +213,21 @@ class ExecutionDetailsMixin:
'message_count': ranked.c['count'],
'is_active': sa.literal(True),
}
derived = sa.select(*[
(projected[field.name] if field.name in projected else ranked.c[field.name]).label(field.name)
for field in fields
]).where(ranked.c.position == 1)
derived = sa.select(
*[
(projected[field.name] if field.name in projected else ranked.c[field.name]).label(field.name)
for field in fields
]
).where(ranked.c.position == 1)
existing = sa.select(*fields).where(session.workspace_uuid == workspace, session.bot_id.in_(bot_ids))
combined = sa.union_all(existing, derived).subquery()
group = [combined.c.workspace_uuid, combined.c.bot_id, combined.c.session_id]
merged = sa.select(
*[combined.c[field.name] for field in fields if field.name not in {'message_count', 'start_time', 'last_activity'}],
*[
combined.c[field.name]
for field in fields
if field.name not in {'message_count', 'start_time', 'last_activity'}
],
sa.func.max(combined.c.message_count).over(partition_by=group).label('message_count'),
sa.func.min(combined.c.start_time).over(partition_by=group).label('start_time'),
sa.func.max(combined.c.last_activity).over(partition_by=group).label('last_activity'),
@@ -209,7 +244,10 @@ class ExecutionDetailsMixin:
if is_active is not None:
query = query.where(merged.c.is_active == is_active)
return await self._session_projection_page(
workspace, query.order_by(merged.c.last_activity.desc(), merged.c.session_id), limit, offset,
workspace,
query.order_by(merged.c.last_activity.desc(), merged.c.session_id),
limit,
offset,
)
def _execution_json_text(self, column, key):
@@ -335,12 +373,10 @@ class ExecutionDetailsMixin:
# Delivered pipeline ingress is not an unhandled execution, including
# older queued/aggregated messages whose trace identity was lost.
~sa.and_(
sa.func.coalesce(
self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_type')), ''
) == 'pipeline',
sa.func.coalesce(
self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'status')), ''
) == 'delivered',
sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'target_type')), '')
== 'pipeline',
sa.func.coalesce(self._execution_json_text(EventLog.metadata_json, ('routes', 0, 'status')), '')
== 'delivered',
),
~sa.exists(
sa.select(AgentRun.id).where(
@@ -400,11 +436,13 @@ class ExecutionDetailsMixin:
'runner_id': None,
'debug': row.source == 'debug',
'has_error': status == 'failed',
'status_reason': metadata.get('reason') or '; '.join(
'status_reason': metadata.get('reason')
or '; '.join(
route.get('reason') or route.get('failure_code') or 'failure_reason_not_recorded'
for route in metadata.get('routes', [])
if isinstance(route, dict) and route.get('status') == 'failed'
) or None,
)
or None,
}
async def enrich_execution_rows(self, workspace, items):
@@ -415,10 +453,13 @@ class ExecutionDetailsMixin:
linked = await self._execution_query(
workspace,
sa.select(models.MonitoringMessage.id, AgentRun)
.join(AgentRun, sa.and_(
AgentRun.workspace_id == workspace,
AgentRun.run_id == models.MonitoringMessage.run_id,
))
.join(
AgentRun,
sa.and_(
AgentRun.workspace_id == workspace,
AgentRun.run_id == models.MonitoringMessage.run_id,
),
)
.where(
models.MonitoringMessage.workspace_uuid == workspace,
models.MonitoringMessage.id.in_(pipeline_items),
@@ -426,10 +467,9 @@ class ExecutionDetailsMixin:
)
for message_id, run in linked.all():
metrics = self._serialize_agent_execution(run)
pipeline_items[message_id].update({
key: metrics[key]
for key in ('started_at_ms', 'finished_at_ms', 'duration_ms', 'usage', 'cost')
})
pipeline_items[message_id].update(
{key: metrics[key] for key in ('started_at_ms', 'finished_at_ms', 'duration_ms', 'usage', 'cost')}
)
calls = models.MonitoringLLMCall
usage_rows = await self._execution_query(
@@ -439,10 +479,12 @@ class ExecutionDetailsMixin:
sa.func.sum(calls.input_tokens),
sa.func.sum(calls.output_tokens),
sa.func.sum(calls.total_tokens),
).where(
)
.where(
calls.workspace_uuid == workspace,
calls.message_id.in_(pipeline_items),
).group_by(calls.message_id),
)
.group_by(calls.message_id),
)
for message_id, input_tokens, output_tokens, total_tokens in usage_rows.all():
if not pipeline_items[message_id].get('usage'):
+3 -1
View File
@@ -69,7 +69,9 @@ class PipelineService:
runner_config.setdefault('expire-time', 0)
ai_config['runner_config'] = {selected.id: self._get_default_values_from_schema(selected.config_schema)}
ai_config['runner_config'][selected.id] = await self.ap.llm_model_service.apply_default_model(
context, selected.config_schema, ai_config['runner_config'][selected.id],
context,
selected.config_schema,
ai_config['runner_config'][selected.id],
)
return config