Skip to content

Commit 736e68f

Browse files
committed
Use the local benchmark from the ai-generations
1 parent 0b0e9e0 commit 736e68f

File tree

1 file changed

+192
-19
lines changed

1 file changed

+192
-19
lines changed

tests/benchmark_search.py

Lines changed: 192 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,135 @@
66
- Semantic Search: ~84% Hit@5
77
- Improvement: 4x
88
9-
Run with:
9+
Run with production API:
1010
STACKONE_API_KEY=xxx python tests/benchmark_search.py
11+
12+
Run with local Lambda (ai-generation/apps/action_search):
13+
# First, start the local Lambda:
14+
# cd ai-generation/apps/action_search && make run-local
15+
# Then run benchmark:
16+
python tests/benchmark_search.py --local
17+
18+
Environment Variables:
19+
STACKONE_API_KEY: Required for production mode
20+
LOCAL_LAMBDA_URL: Optional, defaults to http://localhost:4513/2015-03-31/functions/function/invocations
1121
"""
1222

1323
from __future__ import annotations
1424

25+
import argparse
1526
import os
1627
import time
1728
from dataclasses import dataclass, field
18-
from typing import Literal
29+
from typing import Any, Literal, Protocol
30+
31+
import httpx
1932

2033
from stackone_ai import StackOneToolSet
21-
from stackone_ai.semantic_search import SemanticSearchClient
34+
from stackone_ai.semantic_search import SemanticSearchClient, SemanticSearchResponse, SemanticSearchResult
2235
from stackone_ai.utility_tools import ToolIndex
2336

37+
# Default local Lambda URL (from ai-generation/apps/action_search docker-compose)
38+
DEFAULT_LOCAL_LAMBDA_URL = "http://localhost:4513/2015-03-31/functions/function/invocations"
39+
40+
41+
class SearchClientProtocol(Protocol):
42+
"""Protocol for search clients (production or local)."""
43+
44+
def search(
45+
self,
46+
query: str,
47+
connector: str | None = None,
48+
top_k: int = 10,
49+
) -> SemanticSearchResponse: ...
50+
51+
52+
class LocalLambdaSearchClient:
53+
"""Client for local action_search Lambda.
54+
55+
This client connects to the local Lambda running via docker-compose
56+
from ai-generation/apps/action_search.
57+
58+
Usage:
59+
# Start local Lambda first:
60+
# cd ai-generation/apps/action_search && make run-local
61+
62+
client = LocalLambdaSearchClient()
63+
response = client.search("create employee", connector="bamboohr", top_k=5)
64+
"""
65+
66+
def __init__(
67+
self,
68+
lambda_url: str = DEFAULT_LOCAL_LAMBDA_URL,
69+
timeout: float = 30.0,
70+
) -> None:
71+
"""Initialize the local Lambda client.
72+
73+
Args:
74+
lambda_url: URL of the local Lambda endpoint
75+
timeout: Request timeout in seconds
76+
"""
77+
self.lambda_url = lambda_url
78+
self.timeout = timeout
79+
80+
def search(
81+
self,
82+
query: str,
83+
connector: str | None = None,
84+
top_k: int = 10,
85+
) -> SemanticSearchResponse:
86+
"""Search for relevant actions using local Lambda.
87+
88+
Args:
89+
query: Natural language query
90+
connector: Optional connector filter
91+
top_k: Maximum number of results
92+
93+
Returns:
94+
SemanticSearchResponse with matching actions
95+
"""
96+
# Lambda event envelope format
97+
payload: dict[str, Any] = {
98+
"type": "search",
99+
"payload": {
100+
"query": query,
101+
"top_k": top_k,
102+
},
103+
}
104+
if connector:
105+
payload["payload"]["connector"] = connector
106+
107+
try:
108+
response = httpx.post(
109+
self.lambda_url,
110+
json=payload,
111+
headers={"Content-Type": "application/json"},
112+
timeout=self.timeout,
113+
)
114+
response.raise_for_status()
115+
data = response.json()
116+
117+
# Convert Lambda response to SemanticSearchResponse
118+
results = [
119+
SemanticSearchResult(
120+
action_name=r.get("action_name", ""),
121+
connector_key=r.get("connector_key", ""),
122+
similarity_score=r.get("similarity_score", 0.0),
123+
label=r.get("label", ""),
124+
description=r.get("description", ""),
125+
)
126+
for r in data.get("results", [])
127+
]
128+
return SemanticSearchResponse(
129+
results=results,
130+
total_count=data.get("total_count", len(results)),
131+
query=data.get("query", query),
132+
)
133+
except httpx.RequestError as e:
134+
raise RuntimeError(f"Local Lambda request failed: {e}") from e
135+
except Exception as e:
136+
raise RuntimeError(f"Local Lambda search failed: {e}") from e
137+
24138

25139
@dataclass
26140
class EvaluationTask:
@@ -778,19 +892,17 @@ class SearchBenchmark:
778892
def __init__(
779893
self,
780894
tools: list,
781-
api_key: str,
782-
base_url: str = "https://api.stackone.com",
895+
semantic_client: SearchClientProtocol,
783896
):
784-
"""Initialize benchmark with tools and API credentials.
897+
"""Initialize benchmark with tools and search client.
785898
786899
Args:
787900
tools: List of StackOneTool instances to search
788-
api_key: StackOne API key for semantic search
789-
base_url: Base URL for API requests
901+
semantic_client: Client for semantic search (production or local)
790902
"""
791903
self.tools = tools
792904
self.local_index = ToolIndex(tools)
793-
self.semantic_client = SemanticSearchClient(api_key=api_key, base_url=base_url)
905+
self.semantic_client = semantic_client
794906

795907
def evaluate_local(
796908
self,
@@ -942,22 +1054,38 @@ def print_report(report: ComparisonReport) -> None:
9421054
print(f" ... and {len(failed_local) - 10} more")
9431055

9441056

945-
def run_benchmark(api_key: str | None = None, base_url: str = "https://api.stackone.com") -> ComparisonReport:
1057+
def run_benchmark(
1058+
api_key: str | None = None,
1059+
base_url: str = "https://api.stackone.com",
1060+
use_local: bool = False,
1061+
local_lambda_url: str = DEFAULT_LOCAL_LAMBDA_URL,
1062+
) -> ComparisonReport:
9461063
"""Run the full benchmark comparison.
9471064
9481065
Args:
9491066
api_key: StackOne API key (uses STACKONE_API_KEY env var if not provided)
950-
base_url: Base URL for API requests
1067+
base_url: Base URL for production API requests
1068+
use_local: If True, use local Lambda instead of production API
1069+
local_lambda_url: URL of local Lambda endpoint
9511070
9521071
Returns:
9531072
ComparisonReport with results
9541073
9551074
Raises:
956-
ValueError: If no API key is available
1075+
ValueError: If no API key is available (production mode only)
9571076
"""
958-
api_key = api_key or os.environ.get("STACKONE_API_KEY")
959-
if not api_key:
960-
raise ValueError("API key must be provided or set via STACKONE_API_KEY environment variable")
1077+
# Create semantic search client based on mode
1078+
if use_local:
1079+
print(f"Using LOCAL Lambda at: {local_lambda_url}")
1080+
semantic_client: SearchClientProtocol = LocalLambdaSearchClient(lambda_url=local_lambda_url)
1081+
# For local mode, we still need API key for toolset but can use a dummy if not set
1082+
api_key = api_key or os.environ.get("STACKONE_API_KEY") or "local-testing"
1083+
else:
1084+
api_key = api_key or os.environ.get("STACKONE_API_KEY")
1085+
if not api_key:
1086+
raise ValueError("API key must be provided or set via STACKONE_API_KEY environment variable")
1087+
print(f"Using PRODUCTION API at: {base_url}")
1088+
semantic_client = SemanticSearchClient(api_key=api_key, base_url=base_url)
9611089

9621090
print("Initializing toolset...")
9631091
toolset = StackOneToolSet(api_key=api_key, base_url=base_url)
@@ -967,21 +1095,66 @@ def run_benchmark(api_key: str | None = None, base_url: str = "https://api.stack
9671095
print(f"Loaded {len(tools)} tools")
9681096

9691097
print(f"\nRunning benchmark with {len(EVALUATION_TASKS)} evaluation tasks...")
970-
benchmark = SearchBenchmark(list(tools), api_key=api_key, base_url=base_url)
1098+
benchmark = SearchBenchmark(list(tools), semantic_client=semantic_client)
9711099

9721100
report = benchmark.compare()
9731101
print_report(report)
9741102

9751103
return report
9761104

9771105

978-
if __name__ == "__main__":
1106+
def main() -> None:
1107+
"""Main entry point with CLI argument parsing."""
1108+
parser = argparse.ArgumentParser(
1109+
description="Benchmark comparing local BM25+TF-IDF vs semantic search",
1110+
formatter_class=argparse.RawDescriptionHelpFormatter,
1111+
epilog="""
1112+
Examples:
1113+
# Run with production API
1114+
STACKONE_API_KEY=xxx python tests/benchmark_search.py
1115+
1116+
# Run with local Lambda (start it first: cd ai-generation/apps/action_search && make run-local)
1117+
python tests/benchmark_search.py --local
1118+
1119+
# Run with custom local Lambda URL
1120+
python tests/benchmark_search.py --local --lambda-url http://localhost:9000/invoke
1121+
""",
1122+
)
1123+
parser.add_argument(
1124+
"--local",
1125+
action="store_true",
1126+
help="Use local Lambda instead of production API",
1127+
)
1128+
parser.add_argument(
1129+
"--lambda-url",
1130+
default=DEFAULT_LOCAL_LAMBDA_URL,
1131+
help=f"Local Lambda URL (default: {DEFAULT_LOCAL_LAMBDA_URL})",
1132+
)
1133+
parser.add_argument(
1134+
"--api-url",
1135+
default="https://api.stackone.com",
1136+
help="Production API base URL",
1137+
)
1138+
1139+
args = parser.parse_args()
1140+
9791141
try:
980-
run_benchmark()
1142+
run_benchmark(
1143+
base_url=args.api_url,
1144+
use_local=args.local,
1145+
local_lambda_url=args.lambda_url,
1146+
)
9811147
except ValueError as e:
9821148
print(f"Error: {e}")
983-
print("Set STACKONE_API_KEY environment variable or pass api_key parameter")
1149+
print("Set STACKONE_API_KEY environment variable or use --local flag")
9841150
exit(1)
9851151
except Exception as e:
9861152
print(f"Benchmark failed: {e}")
1153+
import traceback
1154+
1155+
traceback.print_exc()
9871156
exit(1)
1157+
1158+
1159+
if __name__ == "__main__":
1160+
main()

0 commit comments

Comments
 (0)