feat(platform): Cost indication for agent runs (#9527)

- Resolves #9181

### Changes 🏗️

- Add agent run cost indication to `/monitoring` and
`/library/agents/[id]`

Backend:
- Add `GraphExecutionMeta.cost` property - value sourced from
`AgentGraphExecution.stats["cost"]`

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [ ] I have tested my changes according to the test plan:
  - Run an agent
- [ ] Check out the agent run on `/library/agents/[id]` -> should show
cost
  - [ ] Check out the agent run on `/monitoring` -> should show cost
This commit is contained in:
Reinier van der Leer
2025-02-28 15:20:46 +01:00
committed by GitHub
parent 52ee7d150e
commit f6c93eeeff
6 changed files with 25 additions and 24 deletions

View File

@@ -249,7 +249,6 @@ class UserCreditBase(ABC):
metadata: Json,
new_transaction_key: str | None = None,
):
transaction = await CreditTransaction.prisma().find_first_or_raise(
where={"transactionKey": transaction_key, "userId": user_id}
)
@@ -346,7 +345,6 @@ class UsageTransactionMetadata(BaseModel):
class UserCredit(UserCreditBase):
@thread_cached
def notification_client(self) -> NotificationManager:
return get_service_client(NotificationManager)
@@ -840,7 +838,6 @@ class UserCredit(UserCreditBase):
transaction_time_ceiling: datetime | None = None,
transaction_type: str | None = None,
) -> TransactionHistory:
transactions_filter: CreditTransactionWhereInput = {
"userId": user_id,
"isActive": True,

View File

@@ -15,7 +15,7 @@ from prisma.models import (
StoreListingVersion,
)
from prisma.types import AgentGraphWhereInput
from pydantic.fields import computed_field
from pydantic.fields import Field, computed_field
from backend.blocks.agent import AgentExecutorBlock
from backend.blocks.basic import AgentInputBlock, AgentOutputBlock
@@ -112,6 +112,7 @@ class GraphExecutionMeta(BaseDbModel):
execution_id: str
started_at: datetime
ended_at: datetime
cost: Optional[int] = Field(..., description="Execution cost in credits")
duration: float
total_run_time: float
status: ExecutionStatus
@@ -140,6 +141,7 @@ class GraphExecutionMeta(BaseDbModel):
execution_id=_graph_exec.id,
started_at=start_time,
ended_at=end_time,
cost=stats.get("cost", None),
duration=duration,
total_run_time=total_run_time,
status=ExecutionStatus(_graph_exec.executionStatus),

View File

@@ -549,14 +549,10 @@ def execute_graph(
user_id: Annotated[str, Depends(get_user_id)],
graph_version: Optional[int] = None,
) -> ExecuteGraphResponse:
try:
graph_exec = execution_manager_client().add_execution(
graph_id, node_input, user_id=user_id, graph_version=graph_version
)
return ExecuteGraphResponse(graph_exec_id=graph_exec.graph_exec_id)
except Exception as e:
msg = str(e).encode().decode("unicode_escape")
raise HTTPException(status_code=400, detail=msg)
graph_exec = execution_manager_client().add_execution(
graph_id, node_input, user_id=user_id, graph_version=graph_version
)
return ExecuteGraphResponse(graph_exec_id=graph_exec.graph_exec_id)
@v1_router.post(

View File

@@ -4,7 +4,6 @@ import moment from "moment";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import {
BlockIOSubType,
GraphExecution,
GraphExecutionMeta,
GraphMeta,
@@ -30,7 +29,7 @@ export default function AgentRunDetailsView({
}): React.ReactNode {
const api = useBackendAPI();
const selectedRunStatus: AgentRunStatus = useMemo(
const runStatus: AgentRunStatus = useMemo(
() => agentRunStatusMap[run.status],
[run],
);
@@ -40,9 +39,7 @@ export default function AgentRunDetailsView({
return [
{
label: "Status",
value:
selectedRunStatus.charAt(0).toUpperCase() +
selectedRunStatus.slice(1),
value: runStatus.charAt(0).toUpperCase() + runStatus.slice(1),
},
{
label: "Started",
@@ -50,11 +47,11 @@ export default function AgentRunDetailsView({
},
{
label: "Duration",
value: `${moment.duration(run.duration, "seconds").humanize()}`,
value: moment.duration(run.duration, "seconds").humanize(),
},
// { label: "Cost", value: selectedRun.cost }, // TODO: implement cost - https://github.com/Significant-Gravitas/AutoGPT/issues/9181
...(run.cost ? [{ label: "Cost", value: `${run.cost} credits` }] : []),
];
}, [run, selectedRunStatus]);
}, [run, runStatus]);
const agentRunInputs:
| Record<string, { title?: string; /* type: BlockIOSubType; */ value: any }>
@@ -96,8 +93,7 @@ export default function AgentRunDetailsView({
| null
| undefined = useMemo(() => {
if (!("outputs" in run)) return undefined;
if (!["running", "success", "failed"].includes(selectedRunStatus))
return null;
if (!["running", "success", "failed"].includes(runStatus)) return null;
// Add type info from agent input schema
return Object.fromEntries(
@@ -110,7 +106,7 @@ export default function AgentRunDetailsView({
},
]),
);
}, [agent, run, selectedRunStatus]);
}, [agent, run, runStatus]);
const runActions: { label: string; callback: () => void }[] = useMemo(
() => [{ label: "Run again", callback: () => runAgain() }],

View File

@@ -110,7 +110,16 @@ export const FlowRunsStatus: React.FC<{
)}{" "}
seconds
</p>
{/* <p><strong>Total cost:</strong> €1,23</p> */}
{filteredFlowRuns.some((r) => r.cost) && (
<p>
<strong>Total cost:</strong>{" "}
{filteredFlowRuns.reduce(
(total, run) => total + (run.cost ?? 0),
0,
)}{" "}
seconds
</p>
)}
</div>
</div>
);

View File

@@ -221,6 +221,7 @@ export type GraphExecutionMeta = {
execution_id: string;
started_at: number;
ended_at: number;
cost?: number;
duration: number;
total_run_time: number;
status: "QUEUED" | "RUNNING" | "COMPLETED" | "TERMINATED" | "FAILED";