{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": "# Hafta 10: LLM Tabanlı Sistem Mimarileri (LLMOps & Agents)\n\n**Dersin Hedefleri:**\n1.  Tekil LLM çağrılarının ötesine geçerek, birden çok adımdan oluşan karmaşık zincirler (chains) oluşturmayı öğrenmek.\n2.  **Agent (Ajan)** kavramını ve bir LLM'in harici araçları (tools) otonom olarak nasıl kullanabildiğini anlamak.\n3.  `LangChain` gibi bir framework'ün, LLM tabanlı uygulama geliştirmeyi nasıl basitleştirdiğini kavramak.\n4.  **LLMOps** kavramını tanımak: Prompt yönetimi, LLM çıktılarının değerlendirilmesi ve maliyet takibi gibi LLM'e özgü operasyonel zorluklar.\n5.  Basit bir \"agent\" prototipi oluşturmak.\n\n## 1. Zincirlerden (Chains) Ajanlara (Agents)\n\nÖnceki haftalarda LLM'leri tek bir görev için kullandık (bir soruya cevap ver, metni özetle vb.). Ancak gerçek dünya problemleri genellikle birden çok adım gerektirir.\n\n**Zincir (Chain):** Önceden tanımlanmış bir sırayla birden çok LLM çağrısını veya başka bileşenleri (API çağrıları, veri işleme adımları) birbirine bağlayan bir yapıdır.\n*   **Örnek:** Bir makaleyi al -> İlk LLM çağrısı ile özetle -> İkinci LLM çağrısı ile özeti 3 maddelik bir listeye dönüştür -> Üçüncü LLM çağrısı ile bu listeyi sosyal medya postuna çevir.\n\n**Ajan (Agent):** Bir zincirden daha gelişmiştir. Agent, sabit bir adımlar dizisini takip etmez. Bunun yerine, bir **hedefe** ulaşmak için hangi **araçları (tools)** hangi sırayla kullanacağına kendisi karar veren bir LLM tarafından yönetilen bir döngüdür.\n\n**Agent'ın Düşünme Döngüsü (ReAct Prensibi):**\n1.  **Reason (Düşün):** Verilen görev ve mevcut araçlar listesine bakarak bir sonraki adımın ne olması gerektiğine karar verir.\n2.  **Act (Eyleme Geç):** Karar verdiği aracı, uygun parametrelerle çağırır.\n3.  **Observe (Gözlemle):** Aracın döndürdüğü sonucu gözlemler.\n4.  Bu gözlemi kullanarak tekrar 1. adıma döner ve hedefe ulaşana kadar bu döngüyü tekrarlar.\n\n## 2. `LangChain` ile Uygulama Geliştirme\n\n**LangChain**, LLM tabanlı uygulamalar geliştirmek için tasarlanmış açık kaynaklı bir framework'tür. Zincirler, ajanlar, RAG pipeline'ları ve harici araçlarla entegrasyon gibi karmaşık yapıları oluşturmak için standartlaştırılmış bileşenler sunar.\n\n**Kurulum:**\n```bash\npip install langchain langchain-openai\n```\n\n### Uygulama: Hava Durumu Sorabilen Bir Agent\n\nBu uygulamada, LLM'e yeni bir yetenek kazandıracağız: internetten gerçek zamanlı hava durumu bilgisi alabilme. Bunu, LLM'in kullanabileceği özel bir \"araç\" (tool) tanımlayarak yapacağız."
    },
    {
      "cell_type": "markdown",
      "source": "### 2.4 RAG + Agents: The Power Combination\n\nEn güçlü LLM sistemleri, **RAG** ve **Function Calling**'i birleştirir:\n\n**Örnek Use Case: Akıllı Müşteri Destek**\n```python\ntools = [\n    search_knowledge_base(),    # RAG: KB'den bilgi al\n    get_user_data(),            # Function: CRM'den müşteri bilgisi\n    create_ticket(),            # Function: Destek talebi oluştur\n    check_order_status()        # Function: Sipariş durumu\n]\n```\n\n**Kullanıcı:** \"Siparişim nerede ve iade politikanız nedir?\"\n\n**Agent Akışı:**\n1. `check_order_status()` → \"Kargoda, yarın teslim\"\n2. `search_knowledge_base(\"iade politikası\")` → \"14 gün içinde iade\"\n3. Final yanıt: Her iki soruya da cevap verir\n\n### 2.5 Production Best Practices\n\n**✅ Yapılması Gerekenler:**\n1. **Clear Tool Descriptions**: LLM'in doğru tool'u seçmesi için detaylı açıklamalar\n2. **Error Handling**: Her tool try-except ile korunmalı\n3. **Iteration Limits**: Sonsuz döngüyü önlemek için max_iterations\n4. **Logging**: Her tool call'u logla (debugging için kritik)\n5. **Cost Tracking**: API maliyetlerini izle\n\n**❌ Yapılmaması Gerekenler:**\n1. **Too Many Tools**: 10+ tool agent'ı karıştırır\n2. **Unsafe Eval**: Kullanıcı input'unu direkt çalıştırma\n3. **No Validation**: Tool sonuçlarını validate etmeden kullanma\n4. **Stateless Agents**: Context'i koru (multi-turn conversations)\n\n### 2.6 Agent Decision Patterns\n\n**Pattern 1: Sequential** (A → B → C)\n```\nUser: \"Ankara ve Istanbul'daki hava farkı nedir?\"\nAgent: get_weather(\"Ankara\") → get_weather(\"Istanbul\") → calculate(\"12-18\") → Answer\n```\n\n**Pattern 2: Conditional** (if-then)\n```\nUser: \"Ankara soğuksa İstanbul'u da söyle\"\nAgent: get_weather(\"Ankara\") → [if temp < 15] → get_weather(\"Istanbul\") → Answer\n```\n\n**Pattern 3: Parallel** (A || B)\n```\nUser: \"Hem hava hem de döviz kuru\"\nAgent: [get_weather() || get_exchange_rate()] → Answer\n```",
      "metadata": {}
    },
    {
      "cell_type": "markdown",
      "source": "# Usage Examples: Agent in Action\n# \n# ⚠️ ÖNCE ÖNCEKİ HÜCRELERİ ÇALIŞTIRIN:\n#    1. Setup cell (tool definitions)\n#    2. Helper function cell (run_agent definition)\n\nprint(\"=\"*80)\nprint(\"AGENT EXAMPLES - Function Calling ile LLM\")\nprint(\"=\"*80)\n\n# Example 1: Basit hava durumu sorgusu\nprint(\"\\n[Example 1] Basit tool kullanımı\")\nprint(\"-\"*80)\nresponse1 = run_agent(\"Istanbul'daki hava durumu nasıl?\")\nprint(response1)\n\n# Example 2: Çoklu adım - hesaplama gerektiren\nprint(\"\\n\\n[Example 2] Çoklu tool kullanımı\")\nprint(\"-\"*80)\nresponse2 = run_agent(\"Ankara'daki sıcaklığı 2 ile çarp, sonucu söyle\")\nprint(response2)\n\n# Example 3: Birden fazla şehir karşılaştırma\nprint(\"\\n\\n[Example 3] Karmaşık reasoning\")\nprint(\"-\"*80)\nresponse3 = run_agent(\"Istanbul ve Ankara'daki hava durumunu karşılaştır, hangisi daha sıcak?\")\nprint(response3)\n\nprint(\"\\n\" + \"=\"*80)",
      "metadata": {},
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": "# Helper Function: Simple Agent Loop\n\ndef run_agent(user_message, model=\"gemini/gemini-pro\", max_iterations=5, verbose=True):\n    \"\"\"\n    Basit bir agent döngüsü: LLM'e soru sor, tool çağırsın, sonucu kullan\n    \n    Args:\n        user_message: Kullanıcı sorusu\n        model: LiteLLM model tag (default: gemini/gemini-pro)\n        max_iterations: Maksimum tool çağrı sayısı\n        verbose: Ara adımları göster\n    \n    Returns:\n        LLM'in final cevabı\n    \"\"\"\n    messages = [{\"role\": \"user\", \"content\": user_message}]\n    \n    for i in range(max_iterations):\n        # LLM'i çağır\n        response = completion(\n            model=model,\n            messages=messages,\n            tools=tools,\n            temperature=0.0\n        )\n        \n        response_message = response.choices[0].message\n        \n        # Tool çağrısı var mı?\n        if hasattr(response_message, 'tool_calls') and response_message.tool_calls:\n            if verbose:\n                print(f\"\\n🤖 Agent Iteration {i+1}\")\n            \n            # Assistant'ın mesajını kaydet\n            messages.append({\n                \"role\": \"assistant\",\n                \"content\": response_message.content,\n                \"tool_calls\": [\n                    {\n                        \"id\": tc.id,\n                        \"type\": tc.type,\n                        \"function\": {\n                            \"name\": tc.function.name,\n                            \"arguments\": tc.function.arguments\n                        }\n                    }\n                    for tc in response_message.tool_calls\n                ]\n            })\n            \n            # Her tool'u çalıştır\n            for tool_call in response_message.tool_calls:\n                func_name = tool_call.function.name\n                func_args = json.loads(tool_call.function.arguments)\n                \n                if verbose:\n                    print(f\"  🔧 Calling: {func_name}({func_args})\")\n                \n                # Tool'u çalıştır\n                try:\n                    result = tool_functions[func_name](**func_args)\n                    if verbose:\n                        print(f\"  ✓ Result: {result}\")\n                except Exception as e:\n                    result = json.dumps({\"error\": str(e)})\n                    if verbose:\n                        print(f\"  ❌ Error: {result}\")\n                \n                # Tool sonucunu ekle\n                messages.append({\n                    \"role\": \"tool\",\n                    \"tool_call_id\": tool_call.id,\n                    \"name\": func_name,\n                    \"content\": result\n                })\n        \n        # Final cevap\n        elif response_message.content:\n            if verbose:\n                print(f\"\\n✅ Final Answer:\")\n            return response_message.content\n        \n        else:\n            return \"Model yanıt vermedi\"\n    \n    return \"Maksimum iterasyon sayısına ulaşıldı\"\n\nprint(\"✓ Helper function 'run_agent()' tanımlandı\")",
      "metadata": {},
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": "# Setup: LiteLLM ile Function Calling\n\n!pip install -q litellm\n\nimport json\nimport os\nfrom litellm import completion\n\n# API Key setup\nGEMINI_API_KEY = os.getenv(\"GEMINI_API_KEY\")\nif not GEMINI_API_KEY:\n    from getpass import getpass\n    GEMINI_API_KEY = getpass(\"GEMINI_API_KEY girin: \")\n    os.environ[\"GEMINI_API_KEY\"] = GEMINI_API_KEY\n\n# ============================================================================\n# Tool Implementations (Gerçek API'lere bağlanabilir)\n# ============================================================================\n\ndef get_current_weather(location: str, unit: str = \"celsius\") -> str:\n    \"\"\"Simulated weather API\"\"\"\n    weather_db = {\n        \"istanbul\": {\"temp_c\": 18, \"condition\": \"Partly cloudy\"},\n        \"ankara\": {\"temp_c\": 12, \"condition\": \"Sunny\"},\n        \"izmir\": {\"temp_c\": 22, \"condition\": \"Clear\"},\n    }\n    \n    location_key = location.lower().replace(\"ı\", \"i\")\n    data = weather_db.get(location_key, {\"temp_c\": 20, \"condition\": \"Unknown\"})\n    \n    temp = data[\"temp_c\"]\n    if unit == \"fahrenheit\":\n        temp = (temp * 9/5) + 32\n    \n    return json.dumps({\n        \"location\": location,\n        \"temperature\": temp,\n        \"unit\": unit,\n        \"condition\": data[\"condition\"]\n    })\n\n\ndef calculate(expression: str) -> str:\n    \"\"\"Safe mathematical calculation\"\"\"\n    try:\n        allowed_chars = set(\"0123456789+-*/(). \")\n        if not all(c in allowed_chars for c in expression):\n            return json.dumps({\"error\": \"Invalid characters\"})\n        \n        result = eval(expression)\n        return json.dumps({\"expression\": expression, \"result\": result})\n    except Exception as e:\n        return json.dumps({\"error\": str(e)})\n\n\n# ============================================================================\n# Tool Definitions (LLM'in anlayacağı format)\n# ============================================================================\n\ntools = [\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"get_current_weather\",\n            \"description\": \"Get current weather for a specific location\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"location\": {\n                        \"type\": \"string\",\n                        \"description\": \"City name, e.g., Istanbul, Ankara\"\n                    },\n                    \"unit\": {\n                        \"type\": \"string\",\n                        \"description\": \"Temperature unit: celsius or fahrenheit\",\n                        \"enum\": [\"celsius\", \"fahrenheit\"]\n                    }\n                },\n                \"required\": [\"location\"]\n            }\n        }\n    },\n    {\n        \"type\": \"function\",\n        \"function\": {\n            \"name\": \"calculate\",\n            \"description\": \"Evaluate a mathematical expression\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"expression\": {\n                        \"type\": \"string\",\n                        \"description\": \"Math expression, e.g., '25 * 3' or '100 / 4'\"\n                    }\n                },\n                \"required\": [\"expression\"]\n            }\n        }\n    }\n]\n\n# Tool registry: function name -> Python function\ntool_functions = {\n    \"get_current_weather\": get_current_weather,\n    \"calculate\": calculate\n}\n\nprint(\"✓ Setup complete!\")\nprint(f\"✓ Available tools: {list(tool_functions.keys())}\")",
      "metadata": {},
      "execution_count": null,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "source": "---\n\n## 2. Function Calling: LLM'lere Araç Kullanma Yeteneği\n\nHafta 9'da function calling'in temellerini gördük. Şimdi production-ready agent'lar oluşturacağız.\n\n### 2.1 Agent Mimarisi\n\n**Agent = LLM + Tools + Decision Loop**\n\n```\n┌─────────────────────────────────────────┐\n│  1. User Query                           │\n└──────────────┬──────────────────────────┘\n               ↓\n┌─────────────────────────────────────────┐\n│  2. LLM Reasoning (ReAct Pattern)       │\n│     \"I need to call get_weather()\"       │\n└──────────────┬──────────────────────────┘\n               ↓\n┌─────────────────────────────────────────┐\n│  3. Execute Tool                         │\n│     → get_weather(\"Istanbul\")            │\n│     → Result: \"18°C, cloudy\"            │\n└──────────────┬──────────────────────────┘\n               ↓\n┌─────────────────────────────────────────┐\n│  4. LLM Synthesis                        │\n│     \"The weather in Istanbul is...\"      │\n└─────────────────────────────────────────┘\n```\n\n### 2.2 Tool Declaration vs Implementation\n\n**Key Design Pattern:**\n- **Declaration**: JSON Schema (LLM'e ne yapabileceğini söyler)\n- **Implementation**: Python function (gerçek işi yapar)\n- **Registry**: Mapping (declaration → implementation)",
      "metadata": {}
    },
    {
      "cell_type": "markdown",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": "# BONUS: LangChain ile Agent (Opsiyonel)\n# \n# LangChain gibi framework'ler daha karmaşık agent sistemleri için kullanışlıdır.\n# Yukarıdaki basit implementation çoğu use case için yeterlidir.\n#\n# LangChain kullanmak isterseniz:\n# !pip install langchain langchain-openai\n#\n# from langchain.agents import tool, AgentExecutor, create_react_agent\n# from langchain import hub\n# from langchain_openai import ChatOpenAI\n#\n# @tool\n# def get_weather(city: str) -> str:\n#     \"\"\"Bir şehrin hava durumunu döndürür\"\"\"\n#     return f\"{city} için hava durumu: 20°C, güneşli\"\n#\n# tools = [get_weather]\n# llm = ChatOpenAI(model=\"gpt-4\")\n# prompt = hub.pull(\"hwchase17/react\")\n# agent = create_react_agent(llm, tools, prompt)\n# agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)\n# agent_executor.invoke({\"input\": \"Ankara'daki hava durumu nasıl?\"})\n\nprint(\"✓ LangChain örneği - gerçek kullanım için yorum satırlarını kaldırın\")"
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Yukarıdaki Akışın Açıklaması\n\n`agent_executor.invoke` çağrıldığında arka planda şunlar olur:\n\n1.  **İlk LLM Çağrısı:** LangChain, kullanıcı sorusunu (`Ankara'daki hava durumu nasıl?`) ve kullanılabilecek araçların listesini (`get_weather` aracının açıklaması) birleştirerek LLM için bir prompt oluşturur.\n2.  **LLM'in Kararı (Reason & Act):** Bizim `MockChatLLM`'imiz, bu prompt'u aldığında, sorunun hava durumuyla ilgili olduğunu anlar ve `get_weather` aracını kullanması gerektiğine karar verir. Çıktı olarak `Action: get_weather, Action Input: {\"city\": \"Ankara\"}` metnini üretir.\n3.  **LangChain Aracı Çağırır:** LangChain, bu çıktıyı ayrıştırır, `get_weather` fonksiyonunu `\"Ankara\"` parametresiyle çağırır.\n4.  **Gözlem (Observe):** `get_weather` fonksiyonu `\"Hava durumu Ankara'da 25 C ve güneşli.\"` sonucunu döndürür. Bu, \"Observation\" (gözlem) olarak adlandırılır.\n5.  **İkinci LLM Çağrısı:** LangChain, bu gözlemi alıp, LLM için yeni bir prompt oluşturur: `\"...Observation: Hava durumu Ankara'da 25 C ve güneşli. ...\"`\n6.  **LLM'in Nihai Cevabı:** LLM, bu gözleme bakarak artık soruyu cevaplayabileceğini anlar ve `Final Answer: Ankara'da hava 25 derece ve güneşli.` çıktısını üretir.\n7.  Agent döngüsü durur ve bu nihai cevap kullanıcıya gösterilir.\n\n## 4. LLMOps: LLM'e Özgü Operasyonel Zorluklar\n\nLLM tabanlı uygulamaları üretime almak, geleneksel MLOps'a ek olarak yeni zorluklar getirir.\n\n- **Prompt Yönetimi (Prompt Management):** Prompt'lar artık projenin en kritik parçalarından biridir. Onları kod gibi versiyonlamak, A/B testine tabi tutmak ve performanslarını izlemek gerekir.\n- **Çıktı Değerlendirmesi:** LLM çıktılarının \"doğruluğunu\" ölçmek zordur. Geleneksel metrikler (accuracy) genellikle yetersizdir. Bunun yerine, \"Helpfulness\" (Yardımcılık), \"Correctness\" (Doğruluk) gibi metrikler için başka bir LLM'i değerlendirici olarak kullanma (LLM-as-a-Judge) gibi teknikler popülerleşmektedir.\n- **Maliyet ve Gecikme (Cost & Latency):** API çağrıları maliyetlidir ve gecikme süresi yüksek olabilir. Sık kullanılan istekler için bir önbellekleme (caching) mekanizması kurmak genellikle zorunludur.\n- **Güvenlik:** Kötü niyetli kullanıcılar, \"prompt injection\" gibi tekniklerle agent'ınıza istemediğiniz komutları verdirmeye çalışabilirler.\n\n### Alıştırma: Agent'a Yeni Bir Araç Ekleme\n\n1.  Basit bir Python fonksiyonu olarak `calculate_len` adında yeni bir `@tool` tanımlayın. Bu araç, bir string almalı ve o string'in karakter sayısını döndürmelidir. Docstring'i, aracın ne işe yaradığını açıkça belirtmelidir (örn: `\"Bir metnin uzunluğunu karakter olarak hesaplamak için kullanılır.\"`).\n2.  Bu yeni aracı, `tools` listesine ekleyin.\n3.  `agent_executor`'ı `\"Merhaba kelimesi kaç harften oluşur?\"` gibi bir soruyla çağırın.\n4.  Agent'ın, doğru aracı (`calculate_len`) seçip, doğru parametreyle (`\"Merhaba\"`) çağırıp, dönen sonucu kullanıcıya sunduğunu gözlemleyin. (Bunun için `MockChatLLM`'in mantığını bu yeni senaryoyu da içerecek şekilde genişletmeniz gerekebilir)."
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "name": "python",
      "version": "3.8.0"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}