import ipaddress import json import os import re import socket import time from copy import deepcopy from typing import Any from urllib.parse import urljoin, urlparse import gradio as gr import httpx import yaml from openai import OpenAI from llm_providers import ( OPENAI_DEFAULT, OPENROUTER_DEFAULT, provider_models, run_openrouter_tool_loop, ) MODEL = os.getenv("OPENAI_MODEL", OPENAI_DEFAULT) AGENTIC_SCRAPER_BASE_URL = "https://api.agenticscraper.com/api/v1" MAX_AGENT_TURNS = 8 MAX_RESPONSE_CHARS = 30_000 HTTP_TIMEOUT = 20.0 ALLOWED_METHODS = {"get"} OPERATIONS = {"get", "post", "put", "patch", "delete", "options", "head", "trace"} TYPE_MAP = { "Metin": "string", "Sayı": "number", "Tam sayı": "integer", "Doğru/Yanlış": "boolean", "Metin listesi": "array", } def _empty_config() -> dict[str, Any]: return {"sources": [], "output_fields": []} def _agentic_headers(api_key: str) -> dict[str, str]: key = (api_key or "").strip() if not key: raise ValueError("Agentic Scraper API key zorunludur.") return {"X-API-Key": key, "Content-Type": "application/json"} def _safe_name(value: str, fallback: str = "tool") -> str: value = re.sub(r"[^a-zA-Z0-9_-]+", "_", value or "").strip("_") if not value or not value[0].isalpha(): value = f"{fallback}_{value}" return value[:64] def _check_public_https(url: str) -> None: parsed = urlparse(url) if parsed.scheme != "https" or not parsed.hostname: raise ValueError("Yalnızca herkese açık HTTPS adresleri kullanılabilir.") if parsed.username or parsed.password: raise ValueError("URL içinde kullanıcı adı/şifre kullanılamaz.") addresses = {item[4][0] for item in socket.getaddrinfo(parsed.hostname, parsed.port or 443)} for address in addresses: ip = ipaddress.ip_address(address) if not ip.is_global: raise ValueError("Yerel, özel veya ayrılmış ağ adreslerine erişim engellendi.") def _load_document(text_or_url: str) -> tuple[dict[str, Any], str]: value = (text_or_url or "").strip() if not value: raise ValueError("OpenAPI URL'si veya JSON/YAML belgesi gerekli.") if value.startswith("https://"): _check_public_https(value) response = httpx.get(value, timeout=HTTP_TIMEOUT, follow_redirects=False) response.raise_for_status() if len(response.content) > 2_000_000: raise ValueError("OpenAPI belgesi 2 MB sınırını aşıyor.") value = response.text origin = str(response.url) else: origin = "inline" document = yaml.safe_load(value) if not isinstance(document, dict): raise TypeError("Belge bir JSON/YAML nesnesi olmalı.") if not str(document.get("openapi", "")).startswith("3."): raise ValueError("Bu demo OpenAPI 3.x belgesi bekliyor.") return document, origin def _resolve_local_ref(document: dict[str, Any], value: Any) -> Any: if not isinstance(value, dict) or "$ref" not in value: return value ref = value["$ref"] if not ref.startswith("#/"): raise ValueError("Yalnızca belge içi OpenAPI $ref değerleri desteklenir.") current: Any = document for segment in ref[2:].split("/"): current = current[segment.replace("~1", "/").replace("~0", "~")] return deepcopy(current) def _parameter_schema(document: dict[str, Any], parameter: dict[str, Any]) -> dict[str, Any]: schema = _resolve_local_ref(document, parameter.get("schema", {"type": "string"})) if not isinstance(schema, dict): schema = {"type": "string"} allowed = { key: deepcopy(value) for key, value in schema.items() if key in {"type", "description", "enum", "default", "minimum", "maximum", "items"} } allowed.setdefault("type", "string") allowed.setdefault("description", parameter.get("description") or parameter["name"]) return allowed def _openapi_tools(source: dict[str, Any]) -> list[dict[str, Any]]: document = source["document"] tools = [] seen = set() for path, path_item in document.get("paths", {}).items(): path_item = _resolve_local_ref(document, path_item) if not isinstance(path_item, dict): continue shared = path_item.get("parameters", []) for method, operation in path_item.items(): if method.lower() not in ALLOWED_METHODS or not isinstance(operation, dict): continue base_name = operation.get("operationId") or f"{method}_{path}" name = _safe_name(f"{source['id']}_{base_name}") suffix = 2 while name in seen: name = f"{name[:60]}_{suffix}" suffix += 1 seen.add(name) properties: dict[str, Any] = {} required = [] parameter_meta = {} for raw_parameter in [*shared, *operation.get("parameters", [])]: parameter = _resolve_local_ref(document, raw_parameter) if parameter.get("in") not in {"query", "path"}: continue param_name = parameter["name"] properties[param_name] = _parameter_schema(document, parameter) parameter_meta[param_name] = parameter["in"] if parameter.get("required") or parameter["in"] == "path": required.append(param_name) tools.append( { "definition": { "type": "function", "name": name, "description": ( operation.get("summary") or operation.get("description") or f"{method.upper()} {path}" )[:900], "parameters": { "type": "object", "properties": properties, "required": required, "additionalProperties": False, }, "strict": False, }, "runtime": { "source_id": source["id"], "method": method.upper(), "path": path, "parameters": parameter_meta, }, } ) return tools def _server_url(document: dict[str, Any], origin: str) -> str: servers = document.get("servers") or [] if not servers or not servers[0].get("url"): if origin == "inline": raise ValueError("OpenAPI belgesinde `servers[0].url` gerekli.") return urljoin(origin, "/") url = servers[0]["url"] if origin != "inline": url = urljoin(origin, url) return url.rstrip("/") def add_openapi(name: str, spec: str, bearer_token: str, config: dict[str, Any]): config = deepcopy(config or _empty_config()) try: document, origin = _load_document(spec) source_id = _safe_name(name or document.get("info", {}).get("title", "api"), "api") if any(item["id"] == source_id for item in config["sources"]): raise ValueError(f"`{source_id}` adlı kaynak zaten var.") source = { "id": source_id, "type": "openapi", "name": name or document.get("info", {}).get("title", source_id), "base_url": _server_url(document, origin), "document": document, "bearer_token": (bearer_token or "").strip(), } _check_public_https(source["base_url"]) tool_count = len(_openapi_tools(source)) if not tool_count: raise ValueError("Belgede kullanılabilir GET operasyonu bulunamadı.") config["sources"].append(source) return config, _config_summary(config), f"✅ {tool_count} GET aracı eklendi." except Exception as exc: # noqa: BLE001 - validation errors are displayed in the UI return config, _config_summary(config), f"❌ {type(exc).__name__}: {exc}" def add_mcp(label: str, server_url: str, auth_header: str, config: dict[str, Any]): config = deepcopy(config or _empty_config()) try: label = _safe_name(label, "mcp") _check_public_https(server_url) if any(item["id"] == label for item in config["sources"]): raise ValueError(f"`{label}` adlı kaynak zaten var.") config["sources"].append( { "id": label, "type": "mcp", "name": label, "server_url": server_url.strip(), "authorization": (auth_header or "").strip(), } ) return config, _config_summary(config), "✅ Uzak MCP kaynağı eklendi." except Exception as exc: # noqa: BLE001 - validation errors are displayed in the UI return config, _config_summary(config), f"❌ {type(exc).__name__}: {exc}" def load_market(agentic_api_key: str): try: response = httpx.get( f"{AGENTIC_SCRAPER_BASE_URL}/mcp-market/mcps", headers=_agentic_headers(agentic_api_key), timeout=HTTP_TIMEOUT, ) response.raise_for_status() entries = response.json().get("mcps", []) catalog = {entry["id"]: entry for entry in entries} choices = [ ( f"{entry['name']} · {entry['type']} · {entry.get('monetizationStrategy', 'free')}", entry["id"], ) for entry in entries ] if not choices: raise ValueError("Onaylı MCP Market kaydı bulunamadı.") return ( catalog, gr.update(choices=choices, value=choices[0][1]), entries[0], f"✅ {len(choices)} onaylı entegrasyon yüklendi.", ) except Exception as exc: # noqa: BLE001 - API errors are displayed in the UI return {}, gr.update(choices=[], value=None), {}, f"❌ {type(exc).__name__}: {exc}" def preview_market(mcp_id: str, catalog: dict[str, Any]): return (catalog or {}).get(mcp_id, {}) def add_market_mcp( mcp_id: str, agentic_api_key: str, catalog: dict[str, Any], config: dict[str, Any], ): config = deepcopy(config or _empty_config()) try: _agentic_headers(agentic_api_key) listing = (catalog or {}).get(mcp_id) if not listing: raise ValueError("Önce kataloğu yükleyip bir MCP seçin.") source_id = _safe_name(f"market_{listing['slug']}", "market") if any(item["id"] == source_id for item in config["sources"]): raise ValueError(f"`{listing['name']}` zaten eklendi.") config["sources"].append( { "id": source_id, "type": "market_mcp", "name": listing["name"], "mcp_id": listing["id"], "params": {}, "tool_names": [ tool["name"] for tool in listing.get("tools", []) if tool.get("isEnabled", True) ], "monetization": listing.get("monetizationStrategy", "free"), } ) return config, _config_summary(config), f"✅ {listing['name']} MCP Market'ten eklendi." except Exception as exc: # noqa: BLE001 - validation errors are displayed in the UI return config, _config_summary(config), f"❌ {type(exc).__name__}: {exc}" def clear_sources(config: dict[str, Any]): config = deepcopy(config or _empty_config()) config["sources"] = [] return config, _config_summary(config), "Kaynaklar temizlendi." def _rows_to_schema(rows: list[list[Any]] | None) -> tuple[dict[str, Any], list[dict[str, Any]]]: properties = {} required = [] clean_rows = [] for row in rows or []: if not row or not str(row[0] or "").strip(): continue name = _safe_name(str(row[0]), "field") friendly_type = str(row[1] or "Metin") json_type = TYPE_MAP.get(friendly_type, "string") field_schema: dict[str, Any] = { "type": json_type, "description": str(row[2] or name), } if json_type == "array": field_schema["items"] = {"type": "string"} is_required = str(row[3]).lower() in {"true", "1", "evet", "yes"} if not is_required: field_schema["type"] = [json_type, "null"] field_schema["description"] += " (İsteğe bağlıdır; yoksa null döndür.)" properties[name] = field_schema # OpenAI strict structured outputs requires every property in `required`. # User-optional fields remain semantically optional by accepting null. required.append(name) clean_rows.append( {"name": name, "type": friendly_type, "description": field_schema["description"], "required": is_required} ) if not properties: properties = {"answer": {"type": "string", "description": "Kullanıcıya verilecek nihai yanıt"}} required = ["answer"] return ( { "type": "object", "properties": properties, "required": required, "additionalProperties": False, }, clean_rows, ) def save_output_schema(rows: list[list[Any]], config: dict[str, Any]): config = deepcopy(config or _empty_config()) schema, clean_rows = _rows_to_schema(rows) config["output_fields"] = clean_rows return config, schema, _config_summary(config), "✅ Structured output şeması kaydedildi." def _config_summary(config: dict[str, Any]) -> dict[str, Any]: sources = [] for item in (config or {}).get("sources", []): if item["type"] == "openapi": sources.append( { "name": item["name"], "type": "OpenAPI", "base_url": item["base_url"], "tools": len(_openapi_tools(item)), "auth": "var" if item.get("bearer_token") else "yok", } ) elif item["type"] == "mcp": sources.append( { "name": item["name"], "type": "MCP", "server_url": item["server_url"], "auth": "var" if item.get("authorization") else "yok", } ) else: sources.append( { "name": item["name"], "type": "Agentic Scraper MCP Market", "mcp_id": item["mcp_id"], "tools": len(item.get("tool_names", [])), "monetization": item.get("monetization", "free"), "params": "MCP Market tarafından çözülür", } ) return {"sources": sources, "output_fields": (config or {}).get("output_fields", [])} def _execute_openapi(runtime: dict[str, Any], arguments: dict[str, Any], sources: dict[str, Any]) -> dict[str, Any]: source = sources[runtime["source_id"]] path = runtime["path"] query = {} for name, value in arguments.items(): if runtime["parameters"].get(name) == "path": path = path.replace("{" + name + "}", str(value)) else: query[name] = value url = source["base_url"].rstrip("/") + "/" + path.lstrip("/") _check_public_https(url) headers = {"Accept": "application/json"} if source.get("bearer_token"): headers["Authorization"] = f"Bearer {source['bearer_token']}" response = httpx.request( runtime["method"], url, params=query, headers=headers, timeout=HTTP_TIMEOUT, follow_redirects=False, ) content_type = response.headers.get("content-type", "") body: Any if "json" in content_type: body = response.json() else: body = response.text[:MAX_RESPONSE_CHARS] return {"status": response.status_code, "url": str(response.url), "data": body} def _redact(value: Any) -> Any: if isinstance(value, dict): return { key: ( "••••••" if any(marker in key.lower() for marker in {"authorization", "token", "secret", "password", "apikey", "api_key", "headers"}) else _redact(item) ) for key, item in value.items() } if isinstance(value, list): return [_redact(item) for item in value] return value def _trace_markdown(events: list[dict[str, Any]]) -> str: if not events: return "_Bu çalışmada görünür bir araç olayı oluşmadı._" blocks = [] for event in events: blocks.append( f"### Turn {event['turn']} · `{event['type']}` · `{event['name']}`\n" f"```json\n{json.dumps(_redact(event['detail']), ensure_ascii=False, indent=2)[:12000]}\n```" ) return "\n\n".join(blocks) def _provider_controls(provider: str): models = provider_models(provider, require_structured=provider == "OpenRouter") default = OPENROUTER_DEFAULT if provider == "OpenRouter" else MODEL values = {value for _, value in models} if default not in values: default = models[0][1] return ( gr.update(choices=models, value=default), gr.update(visible=provider == "OpenAI"), gr.update(visible=provider == "OpenRouter"), ) def _run_market_agent( *, task: str, instructions: str, model: str, output_schema: dict[str, Any], config: dict[str, Any], agentic_api_key: str, ) -> tuple[str, list[dict[str, Any]], dict[str, Any]]: market_sources = [source for source in config["sources"] if source["type"] == "market_mcp"] custom_sources = [source for source in config["sources"] if source["type"] != "market_mcp"] custom_mcps = [] for source in custom_sources: if source["type"] == "openapi": custom_mcps.append( { "type": "openapi", "name": source["name"], "openapi_spec": source["document"], "params": ( {"apiKey": source["bearer_token"]} if source.get("bearer_token") else {} ), } ) else: custom_mcps.append( { "type": "remote_mcp", "name": source["name"], "source_url": source["server_url"], "params": ( {"authorization": source["authorization"]} if source.get("authorization") else {} ), } ) payload = { "name": "Dynamic Agent Studio run", "task": task, "system_prompt": instructions, "expected_output": json.dumps(output_schema, ensure_ascii=False), "llm_provider": "openrouter", "llm_model": model, "max_tool_calls": MAX_AGENT_TURNS, "market_mcps": [ { "mcp_id": source["mcp_id"], "tool_names": source.get("tool_names", []), "params": source.get("params", {}), } for source in market_sources ], "custom_mcps": custom_mcps, } headers = _agentic_headers(agentic_api_key) response = httpx.post( f"{AGENTIC_SCRAPER_BASE_URL}/agents/run", headers=headers, json=payload, timeout=HTTP_TIMEOUT, ) response.raise_for_status() queued = response.json() job_id = queued["jobId"] trace = [ { "turn": 1, "type": "agentic_scraper_queue", "name": "MCP Market orchestration", "detail": { "jobId": job_id, "market_mcps": [source["name"] for source in market_sources], "model": model, }, } ] deadline = time.monotonic() + 150 job = {} while time.monotonic() < deadline: poll = httpx.get( f"{AGENTIC_SCRAPER_BASE_URL}/agents/runs/{job_id}", headers=headers, timeout=HTTP_TIMEOUT, ) poll.raise_for_status() job = poll.json() if job.get("status") in {"completed", "failed"}: break time.sleep(2) if job.get("status") == "failed": raise RuntimeError(job.get("error") or "Agentic Scraper çalışması başarısız.") if job.get("status") != "completed": raise TimeoutError(f"Agentic Scraper çalışması zaman aşımına uğradı: {job_id}") for index, log in enumerate(job.get("logs", []), start=2): trace.append( { "turn": index, "type": "agentic_scraper_log", "name": "MCP tool trace", "detail": {"message": log}, } ) result = job.get("result") or {} if not isinstance(result, dict): result = {"answer": result} return json.dumps(result, ensure_ascii=False), trace, result def run_agent( task: str, instructions: str, provider: str, model: str, openai_key: str, openrouter_key: str, agentic_api_key: str, rows: list[list[Any]], config: dict[str, Any], ): task = (task or "").strip() config = deepcopy(config or _empty_config()) if not task: return "Lütfen agente bir görev verin.", "_Çalıştırılmadı._", {} output_schema, _ = _rows_to_schema(rows) market_sources = [source for source in config["sources"] if source["type"] == "market_mcp"] if market_sources: if provider != "OpenRouter": return ( "MCP Market çalıştırmaları için LLM sağlayıcısını **OpenRouter** seçin.", "_Çalıştırılmadı._", {}, ) if not (agentic_api_key or "").strip(): return "Agentic Scraper API key zorunludur.", "_Çalıştırılmadı._", {} try: text, trace, parsed = _run_market_agent( task=task, instructions=instructions, model=model, output_schema=output_schema, config=config, agentic_api_key=agentic_api_key, ) pretty = f"```json\n{json.dumps(parsed, ensure_ascii=False, indent=2)}\n```" return pretty, _trace_markdown(trace), parsed except Exception as exc: # noqa: BLE001 - UI boundary must return a readable error return f"Agent hatası: `{type(exc).__name__}: {exc}`", "_MCP Market çalışması tamamlanamadı._", {} if provider == "OpenRouter": key = (openrouter_key or "").strip() or os.getenv("OPENROUTER_API_KEY", "") else: key = (openai_key or "").strip() or os.getenv("OPENAI_API_KEY", "") if not key: return f"{provider} API anahtarı gerekli.", "_Çalıştırılmadı._", {} local_tools = [] runtimes = {} sources = {source["id"]: source for source in config["sources"]} api_tools = [] for source in config["sources"]: if source["type"] == "openapi": api_tools.extend(_openapi_tools(source)) for item in api_tools: local_tools.append(item["definition"]) runtimes[item["definition"]["name"]] = item["runtime"] hosted_tools = [] for source in config["sources"]: if source["type"] != "mcp": continue tool: dict[str, Any] = { "type": "mcp", "server_label": source["id"], "server_url": source["server_url"], "require_approval": "never", } if source.get("authorization"): tool["headers"] = {"Authorization": source["authorization"]} hosted_tools.append(tool) tools = [*local_tools, *hosted_tools] prompt = (instructions or "").strip() or ( "Kullanıcının görevini yalnızca mevcut araçlar ve araç sonuçlarıyla tamamla. " "Kaynak erişimi başarısızsa veri uydurma. Gerekli araçları kullan ve çıktı şemasına uy." ) if provider == "OpenRouter": if hosted_tools: return ( "Doğrudan uzak MCP kaynakları OpenRouter yolunda desteklenmiyor. " "OpenAI seçin veya MCP Market üzerinden Agentic Scraper orkestrasyonunu kullanın.", "_Çalıştırılmadı._", {}, ) try: text, trace = run_openrouter_tool_loop( api_key=key, model=model, system_prompt=prompt, user_prompt=task, tools=local_tools, execute_tool=lambda name, arguments: _execute_openapi( runtimes[name], arguments, sources ), max_turns=MAX_AGENT_TURNS, response_schema=output_schema, ) parsed = json.loads(text) pretty = f"```json\n{json.dumps(parsed, ensure_ascii=False, indent=2)}\n```" return pretty, _trace_markdown(trace), parsed except Exception as exc: # noqa: BLE001 - UI boundary must return a readable error return f"Agent hatası: `{type(exc).__name__}: {exc}`", "_OpenRouter çalışması tamamlanamadı._", {} client = OpenAI(api_key=key) conversation: list[Any] = [{"role": "user", "content": task}] trace: list[dict[str, Any]] = [] response = None try: for turn in range(1, MAX_AGENT_TURNS + 1): response = client.responses.create( model=(model or MODEL).strip(), instructions=prompt, input=conversation, tools=tools, text={ "format": { "type": "json_schema", "name": "agent_result", "strict": True, "schema": output_schema, } }, reasoning={"effort": "low"}, ) output_items = [ item.model_dump(exclude_none=True) if hasattr(item, "model_dump") else dict(item) for item in response.output ] conversation.extend(output_items) calls = [item for item in output_items if item.get("type") == "function_call"] for item in output_items: item_type = item.get("type", "") if item_type.startswith("mcp_"): trace.append( { "turn": turn, "type": item_type, "name": item.get("name") or item.get("server_label", "mcp"), "detail": item, } ) if not calls: parsed = json.loads(response.output_text) pretty = f"```json\n{json.dumps(parsed, ensure_ascii=False, indent=2)}\n```" return pretty, _trace_markdown(trace), parsed for call in calls: arguments = json.loads(call.get("arguments") or "{}") try: result = _execute_openapi(runtimes[call["name"]], arguments, sources) except Exception as exc: # noqa: BLE001 - tool errors are returned to the model result = {"error": f"{type(exc).__name__}: {exc}"} trace.append( { "turn": turn, "type": "function_call", "name": call["name"], "detail": {"arguments": arguments, "result": result}, } ) conversation.append( { "type": "function_call_output", "call_id": call["call_id"], "output": json.dumps(result, ensure_ascii=False)[:MAX_RESPONSE_CHARS], } ) return "Agent tur sınırına ulaştı.", _trace_markdown(trace), {} except Exception as exc: # noqa: BLE001 - UI boundary must return a readable error return f"Agent hatası: `{type(exc).__name__}: {exc}`", _trace_markdown(trace), {} CSS = """ .gradio-container {max-width:1180px!important;margin:auto!important} .hero {background:linear-gradient(130deg,#14152c,#3b2568,#155e75);padding:30px; border-radius:20px;color:white;margin-bottom:18px} .hero h1{margin:0 0 8px;font-size:2rem}.hero p{margin:0;opacity:.86} .hint {padding:12px 16px;border-radius:12px;background:rgba(99,102,241,.08)} """ with gr.Blocks(css=CSS, title="Dynamic Agent Studio") as demo: config_state = gr.State(_empty_config()) market_catalog_state = gr.State({}) gr.HTML( """

🧩 Dynamic Agent Studio

Structured output + sınırsız OpenAPI/MCP kaynağı + çalışma anında agent

""" ) with gr.Tabs(): with gr.Tab("1 · Kaynakları ekle"): with gr.Row(): with gr.Column(): gr.Markdown("### OpenAPI kaynağı") openapi_name = gr.Textbox(label="Kaynak adı", placeholder="rest_countries") openapi_spec = gr.Textbox( label="OpenAPI 3.x URL veya JSON/YAML", lines=10, placeholder="https://example.com/openapi.json veya belge içeriği", ) openapi_token = gr.Textbox( label="Bearer token (isteğe bağlı, oturumluk)", type="password", ) add_openapi_button = gr.Button("OpenAPI kaynağını ekle", variant="primary") with gr.Column(): gr.Markdown("### Uzak MCP kaynağı") mcp_label = gr.Textbox(label="MCP etiketi", placeholder="docs_mcp") mcp_url = gr.Textbox(label="MCP server URL", placeholder="https://…/mcp") mcp_auth = gr.Textbox( label="Authorization header değeri (isteğe bağlı)", type="password", placeholder="Bearer …", ) add_mcp_button = gr.Button("MCP kaynağını ekle", variant="primary") gr.Markdown( "MCP aracı OpenAI Responses API tarafından uzak sunucuya bağlanır. " "Bu demoda otomatik kullanım onayı açıktır; yalnızca güvendiğiniz sunucuları ekleyin." ) gr.Markdown("### Agentic Scraper MCP Market") gr.Markdown( """ **Agentic Scraper**, scraping işlerini, özel agent çalıştırmalarını ve onaylı MCP/OpenAPI araçlarını tek API üzerinden kullanmayı sağlayan bir agent platformudur. MCP Market seçildiğinde yalnızca Agentic Scraper API key'iniz gerekir; Market entegrasyonunun bağlantı parametreleri platform tarafından çözülür. [Web sitesi](https://agenticscraper.com/) · [Dokümantasyon](https://docs.agenticscraper.com/) · [API referansı](https://docs.agenticscraper.com/api-reference/) · [MCP Market rehberi](https://docs.agenticscraper.com/users/mcp/) """ ) with gr.Row(): with gr.Column(scale=4): agentic_key_sources = gr.Textbox( label="Agentic Scraper API key", type="password", placeholder="MCP Market kataloğu ve kullanımı için zorunlu", ) load_market_button = gr.Button("MCP Market kataloğunu yükle") market_select = gr.Dropdown( label="Onaylı MCP/OpenAPI entegrasyonu", choices=[], ) add_market_button = gr.Button("Seçili Market MCP'sini ekle", variant="primary") with gr.Column(scale=6): market_preview = gr.JSON(label="Market kaydı ve fiyat bilgisi") gr.Markdown( "Market kaydı ücretliyse araç çağrıları Agentic Scraper hesabınızın " "bakiyesinden ücretlendirilebilir." ) source_status = gr.Markdown() source_summary = gr.JSON(label="Aktif yapılandırma", value=_config_summary(_empty_config())) clear_button = gr.Button("Tüm kaynakları temizle", variant="secondary") with gr.Tab("2 · Çıktıyı tasarla"): gr.Markdown( "Her satır, agentın nihai JSON çıktısındaki bir alanı tanımlar. " "Alan adı boş olan satırlar yok sayılır." ) output_rows = gr.Dataframe( headers=["Alan adı", "Tür", "Açıklama", "Zorunlu"], datatype=["str", "str", "str", "bool"], value=[ ["answer", "Metin", "Kullanıcıya verilecek nihai yanıt", True], ["sources", "Metin listesi", "Kullanılan kaynaklar", True], ], row_count=(2, "dynamic"), col_count=(4, "fixed"), label="Structured output alanları", ) gr.Markdown("Tür seçenekleri: **Metin, Sayı, Tam sayı, Doğru/Yanlış, Metin listesi**") save_schema_button = gr.Button("Şemayı kaydet", variant="primary") schema_preview = gr.JSON(label="Üretilen JSON Schema") schema_status = gr.Markdown() with gr.Tab("3 · Agentı çalıştır"): with gr.Row(): with gr.Column(scale=5): provider_input = gr.Dropdown( label="LLM sağlayıcısı", choices=["OpenAI", "OpenRouter"], value="OpenAI", ) model_input = gr.Dropdown( label="Tool + structured output destekli model", choices=provider_models("OpenAI"), value=MODEL, allow_custom_value=False, ) task = gr.Textbox( label="Görev", lines=4, placeholder="Eklediğim kaynakları kullanarak…", ) agent_instructions = gr.Textbox( label="Agent talimatı", lines=5, value=( "Görevi mevcut araçlarla tamamla. Araç sonucu olmadan güncel bilgi " "uydurma. Başarısız kaynakları açıkça belirt ve tanımlı JSON şemasına uy." ), ) openai_key = gr.Textbox( label="OpenAI API anahtarı (Space Secret yoksa)", type="password", placeholder="sk-… — saklanmaz", ) openrouter_key = gr.Textbox( label="OpenRouter API anahtarı (Space Secret yoksa)", type="password", placeholder="sk-or-… — saklanmaz", visible=False, ) agentic_key_run = gr.Textbox( label="Agentic Scraper API key (MCP Market seçildiyse zorunlu)", type="password", ) run_button = gr.Button("Dinamik agentı başlat", variant="primary") with gr.Column(scale=7): final_answer = gr.Markdown("Structured sonuç burada görünecek.") final_json = gr.JSON(label="Makinece okunabilir sonuç") with gr.Accordion("🔎 Agent araç izleri", open=True): trace_view = gr.Markdown("_Henüz çalıştırılmadı._") add_openapi_button.click( add_openapi, [openapi_name, openapi_spec, openapi_token, config_state], [config_state, source_summary, source_status], ) add_mcp_button.click( add_mcp, [mcp_label, mcp_url, mcp_auth, config_state], [config_state, source_summary, source_status], ) load_market_button.click( load_market, [agentic_key_sources], [market_catalog_state, market_select, market_preview, source_status], ) market_select.change( preview_market, [market_select, market_catalog_state], [market_preview], ) add_market_button.click( add_market_mcp, [market_select, agentic_key_sources, market_catalog_state, config_state], [config_state, source_summary, source_status], ) agentic_key_sources.change( lambda value: value, [agentic_key_sources], [agentic_key_run], ) clear_button.click( clear_sources, [config_state], [config_state, source_summary, source_status], ) save_schema_button.click( save_output_schema, [output_rows, config_state], [config_state, schema_preview, source_summary, schema_status], ) provider_input.change( _provider_controls, [provider_input], [model_input, openai_key, openrouter_key], ) run_button.click( run_agent, [ task, agent_instructions, provider_input, model_input, openai_key, openrouter_key, agentic_key_run, output_rows, config_state, ], [final_answer, trace_view, final_json], ) if __name__ == "__main__": demo.queue(default_concurrency_limit=4).launch()