golf-mcp 0.1.20__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of golf-mcp might be problematic. Click here for more details.
- golf/__init__.py +9 -1
- golf/_endpoints.py +6 -0
- golf/_endpoints_fallback.py +10 -0
- golf/auth/__init__.py +188 -84
- golf/auth/api_key.py +6 -14
- golf/auth/factory.py +333 -0
- golf/auth/helpers.py +12 -42
- golf/auth/providers.py +396 -0
- golf/auth/registry.py +256 -0
- golf/cli/branding.py +192 -0
- golf/cli/main.py +28 -69
- golf/commands/__init__.py +2 -0
- golf/commands/build.py +4 -7
- golf/commands/init.py +30 -53
- golf/commands/run.py +50 -20
- golf/core/builder.py +355 -414
- golf/core/builder_auth.py +63 -144
- golf/core/builder_telemetry.py +26 -3
- golf/core/config.py +38 -59
- golf/core/parser.py +132 -139
- golf/core/platform.py +12 -10
- golf/core/telemetry.py +11 -19
- golf/core/transformer.py +38 -15
- golf/examples/__pycache__/__init__.cpython-311.pyc +0 -0
- golf/examples/basic/.coverage +0 -0
- golf/examples/basic/.env.example +8 -4
- golf/examples/basic/README.md +117 -45
- golf/examples/basic/__pycache__/auth.cpython-311.pyc +0 -0
- golf/examples/basic/auth.py +76 -0
- golf/examples/basic/golf.json +2 -5
- golf/examples/basic/htmlcov/.gitignore +2 -0
- golf/examples/basic/htmlcov/class_index.html +547 -0
- golf/examples/basic/htmlcov/coverage_html_cb_6fb7b396.js +733 -0
- golf/examples/basic/htmlcov/favicon_32_cb_58284776.png +0 -0
- golf/examples/basic/htmlcov/function_index.html +2091 -0
- golf/examples/basic/htmlcov/index.html +349 -0
- golf/examples/basic/htmlcov/keybd_closed_cb_ce680311.png +0 -0
- golf/examples/basic/htmlcov/status.json +1 -0
- golf/examples/basic/htmlcov/style_cb_8e611ae1.css +337 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496___init___py.html +323 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_api_key_py.html +170 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_factory_py.html +430 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_helpers_py.html +288 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_providers_py.html +493 -0
- golf/examples/basic/htmlcov/z_1c9a91c0e91c8496_registry_py.html +353 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950___init___py.html +120 -0
- golf/examples/basic/htmlcov/z_3ec3b3f490dc0950_instrumentation_py.html +1535 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_branding_py.html +289 -0
- golf/examples/basic/htmlcov/z_4b8b9dd4ccccc5db_main_py.html +476 -0
- golf/examples/basic/htmlcov/z_5a6c4e6bcc86fb2f___init___py.html +97 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d___init___py.html +102 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_build_py.html +178 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_init_py.html +387 -0
- golf/examples/basic/htmlcov/z_6cadab9ec0df475d_run_py.html +222 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4___init___py.html +106 -0
- golf/examples/basic/htmlcov/z_6fcdee0582ba84e4__endpoints_fallback_py.html +107 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217___init___py.html +98 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_auth_py.html +306 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_metrics_py.html +329 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_py.html +1471 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_builder_telemetry_py.html +186 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_config_py.html +315 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_parser_py.html +1149 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_platform_py.html +279 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_telemetry_py.html +589 -0
- golf/examples/basic/htmlcov/z_7ba499ed22986217_transformer_py.html +286 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688___init___py.html +107 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_collector_py.html +417 -0
- golf/examples/basic/htmlcov/z_7d7da37693a43688_registry_py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e___init___py.html +109 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_context_py.html +150 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_elicitation_py.html +267 -0
- golf/examples/basic/htmlcov/z_abe733142b40ad4e_sampling_py.html +318 -0
- golf/examples/basic/prompts/__pycache__/welcome.cpython-311.pyc +0 -0
- golf/examples/basic/prompts/welcome.py +3 -5
- golf/examples/basic/resources/__pycache__/current_time.cpython-311.pyc +0 -0
- golf/examples/basic/resources/__pycache__/info.cpython-311.pyc +0 -0
- golf/examples/basic/resources/current_time.py +5 -13
- golf/examples/basic/resources/weather/__pycache__/common.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/current.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/__pycache__/forecast.cpython-311.pyc +0 -0
- golf/examples/basic/resources/weather/city.py +46 -0
- golf/examples/basic/resources/weather/common.py +4 -11
- golf/examples/basic/resources/weather/current.py +5 -5
- golf/examples/basic/resources/weather/forecast.py +5 -5
- golf/examples/basic/tools/__pycache__/calculator.cpython-311.pyc +0 -0
- golf/examples/basic/tools/calculator.py +94 -0
- golf/examples/basic/tools/say/__pycache__/hello.cpython-311.pyc +0 -0
- golf/examples/basic/tools/say/hello.py +65 -0
- golf/metrics/collector.py +100 -19
- golf/telemetry/__init__.py +4 -0
- golf/telemetry/instrumentation.py +484 -178
- golf/utilities/__init__.py +12 -0
- golf/utilities/context.py +53 -0
- golf/utilities/elicitation.py +170 -0
- golf/utilities/sampling.py +221 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/METADATA +51 -104
- golf_mcp-0.2.0.dist-info/RECORD +110 -0
- golf/auth/oauth.py +0 -861
- golf/auth/provider.py +0 -115
- golf/examples/api_key/.env +0 -2
- golf/examples/api_key/.env.example +0 -1
- golf/examples/api_key/README.md +0 -84
- golf/examples/api_key/golf.json +0 -8
- golf/examples/api_key/pre_build.py +0 -11
- golf/examples/api_key/tools/issues/create.py +0 -93
- golf/examples/api_key/tools/issues/list.py +0 -92
- golf/examples/api_key/tools/repos/list.py +0 -111
- golf/examples/api_key/tools/search/code.py +0 -106
- golf/examples/api_key/tools/users/get.py +0 -82
- golf/examples/basic/.env +0 -5
- golf/examples/basic/pre_build.py +0 -28
- golf/examples/basic/tools/github_user.py +0 -65
- golf/examples/basic/tools/hello.py +0 -34
- golf/examples/basic/tools/payments/charge.py +0 -70
- golf/examples/basic/tools/payments/common.py +0 -36
- golf/examples/basic/tools/payments/refund.py +0 -61
- golf_mcp-0.1.20.dist-info/RECORD +0 -60
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/WHEEL +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/entry_points.txt +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {golf_mcp-0.1.20.dist-info → golf_mcp-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,7 @@ sharing functionality across multiple resources in a directory.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
# Read configuration from environment variables
|
|
10
11
|
WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY", "mock_key")
|
|
@@ -15,30 +16,22 @@ TEMPERATURE_UNIT = os.environ.get("WEATHER_TEMP_UNIT", "fahrenheit")
|
|
|
15
16
|
class WeatherApiClient:
|
|
16
17
|
"""Mock weather API client."""
|
|
17
18
|
|
|
18
|
-
def __init__(
|
|
19
|
-
self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL
|
|
20
|
-
) -> None:
|
|
19
|
+
def __init__(self, api_key: str = WEATHER_API_KEY, api_url: str = WEATHER_API_URL) -> None:
|
|
21
20
|
self.api_key = api_key
|
|
22
21
|
self.api_url = api_url
|
|
23
22
|
self.unit = TEMPERATURE_UNIT
|
|
24
23
|
|
|
25
|
-
async def get_forecast(self, city: str, days: int = 3):
|
|
24
|
+
async def get_forecast(self, city: str, days: int = 3) -> dict[str, Any]:
|
|
26
25
|
"""Get weather forecast for a city (mock implementation)."""
|
|
27
26
|
# This would make an API call in a real implementation
|
|
28
|
-
print(
|
|
29
|
-
f"Would call {self.api_url}/forecast/{city} with API key {self.api_key[:4]}..."
|
|
30
|
-
)
|
|
31
27
|
return {
|
|
32
28
|
"city": city,
|
|
33
29
|
"unit": self.unit,
|
|
34
30
|
"forecast": [{"day": i, "temp": 70 + i} for i in range(days)],
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
async def get_current(self, city: str):
|
|
33
|
+
async def get_current(self, city: str) -> dict[str, Any]:
|
|
38
34
|
"""Get current weather for a city (mock implementation)."""
|
|
39
|
-
print(
|
|
40
|
-
f"Would call {self.api_url}/current/{city} with API key {self.api_key[:4]}..."
|
|
41
|
-
)
|
|
42
35
|
return {
|
|
43
36
|
"city": city,
|
|
44
37
|
"unit": self.unit,
|
|
@@ -6,19 +6,19 @@ from typing import Any
|
|
|
6
6
|
from .common import weather_client
|
|
7
7
|
|
|
8
8
|
# The URI that clients will use to access this resource
|
|
9
|
-
resource_uri = "weather://current
|
|
9
|
+
resource_uri = "weather://current"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
async def current_weather(
|
|
13
|
-
"""Provide current weather for
|
|
12
|
+
async def current_weather() -> dict[str, Any]:
|
|
13
|
+
"""Provide current weather for a default city.
|
|
14
14
|
|
|
15
15
|
This example demonstrates:
|
|
16
16
|
1. Nested resource organization (resources/weather/current.py)
|
|
17
|
-
2.
|
|
17
|
+
2. Resource without URI parameters
|
|
18
18
|
3. Using shared client from the common.py file
|
|
19
19
|
"""
|
|
20
20
|
# Use the shared weather client from common.py
|
|
21
|
-
weather_data = await weather_client.get_current(
|
|
21
|
+
weather_data = await weather_client.get_current("New York")
|
|
22
22
|
|
|
23
23
|
# Add some additional data
|
|
24
24
|
weather_data.update(
|
|
@@ -6,19 +6,19 @@ from typing import Any
|
|
|
6
6
|
from .common import weather_client
|
|
7
7
|
|
|
8
8
|
# The URI that clients will use to access this resource
|
|
9
|
-
resource_uri = "weather://forecast
|
|
9
|
+
resource_uri = "weather://forecast"
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
async def forecast_weather(
|
|
13
|
-
"""Provide a weather forecast for
|
|
12
|
+
async def forecast_weather() -> dict[str, Any]:
|
|
13
|
+
"""Provide a weather forecast for a default city.
|
|
14
14
|
|
|
15
15
|
This example demonstrates:
|
|
16
16
|
1. Nested resource organization (resources/weather/forecast.py)
|
|
17
|
-
2.
|
|
17
|
+
2. Resource without URI parameters
|
|
18
18
|
3. Using shared client from the common.py file
|
|
19
19
|
"""
|
|
20
20
|
# Use the shared weather client from common.py
|
|
21
|
-
forecast_data = await weather_client.get_forecast(
|
|
21
|
+
forecast_data = await weather_client.get_forecast("New York", days=5)
|
|
22
22
|
|
|
23
23
|
# Add some additional data
|
|
24
24
|
forecast_data.update(
|
|
Binary file
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Enhanced calculator tool with optional LLM-powered explanations."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from golf.utilities import sample
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CalculationResult(BaseModel):
|
|
10
|
+
"""Result of a mathematical calculation."""
|
|
11
|
+
|
|
12
|
+
result: float
|
|
13
|
+
operation: str
|
|
14
|
+
expression: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def calculate(
|
|
18
|
+
expression: Annotated[
|
|
19
|
+
str,
|
|
20
|
+
Field(
|
|
21
|
+
description="Mathematical expression to evaluate (e.g., '2 + 3', '10 * 5', '100 / 4')",
|
|
22
|
+
examples=["2 + 3", "10 * 5.5", "(8 - 3) * 2"],
|
|
23
|
+
),
|
|
24
|
+
],
|
|
25
|
+
explain: Annotated[
|
|
26
|
+
bool,
|
|
27
|
+
Field(
|
|
28
|
+
description="Whether to provide an LLM-powered step-by-step explanation",
|
|
29
|
+
default=False,
|
|
30
|
+
),
|
|
31
|
+
] = False,
|
|
32
|
+
) -> CalculationResult:
|
|
33
|
+
"""Evaluate a mathematical expression with optional LLM explanation.
|
|
34
|
+
|
|
35
|
+
This enhanced calculator can:
|
|
36
|
+
- Perform basic arithmetic operations (+, -, *, /, parentheses)
|
|
37
|
+
- Handle decimal numbers
|
|
38
|
+
- Optionally provide LLM-powered step-by-step explanations
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
- calculate("2 + 3") → 5
|
|
42
|
+
- calculate("10 * 5.5") → 55.0
|
|
43
|
+
- calculate("(8 - 3) * 2", explain=True) → 10 with explanation
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
# Simple expression evaluation using eval (safe for basic math)
|
|
47
|
+
# In production, consider using a proper math expression parser
|
|
48
|
+
allowed_chars = set("0123456789+-*/.() ")
|
|
49
|
+
if not all(c in allowed_chars for c in expression):
|
|
50
|
+
raise ValueError("Expression contains invalid characters")
|
|
51
|
+
|
|
52
|
+
# Evaluate the expression
|
|
53
|
+
result = eval(expression, {"__builtins__": {}}, {})
|
|
54
|
+
|
|
55
|
+
# Ensure result is a number
|
|
56
|
+
if not isinstance(result, (int, float)):
|
|
57
|
+
raise ValueError("Expression did not evaluate to a number")
|
|
58
|
+
|
|
59
|
+
# Generate explanation if requested
|
|
60
|
+
result_expression = expression
|
|
61
|
+
if explain:
|
|
62
|
+
try:
|
|
63
|
+
explanation = await sample(
|
|
64
|
+
f"Explain this mathematical expression step by step: {expression} = {result}",
|
|
65
|
+
system_prompt="You are a helpful math tutor. Provide clear, step-by-step explanations.",
|
|
66
|
+
max_tokens=200,
|
|
67
|
+
)
|
|
68
|
+
result_expression = f"{expression}\n\nExplanation: {explanation}"
|
|
69
|
+
except Exception:
|
|
70
|
+
# If sampling fails, continue without explanation
|
|
71
|
+
result_expression = f"{expression}\n\n(Explanation unavailable)"
|
|
72
|
+
|
|
73
|
+
return CalculationResult(
|
|
74
|
+
result=float(result),
|
|
75
|
+
operation="evaluate",
|
|
76
|
+
expression=result_expression,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
except ZeroDivisionError:
|
|
80
|
+
return CalculationResult(
|
|
81
|
+
result=float("inf"),
|
|
82
|
+
operation="error",
|
|
83
|
+
expression=f"{expression} → Division by zero",
|
|
84
|
+
)
|
|
85
|
+
except Exception as e:
|
|
86
|
+
return CalculationResult(
|
|
87
|
+
result=0.0,
|
|
88
|
+
operation="error",
|
|
89
|
+
expression=f"{expression} → Error: {str(e)}",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Export the tool
|
|
94
|
+
export = calculate
|
|
Binary file
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Enhanced hello tool with elicitation capabilities."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
from golf.utilities import elicit
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Output(BaseModel):
|
|
10
|
+
"""Response from the hello tool."""
|
|
11
|
+
|
|
12
|
+
message: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async def hello(
|
|
16
|
+
name: Annotated[str, Field(description="The name of the person to greet")] = "World",
|
|
17
|
+
greeting: Annotated[str, Field(description="The greeting phrase to use")] = "Hello",
|
|
18
|
+
personalized: Annotated[
|
|
19
|
+
bool,
|
|
20
|
+
Field(
|
|
21
|
+
description="Whether to ask for additional personal details to create a personalized greeting",
|
|
22
|
+
default=False,
|
|
23
|
+
),
|
|
24
|
+
] = False,
|
|
25
|
+
) -> Output:
|
|
26
|
+
"""Say hello with optional personalized elicitation.
|
|
27
|
+
|
|
28
|
+
This enhanced tool can:
|
|
29
|
+
- Provide basic greetings
|
|
30
|
+
- Elicit additional personal information for personalized messages
|
|
31
|
+
- Demonstrate Golf's elicitation capabilities
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
- hello("Alice") → "Hello, Alice!"
|
|
35
|
+
- hello("Bob", personalized=True) → Asks for details, then personalized greeting
|
|
36
|
+
"""
|
|
37
|
+
# Basic greeting
|
|
38
|
+
basic_message = f"{greeting}, {name}!"
|
|
39
|
+
|
|
40
|
+
# If personalized greeting is requested, elicit additional info
|
|
41
|
+
if personalized:
|
|
42
|
+
try:
|
|
43
|
+
# Ask for user's mood
|
|
44
|
+
mood = await elicit(
|
|
45
|
+
"How are you feeling today?",
|
|
46
|
+
["happy", "excited", "calm", "focused", "creative"],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create personalized message
|
|
50
|
+
personalized_message = f"{greeting}, {name}! Hope you're having a {mood} day!"
|
|
51
|
+
|
|
52
|
+
return Output(message=personalized_message)
|
|
53
|
+
|
|
54
|
+
except Exception as e:
|
|
55
|
+
# If elicitation fails, fall back to basic greeting
|
|
56
|
+
print(f"Personalization failed: {e}")
|
|
57
|
+
return Output(message=f"{basic_message} (personalization unavailable)")
|
|
58
|
+
|
|
59
|
+
# Return basic greeting
|
|
60
|
+
print(f"{greeting} {name}...")
|
|
61
|
+
return Output(message=basic_message)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Designate the entry point function
|
|
65
|
+
export = hello
|
golf/metrics/collector.py
CHANGED
|
@@ -66,6 +66,38 @@ class MetricsCollector:
|
|
|
66
66
|
["prompt_name"],
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
+
# Sampling metrics
|
|
70
|
+
self._metrics["sampling_requests"] = Counter(
|
|
71
|
+
"golf_sampling_requests_total",
|
|
72
|
+
"Total number of sampling requests",
|
|
73
|
+
["sampling_type", "status"],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
self._metrics["sampling_duration"] = Histogram(
|
|
77
|
+
"golf_sampling_duration_seconds",
|
|
78
|
+
"Sampling request duration in seconds",
|
|
79
|
+
["sampling_type"],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
self._metrics["sampling_tokens"] = Histogram(
|
|
83
|
+
"golf_sampling_tokens",
|
|
84
|
+
"Number of tokens in sampling responses",
|
|
85
|
+
["sampling_type"],
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Elicitation metrics
|
|
89
|
+
self._metrics["elicitation_requests"] = Counter(
|
|
90
|
+
"golf_elicitation_requests_total",
|
|
91
|
+
"Total number of elicitation requests",
|
|
92
|
+
["elicitation_type", "status"],
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self._metrics["elicitation_duration"] = Histogram(
|
|
96
|
+
"golf_elicitation_duration_seconds",
|
|
97
|
+
"Elicitation request duration in seconds",
|
|
98
|
+
["elicitation_type"],
|
|
99
|
+
)
|
|
100
|
+
|
|
69
101
|
# Error metrics
|
|
70
102
|
self._metrics["errors"] = Counter(
|
|
71
103
|
"golf_errors_total",
|
|
@@ -74,18 +106,14 @@ class MetricsCollector:
|
|
|
74
106
|
)
|
|
75
107
|
|
|
76
108
|
# Session metrics
|
|
77
|
-
self._metrics["sessions_total"] = Counter(
|
|
78
|
-
"golf_sessions_total", "Total number of sessions created"
|
|
79
|
-
)
|
|
109
|
+
self._metrics["sessions_total"] = Counter("golf_sessions_total", "Total number of sessions created")
|
|
80
110
|
|
|
81
111
|
self._metrics["session_duration"] = Histogram(
|
|
82
112
|
"golf_session_duration_seconds", "Session duration in seconds"
|
|
83
113
|
)
|
|
84
114
|
|
|
85
115
|
# System metrics
|
|
86
|
-
self._metrics["uptime"] = Gauge(
|
|
87
|
-
"golf_uptime_seconds", "Server uptime in seconds"
|
|
88
|
-
)
|
|
116
|
+
self._metrics["uptime"] = Gauge("golf_uptime_seconds", "Server uptime in seconds")
|
|
89
117
|
|
|
90
118
|
except ImportError:
|
|
91
119
|
# Prometheus client not available, disable metrics
|
|
@@ -101,9 +129,7 @@ class MetricsCollector:
|
|
|
101
129
|
if not self.enabled or "tool_executions" not in self._metrics:
|
|
102
130
|
return
|
|
103
131
|
|
|
104
|
-
self._metrics["tool_executions"].labels(
|
|
105
|
-
tool_name=tool_name, status=status
|
|
106
|
-
).inc()
|
|
132
|
+
self._metrics["tool_executions"].labels(tool_name=tool_name, status=status).inc()
|
|
107
133
|
|
|
108
134
|
def record_tool_duration(self, tool_name: str, duration: float) -> None:
|
|
109
135
|
"""Record tool execution duration.
|
|
@@ -128,9 +154,7 @@ class MetricsCollector:
|
|
|
128
154
|
if not self.enabled or "http_requests" not in self._metrics:
|
|
129
155
|
return
|
|
130
156
|
|
|
131
|
-
self._metrics["http_requests"].labels(
|
|
132
|
-
method=method, status_code=str(status_code), path=path
|
|
133
|
-
).inc()
|
|
157
|
+
self._metrics["http_requests"].labels(method=method, status_code=str(status_code), path=path).inc()
|
|
134
158
|
|
|
135
159
|
def record_http_duration(self, method: str, path: str, duration: float) -> None:
|
|
136
160
|
"""Record HTTP request duration.
|
|
@@ -143,9 +167,7 @@ class MetricsCollector:
|
|
|
143
167
|
if not self.enabled or "http_duration" not in self._metrics:
|
|
144
168
|
return
|
|
145
169
|
|
|
146
|
-
self._metrics["http_duration"].labels(method=method, path=path).observe(
|
|
147
|
-
duration
|
|
148
|
-
)
|
|
170
|
+
self._metrics["http_duration"].labels(method=method, path=path).observe(duration)
|
|
149
171
|
|
|
150
172
|
def increment_resource_read(self, resource_uri: str) -> None:
|
|
151
173
|
"""Record a resource read.
|
|
@@ -174,14 +196,13 @@ class MetricsCollector:
|
|
|
174
196
|
|
|
175
197
|
Args:
|
|
176
198
|
component_type: Type of component ('tool', 'resource', 'prompt', 'http')
|
|
177
|
-
error_type: Type of error ('timeout', 'auth_error',
|
|
199
|
+
error_type: Type of error ('timeout', 'auth_error',
|
|
200
|
+
'validation_error', etc.)
|
|
178
201
|
"""
|
|
179
202
|
if not self.enabled or "errors" not in self._metrics:
|
|
180
203
|
return
|
|
181
204
|
|
|
182
|
-
self._metrics["errors"].labels(
|
|
183
|
-
component_type=component_type, error_type=error_type
|
|
184
|
-
).inc()
|
|
205
|
+
self._metrics["errors"].labels(component_type=component_type, error_type=error_type).inc()
|
|
185
206
|
|
|
186
207
|
def increment_session(self) -> None:
|
|
187
208
|
"""Record a new session."""
|
|
@@ -212,6 +233,66 @@ class MetricsCollector:
|
|
|
212
233
|
|
|
213
234
|
self._metrics["uptime"].set(seconds)
|
|
214
235
|
|
|
236
|
+
def increment_sampling(self, sampling_type: str, status: str) -> None:
|
|
237
|
+
"""Record a sampling request.
|
|
238
|
+
|
|
239
|
+
Args:
|
|
240
|
+
sampling_type: Type of sampling ('sample', 'structured', 'context')
|
|
241
|
+
status: Request status ('success' or 'error')
|
|
242
|
+
"""
|
|
243
|
+
if not self.enabled or "sampling_requests" not in self._metrics:
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
self._metrics["sampling_requests"].labels(sampling_type=sampling_type, status=status).inc()
|
|
247
|
+
|
|
248
|
+
def record_sampling_duration(self, sampling_type: str, duration: float) -> None:
|
|
249
|
+
"""Record sampling request duration.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
sampling_type: Type of sampling
|
|
253
|
+
duration: Request duration in seconds
|
|
254
|
+
"""
|
|
255
|
+
if not self.enabled or "sampling_duration" not in self._metrics:
|
|
256
|
+
return
|
|
257
|
+
|
|
258
|
+
self._metrics["sampling_duration"].labels(sampling_type=sampling_type).observe(duration)
|
|
259
|
+
|
|
260
|
+
def record_sampling_tokens(self, sampling_type: str, token_count: int) -> None:
|
|
261
|
+
"""Record sampling token count.
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
sampling_type: Type of sampling
|
|
265
|
+
token_count: Number of tokens in the response
|
|
266
|
+
"""
|
|
267
|
+
if not self.enabled or "sampling_tokens" not in self._metrics:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
self._metrics["sampling_tokens"].labels(sampling_type=sampling_type).observe(token_count)
|
|
271
|
+
|
|
272
|
+
def increment_elicitation(self, elicitation_type: str, status: str) -> None:
|
|
273
|
+
"""Record an elicitation request.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
elicitation_type: Type of elicitation ('elicit', 'confirmation')
|
|
277
|
+
status: Request status ('success' or 'error')
|
|
278
|
+
"""
|
|
279
|
+
if not self.enabled or "elicitation_requests" not in self._metrics:
|
|
280
|
+
return
|
|
281
|
+
|
|
282
|
+
self._metrics["elicitation_requests"].labels(elicitation_type=elicitation_type, status=status).inc()
|
|
283
|
+
|
|
284
|
+
def record_elicitation_duration(self, elicitation_type: str, duration: float) -> None:
|
|
285
|
+
"""Record elicitation request duration.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
elicitation_type: Type of elicitation
|
|
289
|
+
duration: Request duration in seconds
|
|
290
|
+
"""
|
|
291
|
+
if not self.enabled or "elicitation_duration" not in self._metrics:
|
|
292
|
+
return
|
|
293
|
+
|
|
294
|
+
self._metrics["elicitation_duration"].labels(elicitation_type=elicitation_type).observe(duration)
|
|
295
|
+
|
|
215
296
|
|
|
216
297
|
def init_metrics_collector(enabled: bool = False) -> MetricsCollector:
|
|
217
298
|
"""Initialize the global metrics collector.
|
golf/telemetry/__init__.py
CHANGED
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
from golf.telemetry.instrumentation import (
|
|
4
4
|
get_tracer,
|
|
5
5
|
init_telemetry,
|
|
6
|
+
instrument_elicitation,
|
|
6
7
|
instrument_prompt,
|
|
7
8
|
instrument_resource,
|
|
9
|
+
instrument_sampling,
|
|
8
10
|
instrument_tool,
|
|
9
11
|
telemetry_lifespan,
|
|
10
12
|
)
|
|
@@ -13,6 +15,8 @@ __all__ = [
|
|
|
13
15
|
"instrument_tool",
|
|
14
16
|
"instrument_resource",
|
|
15
17
|
"instrument_prompt",
|
|
18
|
+
"instrument_elicitation",
|
|
19
|
+
"instrument_sampling",
|
|
16
20
|
"telemetry_lifespan",
|
|
17
21
|
"init_telemetry",
|
|
18
22
|
"get_tracer",
|