| 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: |
| 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: |
| 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: |
| 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: |
| 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 |
| |
| |
| 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: |
| 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: |
| 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: |
| 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: |
| 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( |
| """<div class="hero"><h1>🧩 Dynamic Agent Studio</h1> |
| <p>Structured output + sınırsız OpenAPI/MCP kaynağı + çalışma anında agent</p></div>""" |
| ) |
|
|
| 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() |
|
|