Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions examples/portfolio/agents/advisor_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@
# Final stage. Turns the computed portfolio metrics and risk figures into a
# short, plain-English briefing using a small, cheap model on AWS Bedrock
# (Converse API), called via ventis.llm.bedrock so token/cost telemetry gets
# recorded onto this execution's future:<future_id>:metrics hash. Configure
# recorded onto this execution's future:<future_id> hash. Configure
# with env vars:
# BEDROCK_MODEL_ID (default: meta.llama3-8b-instruct-v1:0)
# AWS_REGION (default: us-east-1)
#
# If the LLM is unavailable (returns an empty string), it falls back to a
# deterministic templated summary so the pipeline still returns.
Comment on lines 11 to 12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle empty Bedrock output before returning.

The module documentation promises a deterministic fallback when Bedrock returns an empty string. summarize returns the first text value directly, so an empty response reaches the caller instead of _fallback_summary.

Extract and validate the text before returning it.

Proposed fix
-            return response["output"]["message"]["content"][0]["text"]
+            text = response["output"]["message"]["content"][0].get("text", "").strip()
+            if not text:
+                return self._fallback_summary(metrics, risk)
+            return text

Also applies to: 42-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/portfolio/agents/advisor_agent.py` around lines 11 - 12, Update
summarize to extract the first Bedrock text value, validate that it is
non-empty, and return _fallback_summary when the response is empty instead of
returning it directly. Apply the same handling to both affected return paths
while preserving the existing deterministic fallback behavior.

#
# Resource profile: cheap CPU; the LLM cost sits in LLMAgent, not here.
# Resource profile: cheap CPU; the LLM cost sits in the Bedrock call, not here.

import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "stubs"))
from llm_agent import LLMAgent
try:
from ventis.llm.bedrock import call_bedrock
except ImportError:
Expand All @@ -27,16 +24,14 @@
class AdvisorAgent(object):
def __init__(self):
self.tools = [self.summarize]
self.llm = LLMAgent()
self.model_id = os.environ.get(
"BEDROCK_MODEL_ID", "meta.llama3-8b-instruct-v1:0"
)
self.region = os.environ.get("AWS_REGION", "us-east-1")

def summarize(self, holdings: dict, metrics: dict, risk: dict) -> str:
"""Write a short plain-English briefing on the portfolio."""
prompt = self._build_prompt(holdings, metrics, risk)
text = self.llm.complete(
prompt=prompt, max_tokens=400, temperature=0.2
).value()
if not text:
print("AdvisorAgent: LLM returned no output; using templated summary.")
try:
response = call_bedrock(
model_id=self.model_id,
Expand All @@ -48,7 +43,6 @@ def summarize(self, holdings: dict, metrics: dict, risk: dict) -> str:
except Exception as e:
print(f"AdvisorAgent: Bedrock call failed ({e}); using templated summary.")
return self._fallback_summary(metrics, risk)
return text

def _build_prompt(self, holdings: dict, metrics: dict, risk: dict) -> str:
lines = ["You are a portfolio analyst. Given the figures below, write a "
Expand Down
37 changes: 1 addition & 36 deletions examples/portfolio/agents/intent_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,9 @@
# -> {"holdings": {"AAPL": 0.4, "MSFT": 0.35, "NVDA": 0.25},
# "lookback_days": 180}
#
<<<<<<< HEAD
# The actual model call lives in the shared LLMAgent (remote, resolved via
# .value()) — this agent only builds the prompt and parses the result, so no
# Bedrock boilerplate lives here. If the LLM is unavailable or returns
# unparseable output, parse() raises: there is no fallback, the request fails
# loudly rather than guessing at the holdings. Weights are renormalized to 1.0.
#
# Resource profile: cheap CPU; the LLM cost sits in LLMAgent, not here.

import sys
=======
# Calls AWS Bedrock (Converse API) via ventis.llm.bedrock -- same pattern as
# AdvisorAgent -- so token/cost telemetry gets recorded onto this execution's
# future:<future_id>:metrics hash. Configure with env vars:
# future:<future_id> hash. Configure with env vars:
# BEDROCK_MODEL_ID (default: meta.llama3-8b-instruct-v1:0)
# AWS_REGION (default: us-east-1)
#
Expand All @@ -31,36 +20,21 @@
# Resource profile: cheap CPU, single call per request, on the critical path
# before the fan-out.

>>>>>>> remotes/origin/telemetry-signals
import os
import re
import json

<<<<<<< HEAD
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "stubs"))
from llm_agent import LLMAgent
=======
try:
from ventis.llm.bedrock import call_bedrock
except ImportError:
from bedrock import call_bedrock
>>>>>>> remotes/origin/telemetry-signals

DEFAULT_LOOKBACK_DAYS = 365


class IntentAgent(object):
def __init__(self):
self.tools = [self.parse]
<<<<<<< HEAD
self.llm = LLMAgent()

def parse(self, query: str) -> dict:
"""Parse a natural-language portfolio request into holdings + lookback."""
text = self.llm.complete(
prompt=self._build_prompt(query), max_tokens=300, temperature=0.0
).value()
=======
self.model_id = os.environ.get(
"BEDROCK_MODEL_ID", "meta.llama3-8b-instruct-v1:0"
)
Expand All @@ -75,7 +49,6 @@ def parse(self, query: str) -> dict:
region=self.region,
)
text = response["output"]["message"]["content"][0]["text"]
>>>>>>> remotes/origin/telemetry-signals
if not text:
raise ValueError("IntentAgent: LLM returned no output for the request.")

Expand Down Expand Up @@ -148,15 +121,7 @@ def _sanitize(self, parsed: dict) -> dict:


if __name__ == "__main__":
<<<<<<< HEAD
# Assumes the LLMAgent stub (Future-returning) is on the path, as it is
# inside the deployed pipeline.
agent = IntentAgent()
print(agent.parse(
"Analyze 40% Apple, 35% Microsoft and 25% Nvidia over the last 6 months"
=======
agent = IntentAgent()
print(agent.parse(
query="Analyze 40% Apple, 35% Microsoft and 25% Nvidia over the last 6 months"
>>>>>>> remotes/origin/telemetry-signals
))
50 changes: 0 additions & 50 deletions examples/portfolio/agents/llm_agent.py

This file was deleted.

14 changes: 0 additions & 14 deletions examples/portfolio/agents/llm_agent.yaml

This file was deleted.

22 changes: 0 additions & 22 deletions examples/portfolio/config/global_controller.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,6 @@
# reflect each stage's real cost so the scheduler has placement decisions to make.

agents:
<<<<<<< HEAD
# Shared inference node. Owns all Bedrock plumbing; IntentAgent and
# AdvisorAgent delegate their model calls here. LLM-bound — scale replicas
# to match inference demand.
- name: LLMAgent
host: localhost
port: 8075
redis_port: 6379
replicas: 1
resources:
cpu: 1
memory: 512
entrypoint: agents/llm_agent.py

# Stage 0: parse the free-text request into structured holdings + lookback
# window (calls LLMAgent). Cheap CPU, one call per request, on the critical
# path before the fan-out.
- name: IntentAgent
host: localhost
port: 8076
# Stage 0: parse the free-text request into structured holdings + lookback
# window (calls Bedrock directly via ventis.llm.bedrock). Cheap CPU, one
# call per request, on the critical path before the fan-out.
Expand All @@ -36,8 +16,6 @@ agents:
cpu: 1
memory: 256
entrypoint: agents/intent_agent.py

# Stage 0: price history fetch. Network/IO-bound, cheap CPU. Called by
provider: EC2
instance_type: t3.micro

Expand Down
1 change: 0 additions & 1 deletion examples/portfolio/config/policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ rules:
- match: {}
access:
- Workflow
- LLMAgent
- IntentAgent
- PriceAgent
- MetricsAgent
Expand Down
2 changes: 1 addition & 1 deletion examples/text2sql/agents/vllm_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# LLM backend for SQL candidate generation, called remotely by
# SQLGeneratorAgent. Calls AWS Bedrock (Converse API) via ventis.llm.bedrock
# so token/cost telemetry gets recorded onto this execution's
# future:<future_id>:metrics hash — same pattern as
# future:<future_id> hash — same pattern as
# examples/portfolio/agents/advisor_agent.py.
# Configure with env vars:
# BEDROCK_MODEL_ID (default: meta.llama3-8b-instruct-v1:0)
Expand Down
Loading
Loading