-
Notifications
You must be signed in to change notification settings - Fork 46
Added tool to fetch n most recent orders #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release
Are you sure you want to change the base?
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughIntroduces a new Juspay List Orders helper and tool in analytics, adds the tool schema and dispatch wiring, and adds a GENIUS_LIST_ORDERS_API config constant. The tool supports time-bounded queries, limit parameter, IST/UTC handling, and standardized success/failure responses. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Agent
participant Tool as get_last_n_orders (tool)
participant Helper as _make_list_orders_api_request
participant Juspay as Juspay List Orders API
Agent->>Tool: Invoke with startTime, endTime, limit
Tool->>Helper: Build analytics payload (txnsELS, sort by order_created_at)
Helper->>Helper: Convert times (IST/UTC), prepare headers
Helper->>Juspay: HTTP request to GENIUS_LIST_ORDERS_API
alt Success
Juspay-->>Helper: 200 + orders list
Helper-->>Tool: ApiSuccess(data)
Tool-->>Agent: Callback with results
else Timeout/HTTP error/Exception
Juspay-->>Helper: Error/No response
Helper-->>Tool: ApiFailure(error)
Tool-->>Agent: Callback with failure
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (4)
app/core/config.py (1)
91-91: Make base URL configurable (avoid hardcoding prod).Parameterize the List Orders base URL to support non‑prod/staging and easier overrides, mirroring how EULER_DASHBOARD_API_URL is env‑driven.
Apply this diff:
+# Prefer env‑driven base so we can switch between prod/sandbox +JUSPAY_PORTAL_BASE_URL = os.environ.get("JUSPAY_PORTAL_BASE_URL", "https://portal.juspay.in") -GENIUS_LIST_ORDERS_API = "https://portal.juspay.in/ec/v4/orders" +GENIUS_LIST_ORDERS_API = f"{JUSPAY_PORTAL_BASE_URL}/ec/v4/orders"app/agents/voice/automatic/tools/juspay/analytics.py (3)
269-276: Unify error tag to [list_orders_request] and avoid blind except.Standardize the tag for log/searchability and replace broad Exception with typed RequestError handling (diff above). This also satisfies BLE001 from Ruff.
Also applies to: 277-286, 287-295, 169-175
1823-1835: Schema: use integer for limit and add bounds/default.Set limit to integer with a sensible default and min/max to prevent heavy calls.
Apply this diff:
get_last_n_orders_function = FunctionSchema( name="get_last_n_orders", description="Retrieves the most recently placed orders within a specified time range, sorted by creation time in descending order.", properties={ **time_input_schema["properties"], "limit": { - "type": "number", - "description": "Maximum number of recent orders to retrieve. Defaults to 1 to get the single most recent order. Increase this value to get multiple recent orders (e.g., 5 for last 5 orders, 10 for last 10 orders).", + "type": "integer", + "default": 1, + "minimum": 1, + "maximum": 100, + "description": "Maximum number of recent orders to retrieve. Defaults to 1.", }, }, required=time_input_schema["required"], )Optionally, make startTime/endTime optional for this tool to leverage sensible defaults.
3-3: Minor: pytz is fine here; prefer zoneinfo for new code.No change required, but consider stdlib zoneinfo for new modules to avoid pytz quirks. Based on learnings.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/agents/voice/automatic/tools/juspay/analytics.py(7 hunks)app/core/config.py(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/agents/voice/automatic/tools/juspay/analytics.py (2)
app/agents/voice/automatic/types/models.py (2)
ApiFailure(32-35)ApiSuccess(25-28)app/core/transport/http_client.py (1)
create_http_client(32-51)
🪛 Ruff (0.13.1)
app/agents/voice/automatic/tools/juspay/analytics.py
200-200: Do not catch blind exception: Exception
(BLE001)
287-287: Do not catch blind exception: Exception
(BLE001)
🔇 Additional comments (3)
app/agents/voice/automatic/tools/juspay/analytics.py (3)
2075-2076: Tools registration looks good.Tool is added to the standard toolset as expected.
2092-2093: Dispatch mapping looks good.Correctly wired to the tool function name.
210-249: Confirm filter field naming and usage
Payload merges bothfilters.dateCreated(ISO strings) andqFilters(snake_caseorder_created_at/date_createdwith epoch seconds). Verify which keys and formats the Juspay List Orders API actually honors and remove the unused filters to avoid silent failures.
| async def _make_list_orders_api_request( | ||
| params: FunctionCallParams, payload_details: dict | ||
| ) -> GeniusApiResponse: | ||
| """ | ||
| Generic helper to make requests to the Juspay List Orders API. | ||
| Returns a GeniusApiResponse object. | ||
| """ | ||
| if not euler_token: | ||
| logger.error( | ||
| "Tool Error: [list_orders_request] Juspay tool called without required euler_token." | ||
| ) | ||
| return ApiFailure( | ||
| error={"Tool Error": "[list_orders_request] Juspay tool is not configured."} | ||
| ) | ||
|
|
||
| start_time_str = params.arguments.get("startTime") | ||
| end_time_str = params.arguments.get("endTime") | ||
|
|
||
| try: | ||
| ist = pytz.timezone("Asia/Kolkata") | ||
| utc = pytz.utc | ||
| if not start_time_str: | ||
| now_ist = datetime.now(ist) | ||
| start_time_ist = now_ist.replace(hour=0, minute=0, second=0, microsecond=0) | ||
| else: | ||
| start_time_ist = ist.localize( | ||
| datetime.strptime(start_time_str, "%Y-%m-%d %H:%M:%S") | ||
| ) | ||
| start_time_utc = start_time_ist.astimezone(utc) | ||
|
|
||
| if end_time_str: | ||
| end_time_ist = ist.localize( | ||
| datetime.strptime(end_time_str, "%Y-%m-%d %H:%M:%S") | ||
| ) | ||
| else: | ||
| end_time_ist = datetime.now(ist) | ||
| end_time_utc = end_time_ist.astimezone(utc) | ||
|
|
||
| except Exception as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_request] Error converting time for Juspay API: {e}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_request] Invalid time format provided. Please use 'YYYY-MM-DD HH:MM:SS' in IST. Error: {e}" | ||
| } | ||
| ) | ||
|
|
||
| time_field = ( | ||
| "order_created_at" | ||
| if payload_details.get("domain") == "ordersELS" | ||
| else "date_created" | ||
| ) | ||
|
|
||
| end_time_str = end_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
| start_time_str = start_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ") | ||
|
|
||
| end_time_dt = end_time_utc.fromisoformat(end_time_str.replace("Z", "+00:00")) | ||
| start_time_dt = start_time_utc.fromisoformat(start_time_str.replace("Z", "+00:00")) | ||
|
|
||
| if end_time_dt.tzinfo is None: | ||
| end_time_dt = end_time_dt.replace(tzinfo=timezone.utc) | ||
| if start_time_dt.tzinfo is None: | ||
| start_time_dt = start_time_dt.replace(tzinfo=timezone.utc) | ||
|
|
||
| date_from_ts = int(start_time_dt.timestamp()) | ||
| date_to_ts = int(end_time_dt.timestamp()) | ||
|
|
||
| qFilters = { | ||
| "and": { | ||
| "right": { | ||
| "field": time_field, | ||
| "condition": "LessThanEqual", | ||
| "val": str(date_to_ts), | ||
| }, | ||
| "left": { | ||
| "field": time_field, | ||
| "condition": "GreaterThanEqual", | ||
| "val": str(date_from_ts), | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| full_payload = { | ||
| **payload_details, | ||
| "filters": {"dateCreated": {"lte": end_time_str, "gte": start_time_str}}, | ||
| "qFilters": qFilters, | ||
| } | ||
| headers = { | ||
| "Content-Type": "application/json", | ||
| "x-web-logintoken": euler_token, | ||
| } | ||
|
|
||
| logger.info( | ||
| f"Requesting Juspay List Orders API with payload: {json.dumps(full_payload)}" | ||
| ) | ||
|
|
||
| try: | ||
| async with create_http_client(timeout=10.0) as client: | ||
| response = await client.post( | ||
| GENIUS_LIST_ORDERS_API, json=full_payload, headers=headers | ||
| ) | ||
| response.raise_for_status() | ||
| response_text = response.text | ||
| logger.info(f"Received Raw Juspay API text response: {response_text}") | ||
| return ApiSuccess(data=response_text) | ||
| except httpx.TimeoutException: | ||
| logger.error( | ||
| "Tool Error: [list_orders_api_request] Juspay API request timed out after 10 seconds." | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": "[list_orders_api_request] It is taking too much time to process. Please try again." | ||
| } | ||
| ) | ||
| except httpx.HTTPStatusError as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_api_request] HTTP error calling Juspay API: {e.response.status_code} - {e.response.text}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_api_request] Juspay API error: {e.response.status_code}", | ||
| "details": e.response.text, | ||
| } | ||
| ) | ||
| except Exception as e: | ||
| logger.error( | ||
| f"Tool Error: [list_orders_api_request] Unexpected error calling Juspay API: {e}" | ||
| ) | ||
| return ApiFailure( | ||
| error={ | ||
| "Tool Error": f" [list_orders_api_request] An unexpected error occurred: {e}" | ||
| } | ||
| ) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
List Orders helper: tighten time handling, error typing, and logging/redaction.
- Replace string round‑trip with direct microsecond truncation; add httpx.RequestError catch; avoid logging full responses (PII risk); unify error tags; add UA header.
Apply this diff:
@@
- time_field = (
+ time_field = (
"order_created_at"
if payload_details.get("domain") == "ordersELS"
else "date_created"
)
- end_time_str = end_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
- start_time_str = start_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
-
- end_time_dt = end_time_utc.fromisoformat(end_time_str.replace("Z", "+00:00"))
- start_time_dt = start_time_utc.fromisoformat(start_time_str.replace("Z", "+00:00"))
-
- if end_time_dt.tzinfo is None:
- end_time_dt = end_time_dt.replace(tzinfo=timezone.utc)
- if start_time_dt.tzinfo is None:
- start_time_dt = start_time_dt.replace(tzinfo=timezone.utc)
+ end_time_str = end_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+ start_time_str = start_time_utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+ # Drop microseconds without reparsing strings
+ end_time_dt = end_time_utc.replace(microsecond=0)
+ start_time_dt = start_time_utc.replace(microsecond=0)
@@
full_payload = {
**payload_details,
"filters": {"dateCreated": {"lte": end_time_str, "gte": start_time_str}},
"qFilters": qFilters,
}
headers = {
"Content-Type": "application/json",
"x-web-logintoken": euler_token,
+ "user-agent": "ClairvoyanceApp/1.0",
}
@@
- response_text = response.text
- logger.info(f"Received Raw Juspay API text response: {response_text}")
- return ApiSuccess(data=response_text)
+ response_text = response.text
+ # Avoid logging full order payloads (may contain PII)
+ logger.debug(f"Received Juspay List Orders response (len={len(response_text)})")
+ return ApiSuccess(data=response_text)
except httpx.TimeoutException:
- logger.error(
- "Tool Error: [list_orders_api_request] Juspay API request timed out after 10 seconds."
- )
+ logger.error("Tool Error: [list_orders_request] Juspay API request timed out after 10 seconds.")
return ApiFailure(
error={
- "Tool Error": "[list_orders_api_request] It is taking too much time to process. Please try again."
+ "Tool Error": "[list_orders_request] It is taking too much time to process. Please try again."
}
)
+ except httpx.RequestError as e:
+ logger.error(f"Tool Error: [list_orders_request] Network error calling Juspay API: {e}")
+ return ApiFailure(
+ error={
+ "Tool Error": "[list_orders_request] Network error while contacting Juspay API.",
+ "details": str(e),
+ }
+ )
except httpx.HTTPStatusError as e:
- logger.error(
- f"Tool Error: [list_orders_api_request] HTTP error calling Juspay API: {e.response.status_code} - {e.response.text}"
- )
+ logger.error(f"Tool Error: [list_orders_request] HTTP error calling Juspay API: {e.response.status_code} - {e.response.text}")
return ApiFailure(
error={
- "Tool Error": f" [list_orders_api_request] Juspay API error: {e.response.status_code}",
+ "Tool Error": f" [list_orders_request] Juspay API error: {e.response.status_code}",
"details": e.response.text,
}
)
except Exception as e:
- logger.error(
- f"Tool Error: [list_orders_api_request] Unexpected error calling Juspay API: {e}"
- )
+ logger.error(f"Tool Error: [list_orders_request] Unexpected error calling Juspay API: {e}", exc_info=True)
return ApiFailure(
error={
- "Tool Error": f" [list_orders_api_request] An unexpected error occurred: {e}"
+ "Tool Error": f" [list_orders_request] An unexpected error occurred: {e}"
}
)Note: Consider confirming the necessity/contract of both "filters.dateCreated" (camelCase) and "qFilters.date_created" for ec/v4/orders; keep only the supported one to reduce ambiguity.
🏁 Script executed:
#!/bin/bash
file="app/agents/voice/automatic/tools/juspay/analytics.py"
sed -n '1,50p' "$file"Length of output: 1353
🏁 Script executed:
#!/bin/bash
file="app/agents/voice/automatic/tools/juspay/analytics.py"
grep -n "async def _make_list_orders_api_request" "$file"
sed -n '150,330p' "$file"Length of output: 6229
Refactor List Orders helper: microsecond truncation, error handling, logging, and headers
- Replace the round-trip ISO parse with
end_time_dt = end_time_utc.replace(microsecond=0)(and likewise for start) - Catch
httpx.HTTPStatusErrorimmediately afterTimeoutException, thenhttpx.RequestErrorto avoid swallowing HTTP status errors - Downgrade full response logging to
logger.debugwith length only to redact PII - Use a single tag
[list_orders_request]in all log messages and error payloads (remove extra spaces) - Add a
User-Agentheader (e.g."User-Agent": "ClairvoyanceApp/1.0") - Verify whether both
filters.dateCreated(camelCase) andqFiltersare needed—remove the unsupported one to avoid ambiguity
🧰 Tools
🪛 Ruff (0.13.1)
200-200: Do not catch blind exception: Exception
(BLE001)
287-287: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In app/agents/voice/automatic/tools/juspay/analytics.py around lines 162-296,
fix the list orders helper by: 1) replace the round-trip ISO parsing with direct
microsecond-truncated datetimes (use start_time_utc =
start_time_utc.replace(microsecond=0) and same for end_time_utc) and compute
timestamps from those; 2) reorder exception handling to catch
httpx.TimeoutException first, then httpx.HTTPStatusError immediately after, and
then httpx.RequestError/Exception to avoid swallowing status errors; 3) change
full response logging to logger.debug and log only the response length (not full
body) to avoid PII; 4) normalize all log messages and returned error payload
keys to use the single tag "[list_orders_request]" and remove extra spaces in
messages; 5) add a User-Agent header like "User-Agent": "ClairvoyanceApp/1.0" to
headers; 6) remove the unsupported/ambiguous filters.dateCreated field from
full_payload (or confirm and keep only the supported one, likely qFilters) so
only one filter mechanism is sent.
| def get_last_n_orders(params: FunctionCallParams): | ||
| logger.info(f"Fetching last placed order with params: {params.arguments}") | ||
|
|
||
| analytics_payload = { | ||
| "domain": "txnsELS", | ||
| "offset": 0, | ||
| "sortDimension": "order_created_at", | ||
| "limit": params.arguments.get("limit", 1), | ||
| } | ||
|
|
||
| return _make_list_orders_api_request(params, analytics_payload) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Fix sort field for txnsELS and validate limit.
- sortDimension uses order_created_at but domain=txnsELS implies date_created; this likely breaks sorting.
- Sanitize limit to int and clamp to a safe range.
- Log message says “last placed order” (singular).
Apply this diff:
@handle_genius_response
def get_last_n_orders(params: FunctionCallParams):
- logger.info(f"Fetching last placed order with params: {params.arguments}")
+ logger.info(f"Fetching last N orders with params: {params.arguments}")
- analytics_payload = {
- "domain": "txnsELS",
- "offset": 0,
- "sortDimension": "order_created_at",
- "limit": params.arguments.get("limit", 1),
- }
+ # Validate and clamp limit
+ limit_arg = params.arguments.get("limit", 1)
+ try:
+ limit = max(1, min(100, int(limit_arg)))
+ except (TypeError, ValueError):
+ limit = 1
+
+ analytics_payload = {
+ "domain": "txnsELS",
+ "offset": 0,
+ "sortDimension": "date_created", # txnsELS uses date_created
+ "limit": limit,
+ }
return _make_list_orders_api_request(params, analytics_payload)If ec/v4/orders supports specifying sort order explicitly, add it (e.g., descending) per API contract.
🏁 Script executed:
#!/bin/bash
# Search for txnsELS usage in analytics.py
rg -nP "'domain':\s*\"txnsELS\"" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Search for all sortDimension values in analytics.py
rg -nP "'sortDimension':" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Check for date_created occurrences
rg -nP "date_created" -C3 app/agents/voice/automatic/tools/juspay/analytics.py
# Check for order_created_at occurrences
rg -nP "order_created_at" -C3 app/agents/voice/automatic/tools/juspay/analytics.pyLength of output: 612
Fix sorting field and sanitize limit in get_last_n_orders
- Update log to “Fetching last N orders…”
- Replace
"sortDimension": "order_created_at"with"date_created"fortxnsELS - Cast
limitto int and clamp between 1–100 (default 1) - Optionally add explicit sort order (e.g.
"sortOrder": "desc") if supported
app/agents/voice/automatic/tools/juspay/analytics.py lines 571–582
🤖 Prompt for AI Agents
In app/agents/voice/automatic/tools/juspay/analytics.py around lines 571 to 582,
update the function to log "Fetching last N orders…" instead of the current
message; change the analytics_payload sortDimension from "order_created_at" to
"date_created" for the txnsELS domain; read params.arguments.get("limit") and
cast it to int, defaulting to 1, then clamp it to the range 1–100 before setting
analytics_payload["limit"]; and add an explicit "sortOrder": "desc" entry to
analytics_payload if the API supports it to ensure newest orders are returned
first.
01f7cb5 to
cbc8eb4
Compare
Calling /v4/orders api to fetch order level info and to call domain 'txnsELS'.
Added a tool to fetch n most recent orders along with the relevant order details
Summary by CodeRabbit