Skip to main content

foundation-model-agent-loop

Canonical pattern for building an agentic tool-calling loop using a Databricks-hosted Foundation Model. Use when creating agent_loop.py or any backend that calls a Foundation Model with function-calling to orchestrate tools.

インストールへ移動

ソース情報

リポジトリ
databricks-solutions/vibe-coding-workshop-template
ソースの最終更新活動
2026年4月29日 00:23
検出された SKILL.md の言語
英語
スター
6
フォーク
7

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
foundation-model-agent-loop
description
Canonical pattern for building an agentic tool-calling loop using a Databricks-hosted Foundation Model. Use when creating agent_loop.py or any backend that calls a Foundation Model with function-calling to orchestrate tools.
metadata
{"author":"stayfinder-team","version":"1.0"}
compatibility
Databricks Apps, Foundation Models with function-calling support
# Foundation Model Agent Loop A plain Python agentic loop that calls a Databricks-hosted Foundation Model with tool definitions. The model decides which tools to call; your code executes them and feeds results back until the model produces a final text response. **No LangGraph, no MLflow, no custom model deployment** — just HTTP calls. ## When to Use This Skill Use this skill when: - Building a backend agent that uses a Databricks Foundation Model (e.g., `databricks-meta-llama-3-3-70b-instruct`) as the "brain" - The agent needs to call tools (Genie, business logic, external APIs) based on user input - You want the model to decide routing — no hand-written if/else classifiers ## Prerequisites - A Databricks-hosted Foundation Model endpoint that supports function calling - `databricks-sdk` and `requests` Python packages - Valid Databricks authentication (`Config()` from the SDK) ## Architecture ``` User Message │ ▼ ┌─────────────────────────────────────┐ │ Build messages list: │ │ [system_prompt, user_message] │ │ + tool definitions (TOOL_SCHEMAS) │ └──────────────┬──────────────────────┘ │ ┌──────────▼──────────┐ │ POST to Foundation │◄──────────────────────┐ │ Model endpoint │ │ └──────────┬───────────┘ │ │ │ ┌──────────▼───────────┐ │ │ Parse response: │ │ │ choices[0].message │ │ └──────────┬───────────┘ │ │ │ ┌──────────▼───────────┐ ┌─────────────┐ │ │ Has tool_calls? │─YES─│ Execute each │ │ │ finish_reason? │ │ tool locally │ │ └──────────┬───────────┘ └──────┬──────┘ │ │ NO │ │ │ Append tool results │ ┌──────────▼───────────┐ to messages list │ │ Return final text │ │ │ │ content to user │ └──────────────┘ └──────────────────────┘ ``` ## Canonical Implementation ### Step 1: Define tool schemas (OpenAI function-calling format) ```python TOOL_SCHEMAS = [ { "type": "function", "function": { "name": "query_genie", "description": "Query the accommodation database using natural language.", "parameters": { "type": "object", "properties": { "question": { "type": "string", "description": "A natural-language question about the data", } }, "required": ["question"], }, }, }, # ... more tools ] ``` ### Step 2: Create a dispatch map ```python from myapp.tools.genie_tool import query_genie_fn from myapp.tools.custom_tools import calculate_price_fn TOOL_FUNCTIONS = { "query_genie": query_genie_fn, "calculate_price": calculate_price_fn, } ``` ### Step 3: Call the Foundation Model endpoint ```python import requests from databricks.sdk.core import Config def _call_foundation_model(messages, tools=None): cfg = Config() headers = cfg.authenticate() headers["Content-Type"] = "application/json" host = str(cfg.host).rstrip("/") endpoint = os.getenv("LLM_ENDPOINT_NAME", "databricks-meta-llama-3-3-70b-instruct") url = f"{host}/serving-endpoints/{endpoint}/invocations" payload = {"messages": messages, "max_tokens": 1024} if tools: payload["tools"] = tools resp = requests.post(url, json=payload, headers=headers, timeout=120) resp.raise_for_status() return resp.json() ``` ### Step 4: Implement the agent loop ```python import json MAX_ITERATIONS = 10 def run_agent_loop(user_message: str) -> str: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_message}, ] for iteration in range(MAX_ITERATIONS): try: response = _call_foundation_model(messages, tools=TOOL_SCHEMAS) except Exception as exc: return f"Error connecting to AI service: {exc}" choices = response.get("choices", []) if not choices: return "Unexpected response from AI service." assistant_msg = choices[0].get("message", {}) finish_reason = choices[0].get("finish_reason", "") # Always append the assistant message to maintain conversation history messages.append(assistant_msg) # Check: does the model want to call tools? tool_calls = assistant_msg.get("tool_calls") if not tool_calls or finish_reason == "stop": # No tool calls — return the final text response return assistant_msg.get("content", "No response generated.") # Execute each tool call for tc in tool_calls: tool_name = tc["function"]["name"] raw_args = tc["function"]["arguments"] # Parse arguments (model returns them as a JSON string) try: args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args except json.JSONDecodeError: args = {} # Execute the tool fn = TOOL_FUNCTIONS.get(tool_name) if fn: try: result = fn(**args) except Exception as exc: result = json.dumps({"error": str(exc)}) else: result = json.dumps({"error": f"Unknown tool: {tool_name}"}) # Ensure result is a string if not isinstance(result, str): result = json.dumps(result, default=str) # Append tool result to conversation — MUST include tool_call_id messages.append({ "role": "tool", "tool_call_id": tc["id"], "content": result, }) # Loop continues: model receives tool results and decides next action return "Too many steps. Please try a simpler query." ``` ## Critical Details ### Response format from Foundation Model The Foundation Model returns OpenAI-compatible responses: ```json { "choices": [ { "message": { "role": "assistant", "content": "Here are apartments in Austin...", "tool_calls": [ { "id": "call_abc123", "type": "function", "function": { "name": "query_genie", "arguments": "{\"question\": \"Find apartments in Austin under $200\"}" } } ] }, "finish_reason": "tool_calls" } ] } ``` Key fields: - `choices[0].message.tool_calls` — present when the model wants to call tools - `choices[0].message.content` — present when the model has a final text response - `choices[0].finish_reason` — `"stop"` for final response, `"tool_calls"` for tool requests - `tool_calls[].id` — must be passed back as `tool_call_id` in the tool result message - `tool_calls[].function.arguments` — a **JSON string** (not a dict), must be parsed ### Tool result message format When returning tool results to the model, use this exact format: ```python { "role": "tool", "tool_call_id": "call_abc123", # MUST match the id from tool_calls "content": "JSON string with the tool result" } ``` ### Common mistakes to avoid 1. **Forgetting `tool_call_id`** — the model will reject tool results without it 2. **Not parsing `arguments`** — they come as a JSON string, not a dict 3. **Not appending the assistant message** — always append `assistant_msg` to `messages` before processing tool calls, to maintain conversation history 4. **Raising exceptions in tools** — tools should catch all errors and return error strings; never let exceptions propagate 5. **Making the loop async** — `requests.post()` is synchronous; use `def` not `async def` unless you switch to `httpx` 6. **No iteration cap** — always limit iterations (10 is a good default) to prevent infinite loops if the model keeps requesting tools ### Genie tool null-safety The Databricks Genie API may return responses where `statement_response` or `manifest` is `None` — this happens when Genie answers with text instead of generating a SQL query. Always guard: ```python if ( result is None or result.statement_response is None or result.statement_response.manifest is None ): return json.dumps({"genie_text": "No SQL result generated."}) ``` ## Wiring into FastAPI In your API router, import and call the agent loop directly: ```python def _query_agent(user_message: str): try: from server.agents.agent_loop import run_agent_loop response_text = run_agent_loop(user_message) return {"agent_response": response_text} if response_text else None except Exception as exc: logger.warning("Agent loop failed: %s", exc) return None ``` No HTTP call to a serving endpoint — the agent runs in-process.
GitHubで見る