lanternfish 0.3.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.
- lanternfish/__init__.py +61 -0
- lanternfish/configuration.py +81 -0
- lanternfish/context.py +67 -0
- lanternfish/decorators.py +222 -0
- lanternfish/deployments.py +69 -0
- lanternfish/errors.py +9 -0
- lanternfish/exporters.py +34 -0
- lanternfish/instrument.py +181 -0
- lanternfish/integrations/__init__.py +61 -0
- lanternfish/integrations/anthropic.py +20 -0
- lanternfish/integrations/autogen.py +75 -0
- lanternfish/integrations/crewai.py +17 -0
- lanternfish/integrations/gemini.py +24 -0
- lanternfish/integrations/groq.py +21 -0
- lanternfish/integrations/haystack.py +65 -0
- lanternfish/integrations/http.py +129 -0
- lanternfish/integrations/instrumentor.py +24 -0
- lanternfish/integrations/langchain.py +21 -0
- lanternfish/integrations/llamaindex.py +21 -0
- lanternfish/integrations/mcp.py +208 -0
- lanternfish/integrations/ollama.py +73 -0
- lanternfish/integrations/openai.py +17 -0
- lanternfish/integrations/openai_agents.py +78 -0
- lanternfish/integrations/pydantic_ai.py +66 -0
- lanternfish/integrations/redis.py +296 -0
- lanternfish/integrations/sql.py +300 -0
- lanternfish/json_exporter.py +170 -0
- lanternfish/providers.py +14 -0
- lanternfish/py.typed +0 -0
- lanternfish/semantic.py +794 -0
- lanternfish/span_processor.py +69 -0
- lanternfish-0.3.0.dist-info/METADATA +137 -0
- lanternfish-0.3.0.dist-info/RECORD +35 -0
- lanternfish-0.3.0.dist-info/WHEEL +5 -0
- lanternfish-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SDK giriş noktası.
|
|
3
|
+
|
|
4
|
+
Görev: `instrument()` ile provider'ı kurar, global OpenTelemetry provider'ı yapar (elle açılan span'ler de gitsin diye),
|
|
5
|
+
göndericiyi bağlar ve yüklü LLM/framework kütüphanelerini enstrümante eder. HTTP, SQL ve Redis yalnız istenirse
|
|
6
|
+
açılır. `flush()` ve `shutdown()` bekleyen span'leri gönderir.
|
|
7
|
+
Yapmaz: Span'i kendisi çevirmez veya göndermez (json_exporter.py); entegrasyon ayrıntısı içermez (integrations/).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import os
|
|
12
|
+
from collections.abc import Callable, Iterable
|
|
13
|
+
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
16
|
+
|
|
17
|
+
from lanternfish.configuration import resolve_api_key, resolve_endpoint, resolve_environment, resolve_release
|
|
18
|
+
from lanternfish.errors import LanternfishConfigurationError
|
|
19
|
+
from lanternfish.exporters import configure_exporter
|
|
20
|
+
from lanternfish.integrations import (
|
|
21
|
+
instrument_anthropic,
|
|
22
|
+
instrument_autogen,
|
|
23
|
+
instrument_crewai,
|
|
24
|
+
instrument_gemini,
|
|
25
|
+
instrument_groq,
|
|
26
|
+
instrument_haystack,
|
|
27
|
+
instrument_http,
|
|
28
|
+
instrument_langchain,
|
|
29
|
+
instrument_llamaindex,
|
|
30
|
+
instrument_mcp,
|
|
31
|
+
instrument_mcp_server,
|
|
32
|
+
instrument_ollama,
|
|
33
|
+
instrument_openai,
|
|
34
|
+
instrument_openai_agents,
|
|
35
|
+
instrument_pydantic_ai,
|
|
36
|
+
instrument_redis,
|
|
37
|
+
instrument_sql,
|
|
38
|
+
)
|
|
39
|
+
from lanternfish.providers import create_tracer_provider
|
|
40
|
+
from lanternfish.span_processor import AGENT_NAME_KEY, ENVIRONMENT_KEY, RELEASE_KEY
|
|
41
|
+
|
|
42
|
+
logger = logging.getLogger("lanternfish")
|
|
43
|
+
|
|
44
|
+
Integration = Callable[[TracerProvider], None]
|
|
45
|
+
|
|
46
|
+
# Yüklü olan her zaman açılır: agent'ın LLM ve framework çağrıları.
|
|
47
|
+
_INTEGRATIONS: list[tuple[str, Integration]] = [
|
|
48
|
+
("openai", instrument_openai), # Azure OpenAI istemcisi de aynı paketten izlenir.
|
|
49
|
+
("anthropic", instrument_anthropic),
|
|
50
|
+
("gemini", instrument_gemini),
|
|
51
|
+
("groq", instrument_groq),
|
|
52
|
+
("ollama", instrument_ollama),
|
|
53
|
+
("langchain", instrument_langchain),
|
|
54
|
+
("crewai", instrument_crewai),
|
|
55
|
+
("llamaindex", instrument_llamaindex),
|
|
56
|
+
("haystack", instrument_haystack),
|
|
57
|
+
("openai_agents", instrument_openai_agents),
|
|
58
|
+
("pydantic_ai", instrument_pydantic_ai),
|
|
59
|
+
("autogen", instrument_autogen),
|
|
60
|
+
("mcp", instrument_mcp),
|
|
61
|
+
("mcp_server", instrument_mcp_server),
|
|
62
|
+
]
|
|
63
|
+
# Yalnız `include=` ile açılır: agent dışındaki her sorgu ve istek de kayıt olur, kotayı ve gürültüyü artırır.
|
|
64
|
+
OPTIONAL_INTEGRATIONS: dict[str, Integration] = {
|
|
65
|
+
"http": instrument_http,
|
|
66
|
+
"sql": instrument_sql,
|
|
67
|
+
"redis": instrument_redis,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
CONTENT_SWITCH_ENV = "TRACELOOP_TRACE_CONTENT"
|
|
71
|
+
|
|
72
|
+
_provider: TracerProvider | None = None
|
|
73
|
+
_owns_provider = False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def instrument(
|
|
77
|
+
api_key: str | None = None,
|
|
78
|
+
endpoint: str | None = None,
|
|
79
|
+
*,
|
|
80
|
+
agent: str | None = None,
|
|
81
|
+
release: str | None = None,
|
|
82
|
+
environment: str | None = None,
|
|
83
|
+
include: Iterable[str] = (),
|
|
84
|
+
capture_content: bool = True,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Lanternfish izlemesini başlatır. Uygulama açılışında bir kez çağrılır; ikinci çağrı etkisizdir.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
api_key: Proje API anahtarı; verilmezse LANTERNFISH_API_KEY.
|
|
90
|
+
endpoint: Backend adresi; verilmezse LANTERNFISH_ENDPOINT. Varsayılan adres yoktur.
|
|
91
|
+
agent: Her run'a yazılacak agent adı (ör. "travel-agent").
|
|
92
|
+
release: Çalışan sürüm; verilmezse LANTERNFISH_RELEASE ya da CI'ın commit değişkeni (GITHUB_SHA vb.).
|
|
93
|
+
environment: Ortam adı (ör. "production"); verilmezse LANTERNFISH_ENVIRONMENT.
|
|
94
|
+
include: Ek olarak açılacak entegrasyonlar: "http", "sql", "redis".
|
|
95
|
+
capture_content: LLM entegrasyonları prompt ve yanıt metnini kaydetsin mi. Kişisel veri işleyen agent'larda
|
|
96
|
+
False verin; model, token ve süre yine kaydedilir.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
LanternfishConfigurationError: Anahtar veya adres eksik/geçersizse ya da `include` tanınmayan ad içeriyorsa.
|
|
100
|
+
"""
|
|
101
|
+
global _provider, _owns_provider
|
|
102
|
+
if _provider is not None:
|
|
103
|
+
logger.warning("Lanternfish: instrument() was already called; the second call is ignored.")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
resolved_endpoint = resolve_endpoint(endpoint)
|
|
107
|
+
resolved_key = resolve_api_key(api_key)
|
|
108
|
+
optional = _optional_integrations(include)
|
|
109
|
+
|
|
110
|
+
# Uygulama kendi OpenTelemetry provider'ını kurduysa (Logfire, kendi OTel kurulumu) ona eklenir; ikinci bir global
|
|
111
|
+
# provider kurulamaz ve elle açılan span'ler onunkine giderdi.
|
|
112
|
+
existing = trace.get_tracer_provider()
|
|
113
|
+
owns_provider = not isinstance(existing, TracerProvider)
|
|
114
|
+
provider = create_tracer_provider() if owns_provider else existing
|
|
115
|
+
configure_exporter(
|
|
116
|
+
provider,
|
|
117
|
+
resolved_key,
|
|
118
|
+
resolved_endpoint,
|
|
119
|
+
{
|
|
120
|
+
AGENT_NAME_KEY: agent or "",
|
|
121
|
+
RELEASE_KEY: resolve_release(release) or "",
|
|
122
|
+
ENVIRONMENT_KEY: resolve_environment(environment) or "",
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
if owns_provider:
|
|
126
|
+
trace.set_tracer_provider(provider)
|
|
127
|
+
_provider, _owns_provider = provider, owns_provider
|
|
128
|
+
|
|
129
|
+
# Hazır LLM instrumentor'ları (OpenLLMetry) metni bu değişkene göre kaydeder; her çağrıda okurlar.
|
|
130
|
+
os.environ[CONTENT_SWITCH_ENV] = "true" if capture_content else "false"
|
|
131
|
+
enabled = [name for name, integration in [*_INTEGRATIONS, *optional] if _enable(name, integration, provider)]
|
|
132
|
+
logger.info("Lanternfish: tracing to %s (%s)", resolved_endpoint, ", ".join(enabled) or "manual spans only")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def flush(timeout_seconds: float = 10.0) -> bool:
|
|
136
|
+
"""Bekleyen span'leri hemen gönderir. Serverless işlevlerin ve kısa betiklerin sonunda çağırın.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Süre dolmadan gönderildiyse True; instrument() çağrılmadıysa da True.
|
|
140
|
+
"""
|
|
141
|
+
if _provider is None:
|
|
142
|
+
return True
|
|
143
|
+
return _provider.force_flush(int(timeout_seconds * 1000))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def shutdown() -> None:
|
|
147
|
+
"""Bekleyen span'leri gönderir ve izlemeyi kapatır. Süreç kapanırken kendiliğinden de çalışır.
|
|
148
|
+
|
|
149
|
+
Provider uygulamanınsa yalnız bekleyenler gönderilir; uygulamanın provider'ı kapatılmaz.
|
|
150
|
+
"""
|
|
151
|
+
global _provider
|
|
152
|
+
if _provider is None:
|
|
153
|
+
return
|
|
154
|
+
if _owns_provider:
|
|
155
|
+
_provider.shutdown()
|
|
156
|
+
else:
|
|
157
|
+
_provider.force_flush()
|
|
158
|
+
_provider = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _optional_integrations(include: Iterable[str]) -> list[tuple[str, Integration]]:
|
|
162
|
+
"""`include` adlarını entegrasyonlara çevirir; tanınmayan adı hemen bildirir."""
|
|
163
|
+
names = list(dict.fromkeys(name.strip().lower() for name in include))
|
|
164
|
+
unknown = [name for name in names if name not in OPTIONAL_INTEGRATIONS]
|
|
165
|
+
if unknown:
|
|
166
|
+
allowed = ", ".join(sorted(OPTIONAL_INTEGRATIONS))
|
|
167
|
+
raise LanternfishConfigurationError(f"Unknown include= value(s): {', '.join(unknown)}. Use: {allowed}.")
|
|
168
|
+
return [(name, OPTIONAL_INTEGRATIONS[name]) for name in names]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _enable(name: str, integration: Integration, provider: TracerProvider) -> bool:
|
|
172
|
+
"""Bir entegrasyonu açar; kütüphane yüklü değilse veya uyumsuzsa sessizce atlar."""
|
|
173
|
+
try:
|
|
174
|
+
integration(provider)
|
|
175
|
+
except ModuleNotFoundError:
|
|
176
|
+
logger.debug("Lanternfish: %s skipped (package not installed)", name)
|
|
177
|
+
return False
|
|
178
|
+
except Exception as error: # noqa: BLE001 Bir entegrasyonun hatası agent'ı durdurmamalı.
|
|
179
|
+
logger.warning("Lanternfish: %s instrumentation was skipped (%s: %s)", name, type(error).__name__, error)
|
|
180
|
+
return False
|
|
181
|
+
return True
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Entegrasyon kayıt defteri.
|
|
3
|
+
|
|
4
|
+
Görev: Her entegrasyonu ilk çağrıldığında içe aktaran ince sarmalayıcıları dışa verir. İlgili kütüphane yüklü değilse
|
|
5
|
+
ImportError çağırana (instrument()) çıkar; instrument() o entegrasyonu "atlandı" sayar.
|
|
6
|
+
Yapmaz: Enstrümantasyon mantığı içermez (her biri kendi modülünde).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from importlib import import_module
|
|
11
|
+
|
|
12
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _lazy(module: str, function: str) -> Callable[[TracerProvider], None]:
|
|
16
|
+
"""Modülü ancak entegrasyon açılırken içe aktaran sarmalayıcı döndürür."""
|
|
17
|
+
|
|
18
|
+
def enable(tracer_provider: TracerProvider) -> None:
|
|
19
|
+
getattr(import_module(f"lanternfish.integrations.{module}"), function)(tracer_provider)
|
|
20
|
+
|
|
21
|
+
enable.__name__ = function
|
|
22
|
+
return enable
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
instrument_openai = _lazy("openai", "instrument_openai")
|
|
26
|
+
instrument_anthropic = _lazy("anthropic", "instrument_anthropic")
|
|
27
|
+
instrument_gemini = _lazy("gemini", "instrument_gemini")
|
|
28
|
+
instrument_groq = _lazy("groq", "instrument_groq")
|
|
29
|
+
instrument_ollama = _lazy("ollama", "instrument_ollama")
|
|
30
|
+
instrument_langchain = _lazy("langchain", "instrument_langchain")
|
|
31
|
+
instrument_crewai = _lazy("crewai", "instrument_crewai")
|
|
32
|
+
instrument_llamaindex = _lazy("llamaindex", "instrument_llamaindex")
|
|
33
|
+
instrument_haystack = _lazy("haystack", "instrument_haystack")
|
|
34
|
+
instrument_openai_agents = _lazy("openai_agents", "instrument_openai_agents")
|
|
35
|
+
instrument_pydantic_ai = _lazy("pydantic_ai", "instrument_pydantic_ai")
|
|
36
|
+
instrument_autogen = _lazy("autogen", "instrument_autogen")
|
|
37
|
+
instrument_mcp = _lazy("mcp", "instrument_mcp")
|
|
38
|
+
instrument_mcp_server = _lazy("mcp", "instrument_mcp_server")
|
|
39
|
+
instrument_http = _lazy("http", "instrument_http")
|
|
40
|
+
instrument_sql = _lazy("sql", "instrument_sql")
|
|
41
|
+
instrument_redis = _lazy("redis", "instrument_redis")
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"instrument_anthropic",
|
|
45
|
+
"instrument_autogen",
|
|
46
|
+
"instrument_crewai",
|
|
47
|
+
"instrument_gemini",
|
|
48
|
+
"instrument_groq",
|
|
49
|
+
"instrument_haystack",
|
|
50
|
+
"instrument_http",
|
|
51
|
+
"instrument_langchain",
|
|
52
|
+
"instrument_llamaindex",
|
|
53
|
+
"instrument_mcp",
|
|
54
|
+
"instrument_mcp_server",
|
|
55
|
+
"instrument_ollama",
|
|
56
|
+
"instrument_openai",
|
|
57
|
+
"instrument_openai_agents",
|
|
58
|
+
"instrument_pydantic_ai",
|
|
59
|
+
"instrument_redis",
|
|
60
|
+
"instrument_sql",
|
|
61
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# anthropic.py
|
|
2
|
+
# Görev: Anthropic Claude çağrılarını OpenTelemetry ile otomatik enstrümante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter oluşturmaz
|
|
4
|
+
|
|
5
|
+
from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
|
|
8
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def instrument_anthropic(tracer_provider: TracerProvider) -> None:
|
|
12
|
+
"""Anthropic SDK çağrılarını verilen tracer provider ile izler.
|
|
13
|
+
|
|
14
|
+
messages.create() çağrılarını yakalar; model adı, input/output token
|
|
15
|
+
sayıları, latency ve hata durumlarını gen_ai.* attribute'ları olarak kaydeder.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tracer_provider: Span'lerin gönderileceği TracerProvider.
|
|
19
|
+
"""
|
|
20
|
+
enable_instrumentor(AnthropicInstrumentor(), tracer_provider)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# autogen.py
|
|
2
|
+
# Gorev: AutoGen 0.7+ (autogen-agentchat) agent/team run() cagrilarini OpenTelemetry ile enstrumante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz
|
|
4
|
+
#
|
|
5
|
+
# Gercek API (autogen-agentchat >= 0.6):
|
|
6
|
+
# AssistantAgent.run(task, cancellation_token=None, ...) <- async
|
|
7
|
+
# RoundRobinGroupChat.run(task, ...) <- async (ayni imza)
|
|
8
|
+
# TaskResult.messages -> list[BaseAgentEvent | BaseChatMessage]
|
|
9
|
+
# TaskResult.stop_reason -> str | None
|
|
10
|
+
|
|
11
|
+
import functools
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def instrument_autogen(tracer_provider: TracerProvider) -> None:
|
|
19
|
+
"""AutoGen AssistantAgent ve RoundRobinGroupChat run() cagrilarini izler.
|
|
20
|
+
|
|
21
|
+
Agent adi, model adi, mesaj sayisi ve stop_reason gen_ai.* + agent.*
|
|
22
|
+
attribute'lari olarak kaydedilir.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
tracer_provider: Span'lerin gonderilecegi TracerProvider.
|
|
26
|
+
"""
|
|
27
|
+
# Kütüphane yüklü değilse ImportError instrument()'a çıkar; o entegrasyon atlanır.
|
|
28
|
+
from autogen_agentchat.agents import AssistantAgent
|
|
29
|
+
from autogen_agentchat.teams import RoundRobinGroupChat
|
|
30
|
+
|
|
31
|
+
tracer = trace.get_tracer("lanternfish.autogen", tracer_provider=tracer_provider)
|
|
32
|
+
|
|
33
|
+
def _make_traced_run(original_run: Any, span_name: str) -> Any:
|
|
34
|
+
@functools.wraps(original_run)
|
|
35
|
+
async def traced_run(self: Any, *, task: Any = None, **kwargs: Any) -> Any:
|
|
36
|
+
agent_name = getattr(self, "name", type(self).__name__)
|
|
37
|
+
|
|
38
|
+
# Model bilgisini cek (AssistantAgent'ta _model_client var)
|
|
39
|
+
model_client = getattr(self, "_model_client", None)
|
|
40
|
+
model_name: str = "unknown"
|
|
41
|
+
if model_client is not None:
|
|
42
|
+
# OpenAIChatCompletionClient: _resolved_model (str) veya model / _model_name
|
|
43
|
+
model_name = (
|
|
44
|
+
getattr(model_client, "_resolved_model", None)
|
|
45
|
+
or getattr(model_client, "model", None)
|
|
46
|
+
or getattr(model_client, "_model_name", None)
|
|
47
|
+
or getattr(model_client, "_model", None)
|
|
48
|
+
or type(model_client).__name__
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
52
|
+
span.set_attribute("gen_ai.system", "autogen")
|
|
53
|
+
span.set_attribute("agent.name", str(agent_name))
|
|
54
|
+
span.set_attribute("gen_ai.request.model", str(model_name))
|
|
55
|
+
|
|
56
|
+
if task is not None:
|
|
57
|
+
span.set_attribute("agent.task_preview", str(task)[:120])
|
|
58
|
+
|
|
59
|
+
# task keyword-only: positional olarak gecirilemez
|
|
60
|
+
result = await original_run(self, task=task, **kwargs)
|
|
61
|
+
|
|
62
|
+
messages = getattr(result, "messages", None)
|
|
63
|
+
if messages is not None:
|
|
64
|
+
span.set_attribute("agent.message_count", len(messages))
|
|
65
|
+
|
|
66
|
+
stop_reason = getattr(result, "stop_reason", None)
|
|
67
|
+
if stop_reason is not None:
|
|
68
|
+
span.set_attribute("agent.stop_reason", str(stop_reason))
|
|
69
|
+
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
return traced_run
|
|
73
|
+
|
|
74
|
+
AssistantAgent.run = _make_traced_run(AssistantAgent.run, "autogen.agent.run")
|
|
75
|
+
RoundRobinGroupChat.run = _make_traced_run(RoundRobinGroupChat.run, "autogen.team.run")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# crewai.py
|
|
2
|
+
# Görev: CrewAI çağrılarını OpenTelemetry ile otomatik enstrümante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter oluşturmaz
|
|
4
|
+
|
|
5
|
+
from opentelemetry.instrumentation.crewai import CrewAIInstrumentor
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
|
|
8
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def instrument_crewai(tracer_provider: TracerProvider) -> None:
|
|
12
|
+
"""CrewAI agent/task/crew çağrılarını verilen tracer provider ile izler.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
tracer_provider: Span'lerin gönderileceği TracerProvider.
|
|
16
|
+
"""
|
|
17
|
+
enable_instrumentor(CrewAIInstrumentor(), tracer_provider)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# gemini.py
|
|
2
|
+
# Gorev: Google Gemini (google-genai SDK) cagrilarini OpenTelemetry ile otomatik enstrumante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz
|
|
4
|
+
#
|
|
5
|
+
# Not: Bu dosya google-genai SDK'yi izler (google.genai), eski google-generativeai
|
|
6
|
+
# paketini degil. Dogru SDK: pip install google-genai
|
|
7
|
+
|
|
8
|
+
from opentelemetry.instrumentation.google_generativeai import GoogleGenerativeAiInstrumentor
|
|
9
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
10
|
+
|
|
11
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def instrument_gemini(tracer_provider: TracerProvider) -> None:
|
|
15
|
+
"""Google Gemini SDK cagrilarini verilen tracer provider ile izler.
|
|
16
|
+
|
|
17
|
+
google.genai.Client uzerindeki generate_content() cagrilarini yakalar;
|
|
18
|
+
model adi, token sayilari, latency ve hata durumlarini gen_ai.* attribute'lari
|
|
19
|
+
olarak kaydeder.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
tracer_provider: Span'lerin gonderilecegi TracerProvider.
|
|
23
|
+
"""
|
|
24
|
+
enable_instrumentor(GoogleGenerativeAiInstrumentor(), tracer_provider)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# groq.py
|
|
2
|
+
# Gorev: Groq hizli inference cagrilarini OpenTelemetry ile otomatik enstrumante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz
|
|
4
|
+
|
|
5
|
+
from opentelemetry.instrumentation.groq import GroqInstrumentor
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
|
|
8
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def instrument_groq(tracer_provider: TracerProvider) -> None:
|
|
12
|
+
"""Groq SDK cagrilarini verilen tracer provider ile izler.
|
|
13
|
+
|
|
14
|
+
groq.Groq().chat.completions.create() cagrilarini yakalar; model adi,
|
|
15
|
+
token sayilari ve latency gen_ai.* attribute'lari olarak kaydedilir.
|
|
16
|
+
Groq'un cok dusuk latency'si (<100ms) span'lerde gorulur.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
tracer_provider: Span'lerin gonderilecegi TracerProvider.
|
|
20
|
+
"""
|
|
21
|
+
enable_instrumentor(GroqInstrumentor(), tracer_provider)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# haystack.py
|
|
2
|
+
# Gorev: Haystack 2.x (haystack-ai) pipeline cagrilarini OpenTelemetry ile enstrumante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz
|
|
4
|
+
#
|
|
5
|
+
# Haystack 2.x gercek API:
|
|
6
|
+
# Pipeline.run(data: dict, include_outputs_from=None, ...) <- sync
|
|
7
|
+
# Pipeline.graph <- networkx.MultiDiGraph, nodes listesi buradan alinir
|
|
8
|
+
# Sonuc: dict { component_name: { output_key: value } }
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from opentelemetry import trace
|
|
15
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def instrument_haystack(tracer_provider: TracerProvider) -> None:
|
|
19
|
+
"""Haystack 2.x Pipeline.run() cagrilarini verilen tracer provider ile izler.
|
|
20
|
+
|
|
21
|
+
Pipeline.run() sararak; component sayisi, input verisi (ozet) ve
|
|
22
|
+
cikti anahtarlari haystack.* attribute'lari olarak kaydedilir.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
tracer_provider: Span'lerin gonderilecegi TracerProvider.
|
|
26
|
+
"""
|
|
27
|
+
# Haystack, global provider görünce kendi izlemesini açar ve açılışta deneme span'i gönderir; pipeline'ı
|
|
28
|
+
# burada izlediğimiz için onunki kapatılır (aksi hâlde her açılışta anlamsız bir run oluşur).
|
|
29
|
+
os.environ.setdefault("HAYSTACK_AUTO_TRACE_ENABLED", "false")
|
|
30
|
+
# Kütüphane yüklü değilse ImportError instrument()'a çıkar; o entegrasyon atlanır.
|
|
31
|
+
from haystack import Pipeline
|
|
32
|
+
|
|
33
|
+
tracer = trace.get_tracer("lanternfish.haystack", tracer_provider=tracer_provider)
|
|
34
|
+
original_run = Pipeline.run
|
|
35
|
+
|
|
36
|
+
@functools.wraps(original_run)
|
|
37
|
+
def traced_run(self: Any, data: Any = None, **kwargs: Any) -> Any:
|
|
38
|
+
with tracer.start_as_current_span("haystack.pipeline.run") as span:
|
|
39
|
+
span.set_attribute("gen_ai.system", "haystack")
|
|
40
|
+
|
|
41
|
+
# Haystack 2.x: graph is a networkx.MultiDiGraph
|
|
42
|
+
graph = getattr(self, "graph", None)
|
|
43
|
+
if graph is not None:
|
|
44
|
+
nodes = list(graph.nodes)
|
|
45
|
+
span.set_attribute("haystack.component_count", len(nodes))
|
|
46
|
+
span.set_attribute("haystack.components", str(nodes[:10]))
|
|
47
|
+
|
|
48
|
+
# data dict icinden query bul (orn: {"query": {"text": "..."}} veya {"text_file_converter": {...}})
|
|
49
|
+
if isinstance(data, dict):
|
|
50
|
+
span.set_attribute("haystack.input_components", str(list(data.keys())[:5]))
|
|
51
|
+
for val in data.values():
|
|
52
|
+
if isinstance(val, dict):
|
|
53
|
+
for k, v in val.items():
|
|
54
|
+
if "query" in k.lower() or k == "query":
|
|
55
|
+
span.set_attribute("haystack.query_preview", str(v)[:120])
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
result = original_run(self, data, **kwargs)
|
|
59
|
+
|
|
60
|
+
if isinstance(result, dict):
|
|
61
|
+
span.set_attribute("haystack.output_components", str(list(result.keys())[:5]))
|
|
62
|
+
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
Pipeline.run = traced_run
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTTP istemci entegrasyonu (yalnız `include=["http"]` ile açılır).
|
|
3
|
+
|
|
4
|
+
Görev: requests, httpx (sync/async) ve aiohttp isteklerini span olarak kaydeder: yöntem, host, yol, durum kodu, süre
|
|
5
|
+
ve hata türü. Lanternfish'in kendi gönderimlerini kaydetmez.
|
|
6
|
+
Yapmaz: İstek/yanıt gövdesini, başlıkları veya sorgu parametrelerini kaydetmez (parola, token, kişisel veri taşıyabilir);
|
|
7
|
+
yanıtı okumaz (akış hâlindeki yanıtı bozmamak için).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import functools
|
|
11
|
+
from collections.abc import Iterator
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
from typing import Any
|
|
14
|
+
from urllib.parse import urlsplit
|
|
15
|
+
|
|
16
|
+
from opentelemetry import trace as otel_trace
|
|
17
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
18
|
+
from opentelemetry.trace import Span, StatusCode, Tracer
|
|
19
|
+
|
|
20
|
+
# Lanternfish'in kendi API yolları: bunları izlemek sonsuz döngü ve gürültü olurdu.
|
|
21
|
+
_OWN_PATHS = ("/v1/traces", "/v1/deployments")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def instrument_http(tracer_provider: TracerProvider) -> None:
|
|
25
|
+
"""Yüklü HTTP kütüphanelerinin merkezi istek yöntemini sarar.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
tracer_provider: Span'lerin gönderileceği provider.
|
|
29
|
+
"""
|
|
30
|
+
tracer = otel_trace.get_tracer("lanternfish.http", tracer_provider=tracer_provider)
|
|
31
|
+
_instrument_requests(tracer)
|
|
32
|
+
_instrument_httpx(tracer)
|
|
33
|
+
_instrument_aiohttp(tracer)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@contextmanager
|
|
37
|
+
def _request_span(tracer: Tracer, system: str, method: str, url: str) -> Iterator[Span | None]:
|
|
38
|
+
"""İstek için span açar; Lanternfish'in kendi isteğiyse span açmadan geçer."""
|
|
39
|
+
parts = urlsplit(url)
|
|
40
|
+
if parts.path.startswith(_OWN_PATHS):
|
|
41
|
+
yield None
|
|
42
|
+
return
|
|
43
|
+
host = parts.netloc or "unknown"
|
|
44
|
+
with tracer.start_as_current_span(f"http {method.upper()} {host}", record_exception=False,
|
|
45
|
+
set_status_on_exception=False) as span:
|
|
46
|
+
span.set_attribute("http.system", system)
|
|
47
|
+
span.set_attribute("http.method", method.upper())
|
|
48
|
+
span.set_attribute("http.host", host)
|
|
49
|
+
span.set_attribute("http.path", parts.path or "/")
|
|
50
|
+
try:
|
|
51
|
+
yield span
|
|
52
|
+
except Exception as error:
|
|
53
|
+
span.set_status(StatusCode.ERROR, type(error).__name__)
|
|
54
|
+
span.set_attribute("http.error.type", type(error).__name__)
|
|
55
|
+
raise
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _record_status(span: Span | None, status_code: int) -> None:
|
|
59
|
+
"""Durum kodunu yazar; 4xx/5xx yanıtı hata sayar."""
|
|
60
|
+
if span is None:
|
|
61
|
+
return
|
|
62
|
+
span.set_attribute("http.status_code", status_code)
|
|
63
|
+
if status_code >= 400:
|
|
64
|
+
span.set_status(StatusCode.ERROR, f"HTTP {status_code}")
|
|
65
|
+
else:
|
|
66
|
+
span.set_status(StatusCode.OK)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _instrument_requests(tracer: Tracer) -> None:
|
|
70
|
+
"""requests.Session.request() sarmalayıcısı."""
|
|
71
|
+
try:
|
|
72
|
+
import requests
|
|
73
|
+
except ImportError:
|
|
74
|
+
return
|
|
75
|
+
original = requests.Session.request
|
|
76
|
+
|
|
77
|
+
@functools.wraps(original)
|
|
78
|
+
def traced(self: Any, method: str, url: str, *args: Any, **kwargs: Any) -> Any:
|
|
79
|
+
with _request_span(tracer, "requests", method, str(url)) as span:
|
|
80
|
+
response = original(self, method, url, *args, **kwargs)
|
|
81
|
+
_record_status(span, response.status_code)
|
|
82
|
+
return response
|
|
83
|
+
|
|
84
|
+
requests.Session.request = traced
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _instrument_httpx(tracer: Tracer) -> None:
|
|
88
|
+
"""httpx.Client.send() ve httpx.AsyncClient.send() sarmalayıcıları."""
|
|
89
|
+
try:
|
|
90
|
+
import httpx
|
|
91
|
+
except ImportError:
|
|
92
|
+
return
|
|
93
|
+
original_sync = httpx.Client.send
|
|
94
|
+
original_async = httpx.AsyncClient.send
|
|
95
|
+
|
|
96
|
+
@functools.wraps(original_sync)
|
|
97
|
+
def traced_sync(self: Any, request: Any, **kwargs: Any) -> Any:
|
|
98
|
+
with _request_span(tracer, "httpx", request.method, str(request.url)) as span:
|
|
99
|
+
response = original_sync(self, request, **kwargs)
|
|
100
|
+
_record_status(span, response.status_code)
|
|
101
|
+
return response
|
|
102
|
+
|
|
103
|
+
@functools.wraps(original_async)
|
|
104
|
+
async def traced_async(self: Any, request: Any, **kwargs: Any) -> Any:
|
|
105
|
+
with _request_span(tracer, "httpx", request.method, str(request.url)) as span:
|
|
106
|
+
response = await original_async(self, request, **kwargs)
|
|
107
|
+
_record_status(span, response.status_code)
|
|
108
|
+
return response
|
|
109
|
+
|
|
110
|
+
httpx.Client.send = traced_sync
|
|
111
|
+
httpx.AsyncClient.send = traced_async
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _instrument_aiohttp(tracer: Tracer) -> None:
|
|
115
|
+
"""aiohttp.ClientSession._request() sarmalayıcısı."""
|
|
116
|
+
try:
|
|
117
|
+
import aiohttp
|
|
118
|
+
except ImportError:
|
|
119
|
+
return
|
|
120
|
+
original = aiohttp.ClientSession._request
|
|
121
|
+
|
|
122
|
+
@functools.wraps(original)
|
|
123
|
+
async def traced(self: Any, method: str, str_or_url: Any, **kwargs: Any) -> Any:
|
|
124
|
+
with _request_span(tracer, "aiohttp", method, str(str_or_url)) as span:
|
|
125
|
+
response = await original(self, method, str_or_url, **kwargs)
|
|
126
|
+
_record_status(span, response.status)
|
|
127
|
+
return response
|
|
128
|
+
|
|
129
|
+
aiohttp.ClientSession._request = traced
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hazır OpenTelemetry instrumentor'larını açma yardımcısı.
|
|
3
|
+
|
|
4
|
+
Görev: Bir instrumentor'ı yalnız hedef kütüphane yüklü ve sürümü uyumluyken açar; değilse ModuleNotFoundError
|
|
5
|
+
fırlatır, böylece instrument() onu "atlandı" sayar ve kullanıcının ekranına uyumsuzluk satırı düşmez.
|
|
6
|
+
Yapmaz: Hangi instrumentor'ın açılacağına karar vermez (instrument.py).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from opentelemetry.instrumentation.dependencies import get_dependency_conflicts
|
|
10
|
+
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
|
|
11
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def enable_instrumentor(instrumentor: BaseInstrumentor, tracer_provider: TracerProvider) -> None:
|
|
15
|
+
"""Instrumentor'ı bağımlılığı karşılanıyorsa ve henüz açık değilse verilen provider ile açar.
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
ModuleNotFoundError: Hedef kütüphane yüklü değil veya sürümü desteklenmiyorsa.
|
|
19
|
+
"""
|
|
20
|
+
conflict = get_dependency_conflicts(instrumentor.instrumentation_dependencies())
|
|
21
|
+
if conflict is not None:
|
|
22
|
+
raise ModuleNotFoundError(str(conflict))
|
|
23
|
+
if not instrumentor.is_instrumented_by_opentelemetry:
|
|
24
|
+
instrumentor.instrument(tracer_provider=tracer_provider)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# langchain.py
|
|
2
|
+
# Görev: LangChain çağrılarını OpenTelemetry ile otomatik enstrümante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter oluşturmaz
|
|
4
|
+
|
|
5
|
+
from opentelemetry.instrumentation.langchain import LangchainInstrumentor
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
|
|
8
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def instrument_langchain(tracer_provider: TracerProvider) -> None:
|
|
12
|
+
"""LangChain çağrılarını verilen tracer provider ile izler.
|
|
13
|
+
|
|
14
|
+
Tool call'lar dahil tüm LangChain callback olayları otomatik yakalanır;
|
|
15
|
+
tool span'leri OpenTelemetry GenAI konvansiyonuna göre
|
|
16
|
+
"execute_tool <tool_name>" adıyla oluşturulur (örn. "execute_tool multiply").
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
tracer_provider: Span'lerin gönderileceği TracerProvider.
|
|
20
|
+
"""
|
|
21
|
+
enable_instrumentor(LangchainInstrumentor(), tracer_provider)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# llamaindex.py
|
|
2
|
+
# Gorev: LlamaIndex RAG cagrilarini OpenTelemetry ile otomatik enstrumante etmek
|
|
3
|
+
# Yapmaz: TracerProvider veya exporter olusturmaz
|
|
4
|
+
|
|
5
|
+
from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor
|
|
6
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
7
|
+
|
|
8
|
+
from lanternfish.integrations.instrumentor import enable_instrumentor
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def instrument_llamaindex(tracer_provider: TracerProvider) -> None:
|
|
12
|
+
"""LlamaIndex RAG pipeline cagrilarini verilen tracer provider ile izler.
|
|
13
|
+
|
|
14
|
+
QueryEngine.query(), Retriever.retrieve() ve LLM cagrilarini otomatik
|
|
15
|
+
yakalar; node sayisi, token kullanimi ve latency gen_ai.* attribute'lari
|
|
16
|
+
olarak kaydedilir.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
tracer_provider: Span'lerin gonderilecegi TracerProvider.
|
|
20
|
+
"""
|
|
21
|
+
enable_instrumentor(LlamaIndexInstrumentor(), tracer_provider)
|