Skip to content

Commit fc3968a

Browse files
authored
Merge pull request #138 from esnible/currency-converter-logging
Feature: Currency converter logging and A2A error reporting
2 parents 5f5d1f8 + 6891ac2 commit fc3968a

3 files changed

Lines changed: 82 additions & 11 deletions

File tree

a2a/a2a_currency_converter/app/__main__.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,7 @@ class MissingAPIKeyError(Exception):
3737
def main(host, port):
3838
"""Starts the Currency Agent server."""
3939
try:
40-
if not os.getenv('OPENAI_API_KEY'):
41-
raise MissingAPIKeyError(
42-
'OPENAI_API_KEY environment variable not set.'
43-
)
40+
# We don't check OPENAI_API_KEY here. We want the agent pod to run even if OPENAI_API_KEY isn't defined.
4441

4542
capabilities = AgentCapabilities(streaming=True, pushNotifications=True)
4643
skill = AgentSkill(

a2a/a2a_currency_converter/app/agent.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
"""Currency conversion logic for A2A example"""
2+
3+
import os
4+
15
from collections.abc import AsyncIterable
26
from typing import Any, Literal
37

@@ -10,16 +14,19 @@
1014
from langgraph.prebuilt import create_react_agent
1115
from pydantic import BaseModel
1216
from pydantic_settings import BaseSettings
13-
import os
1417
from langchain_openai import ChatOpenAI
1518

1619

1720
memory = MemorySaver()
1821

1922
class Configuration(BaseSettings):
23+
"""The configuration of the Agent"""
24+
2025
llm_model: str = "gpt-4o"
2126
llm_api_base: str = ""
22-
llm_api_key: str = os.getenv("OPENAI_API_KEY")
27+
# We don't want the pod to crash without a valid key.
28+
# Report authentication error to A2A user instead.
29+
llm_api_key: str = os.getenv("OPENAI_API_KEY", "Failed to load env var OPENAI_API_KEY")
2330

2431

2532
config = Configuration()

a2a/a2a_currency_converter/app/agent_executor.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1+
"""
2+
Example of the business logic of an A2A agent for currency conversion.
3+
"""
4+
15
import logging
6+
import os
7+
8+
from app.agent import CurrencyAgent
9+
10+
from openai import AuthenticationError, InternalServerError
211

312
from a2a.server.agent_execution import AgentExecutor, RequestContext
413
from a2a.server.events import EventQueue
@@ -18,9 +27,6 @@
1827
)
1928
from a2a.utils.errors import ServerError
2029

21-
from app.agent import CurrencyAgent
22-
23-
2430
logging.basicConfig(level=logging.INFO)
2531
logger = logging.getLogger(__name__)
2632

@@ -38,12 +44,14 @@ async def execute(
3844
) -> None:
3945
error = self._validate_request(context)
4046
if error:
47+
logger.warning(f'Invalid agent executor request: {context}')
4148
raise ServerError(error=InvalidParamsError())
4249

4350
query = context.get_user_input()
4451
task = context.current_task
4552
if not task:
4653
task = new_task(context.message)
54+
logger.info(f'Created task for message : {context.message}')
4755
event_queue.enqueue_event(task)
4856
updater = TaskUpdater(event_queue, task.id, task.contextId)
4957
try:
@@ -52,6 +60,7 @@ async def execute(
5260
require_user_input = item['require_user_input']
5361

5462
if not is_task_complete and not require_user_input:
63+
logger.info(f'Updating status for non-input task: {task.id}')
5564
updater.update_status(
5665
TaskState.working,
5766
new_agent_text_message(
@@ -61,6 +70,7 @@ async def execute(
6170
),
6271
)
6372
elif require_user_input:
73+
logger.info(f'Updating status for input task: {task.id}')
6474
updater.update_status(
6575
TaskState.input_required,
6676
new_agent_text_message(
@@ -72,21 +82,78 @@ async def execute(
7282
)
7383
break
7484
else:
85+
logger.info('Adding artifact for item')
7586
updater.add_artifact(
7687
[Part(root=TextPart(text=item['content']))],
7788
name='conversion_result',
7889
)
7990
updater.complete()
8091
break
8192

93+
except InternalServerError as e:
94+
msg=f"""CurrencyAgentExecutor reports an InternalServerError error.
95+
96+
This can happen if the agent's LLM_API_BASE environment variable does not point to an OpenAI server.
97+
98+
LLM_API_BASE is {os.getenv("LLM_API_BASE", "undefined")}
99+
100+
Use `kubectl -n <namespace> logs deployment/<agent-name>` for details.
101+
"""
102+
logger.error(msg=msg)
103+
logger.error(msg=f"Raw InternalServerError: {e}")
104+
updater.update_status(
105+
TaskState.input_required,
106+
new_agent_text_message(
107+
msg,
108+
task.contextId,
109+
task.id,
110+
),
111+
final=True,
112+
)
113+
114+
except AuthenticationError as e:
115+
msg=f"""CurrencyAgentExecutor reports an authentication error.
116+
117+
When importing this agent into Kagenti, expand Environment Variables and Add Variable,
118+
or import https://github.com/kagenti/agent-examples/blob/main/a2a/a2a_currency_converter/.env.openai
119+
120+
Use `kubectl -n <namespace> logs deployment/<agent-name>` for details.
121+
122+
Also check
123+
`kubectl -n <namespace> get secret openai-secret -o jsonpath="{'{'}.data.apikey{'}'}" | base64 -d`
124+
The key should match your OpenAI key."""
125+
logger.error(msg=msg)
126+
logger.error(msg=f"Raw AuthenticationError {e}")
127+
updater.update_status(
128+
TaskState.input_required,
129+
new_agent_text_message(
130+
msg,
131+
task.contextId,
132+
task.id,
133+
),
134+
final=True,
135+
)
136+
82137
except Exception as e:
83138
logger.error(f'An error occurred while streaming the response: {e}')
139+
logger.info(msg=f'The error is a {type(e)}')
140+
updater.update_status(
141+
TaskState.input_required,
142+
new_agent_text_message(
143+
# We don't show the error to the user, as it may have credentials
144+
"""Internal error on the agent.
145+
Use `kubectl -n <namespace> logs deployment/<agent-name>` for details""",
146+
task.contextId,
147+
task.id,
148+
),
149+
final=True,
150+
)
84151
raise ServerError(error=InternalError()) from e
85152

86-
def _validate_request(self, context: RequestContext) -> bool:
153+
def _validate_request(self, _: RequestContext) -> bool:
87154
return False
88155

89156
async def cancel(
90-
self, request: RequestContext, event_queue: EventQueue
157+
self, _: RequestContext, event_queue: EventQueue
91158
) -> Task | None:
92159
raise ServerError(error=UnsupportedOperationError())

0 commit comments

Comments
 (0)