Практичні нотатки: Я створив локального AI-агента за допомогою Ollama — і складна частина
Покрокова інструкція з практичних нотаток: Я створив локального AI-агента за допомогою Ollama — та складні аспекти: контракти, перевірки та місця для вставки коду для команд, які використовують цю схему.
У цьому посібнику детально описано шлях від сировини до функціональної системи для статті: «Я створив локального AI-агента за допомогою Ollama — а складною частиною була не модель». Основна увага приділяється конкретним крокам виконання, чітким перевіркам та коду, який можна просто додати до репозиторію, не намагаючись здогадатися про його призначення. На етапі огляду необхідно визначити вхідні дані, виконавця кроку та критерії завершення перед зміною коду. Виконавці мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись зрозуміти прихований стан системи. Краще використовувати невеликі, тестовані одиниці коду замість об’ємних скриптів. Коли крок зазнає невдачі, причина має бути чітко визначена та стосуватися лише однієї функції, а не всієї складної системи.
Чому ви обрали Ollama
Під час роботи над етапом «Чому ви обрали Ollama» спочатку запишіть умови договору: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Розглядайте цей етап як договір між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте безпроблемного часткового виконання завдань. Записуйте ідентифікатор запиту, ідентифікатор моделі та час виконання кожного запиту. Без цих записів періодичні помилки постачальника можуть здаватися багами програми.
ollama pull qwen3
pip install ollama
from ollama import chat
response = chat(
model="qwen3",
messages=[
{"role": "user", "content": "Explain what an overdue invoice is."}
],
)print(response.message.content)
Чат-бот відповідає; агент виконує кроки
Під час роботи над кожним етапом, на який відповідає чат-бот, спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність під час подальших змін у коді. Записуйте час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Фіксуйте ідентифікатор запиту, ідентифікатор моделі та час затримки при кожному виклику. Без цих записів періодичні помилки постачальника виглядають як баги програми.
Почніть із простих інструментів
Під час роботи на етапі «Почніть з обмежених інструментів» спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Записуйте ідентифікатор запиту, ідентифікатор моделі та час виконання кожного виклику. Без цих даних періодичні помилки постачальника виглядають як баги додатку. Під час роботи на етапі «Почніть з обмежених інструментів» спочатку запишіть умови взаємодії: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед складними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну функцію, а не на заплутану послідовність операцій.
CUSTOMERS = {
"acme plumbing": {
"customer_id": "cus_1042",
"name": "Acme Plumbing",
"email": "billing@example.com",
}
}
INVOICES = [
{
"invoice_id": "INV-2048",
"customer_id": "cus_1042",
"amount": 1850.00,
"days_overdue": 18,
}
]
def find_customer(name: str) -> dict:
customer = CUSTOMERS.get(name.strip().lower())
return customer or {"error": "customer_not_found"}
def get_overdue_invoices(customer_id: str) -> dict:
matches = [
invoice
for invoice in INVOICES
if invoice["customer_id"] == customer_id
and invoice["days_overdue"] > 0
]
return {"invoices": matches, "count": len(matches)}
Надайте моделі інструменти, а не уявний доступ
Етап надання моделі інструментів працює найкраще, якщо його розглядати як вимірювану поверхню. Запишіть один ідеальний приклад роботи, один випадок збою та примітку про скасування змін перед розширенням обсягу завдань. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не погоджуйтесь на мовчазне часткове виконання завдань. Забезпечте фіксацію версії інтерпретатора та файлу з параметрами залежностей перед навчанням алгоритму циклу. Відхилення між ноутбуком та системою CI є найпоширенішою причиною мовчазних збоїв під час демонстрацій API.
import json
from ollama import chat
def find_customer(name: str) -> dict:
"""Find a customer by business name and return its verified record."""
customer = CUSTOMERS.get(name.strip().lower())
return customer or {"error": "customer_not_found"}
def get_overdue_invoices(customer_id: str) -> dict:
"""Return overdue invoices for a verified customer ID."""
matches = [
invoice
for invoice in INVOICES
if invoice["customer_id"] == customer_id
and invoice["days_overdue"] > 0
]
return {"invoices": matches, "count": len(matches)}
TOOLS = {
"find_customer": find_customer,
"get_overdue_invoices": get_overdue_invoices,
}
Створіть цикл агента
Етап створення циклу агента працює найкраще, якщо його розглядати як вимірювану характеристику. Запишіть один ідеальний запис роботи, один випадок збою та примітку щодо скасування змін перед розширенням обсягу завдань. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Візуальний контроль витрат заздалегідь запобігає несподіваним рахункам під час переходу від демо-версії до спільних середовищ. Закріпіть інтерпретатор та файли блокування залежностей перед тим, як навчати цикл. Розбіжності між ноутбуком та системою CI є найпоширенішою причиною прихованих збоїв у демонстраціях API.
SYSTEM_PROMPT = """
You are an invoice assistant.
Rules:
- Never invent a customer, invoice, email address, balance, or date.
- Use find_customer before requesting invoices.
- Only use customer IDs returned by tools.
- If a tool returns an error or no records, explain that clearly.
- You may draft communication, but you cannot send it.
"""
def run_agent(user_request: str) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_request},
] for _ in range(6):
response = chat(
model="qwen3",
messages=messages,
tools=list(TOOLS.values()),
) messages.append(response.message) if not response.message.tool_calls:
return response.message.content for call in response.message.tool_calls:
name = call.function.name
arguments = call.function.arguments if name not in TOOLS:
result = {"error": "tool_not_allowed"}
else:
try:
result = TOOLS[name](**arguments)
except (TypeError, ValueError) as error:
result = {
"error": "invalid_tool_arguments",
"detail": str(error),
} messages.append(
{
"role": "tool",
"tool_name": name,
"content": json.dumps(result),
}
) return "I stopped because the task exceeded the maximum number of steps."
Справжнім рішенням було не покращення запиту
Справжнім рішенням є те, що етапи найкраще обробляти як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Фіксуйте версію інтерпретатора та файл з блокуванням залежностей перед тим, як пояснювати логіку циклу. Розбіжності між ноутбуком та системою CI є найпоширенішою причиною прихованих збоїв у демонстраціях API. Справжнім рішенням є те, що етапи найкраще обробляти як вимірювану поверхню. Збережіть один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Віддавайте перевагу малим, тестованим одиницям коду перед величезними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій.
Додайте структурований вихід на межі
Щоб додати структурований вихід на цьому етапі, необхідно спочатку визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість знову виконати крок, починаючи з відомої точки контролю, без необхідності здогадуватися про прихований стан. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Призначте назви елементів, визначте критерії успіху та не допускайте мовчазного часткового завершення. Відокремте процес створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без необхідності переписування машини станів розмови.
from pydantic import BaseModel, Field
class ReminderReview(BaseModel):
customer_name: str
invoice_ids: list[str]
total_due: float = Field(ge=0)
draft_subject: str
draft_body: str
requires_approval: bool = True
review_response = chat(
model="qwen3",
messages=messages,
format=ReminderReview.model_json_schema(),
)
review = ReminderReview.model_validate_json(
review_response.message.content
)
Стан та пам’ять — це різні речі
Для державних систем пам’ять є сценою — необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись визначити прихований стан. Записуйте час виконання та витрати на токени чи запити поруч із функціональними результатами. Чітке відображення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-середовища до спільних. Розділіть процес створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без переписування машини станів розмови.
task_state = {
"customer_id": "cus_1042",
"verified_invoice_ids": ["INV-2048"],
"approved_actions": [],
}
Локальний режим не означає автоматично безпеки
Оскільки For the Local не створює етапи автоматично, перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Зберігайте конфігурацію поза кодом додатку. Файли середовища, сховища секретів та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь граф. Розділіть створення клієнта від циклу обробки повідомлень, щоб можна було замінювати постачальників без переписування машини станів розмови. Оскільки For the Local не створює етапи автоматично, перед зміною коду необхідно визначити вхідні дані, власника кроку та критерії завершення. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Віддавайте перевагу малим, тестованим одиницям перед величезними скриптами. Коли крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на щось загальне.
Сконструйований конвеєр обробки даних.
Як протестувати агента
Під час виконання етапу «Як протестувати» спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Цей перелік допомагає зберігати чесність у подальших змінах коду. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Призначте назви елементам, визначте критерії успіху та не допускайте безпроблемного часткового завершення роботи. Фіксуйте ідентифікатор запиту, ідентифікатор моделі та час виконання кожного виклику. Без цих записів періодичні помилки постачальника виглядають як баги програмного забезпечення.
Як виглядала функціональна версія
Під час роботи над етапом «Яка робоча версія» спочатку запишіть умови контракту: необхідні вхідні дані, сигнал про успіх та те, що відбувається у разі часткової невдачі. Такий перелік допомагає зберігати чесність пізніших змін у коді. Запишіть час виконання та витрати на токени або запити поруч із функціональними результатами. Чітке бачення витрат заздалегідь запобігає несподіваним рахункам, коли процес переходить від демо-версії до спільних середовищ. Записуйте ідентифікатор запиту, ідентифікатор моделі та час затримки після кожного виклику. Без цих записів періодичні помилки постачальника виглядають як баги програми.
User request
→ find_customer(name="Acme Plumbing")
→ verified customer_id: cus_1042
→ get_overdue_invoices(customer_id="cus_1042")
→ verified invoice: INV-2048, $1,850, 18 days overdue
→ generate draft
→ wait for human approval
Останнє урок
Під час виконання останнього етапу уроку спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Зберігайте конфігурацію окремо від коду додатку. Файли середовища, сховища секретних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Записуйте ідентифікатор запиту, ідентифікатор моделі та час відгуку для кожного виклику. Без цих даних періодичні помилки постачальника виглядають як баги додатку. Під час виконання останнього етапу уроку спочатку запишіть контракт: необхідні вхідні дані, сигнал про успіх та те, що відбувається при частковій невдачі. Цей перелік допомагає зберігати чесність пізніших змін у коді. Віддавайте перевагу невеликим, тестованим одиницям коду перед об’ємними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій.
Чек-лист для експлуатації
Етап перевірки операційних процедур працює найкраще, якщо його розглядати як вимірювану основу. Збережіть один ідеальний запис, один випадок збою та примітку про скасування дій перед розширенням обсягу роботи.
Документуйте як успішний, так і відновлювальний сценарії роботи разом. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої доробки.
Фіксуйте версії інтерпретатора та файли блокування залежностей перед початком виконання циклу. Розбіжності між ноутбуком та системою CI є найпоширенішою причиною безслухняних збоїв під час демонстрацій API.
У разі, коли наступним кроком є написання коду чи виклик інструменту, краще використовувати структуровані результати з перевіркою схеми, ніж вільний текст.
Робіть контрольні пункти після дорогих операцій. Система відновлення не повинна знову оплачувати однаковий виклик LLM, коли оператор повторює роботу з пізнішим етапом.
Фіксуйте версії залежностей та записуйте хеш-значення зображення, яке використовувалося під час демонстрації. Відтворюваність є кращою за індивідуальні знання.
Перш ніж запускати стек у продакшн, заморозьте версії, створіть «золотий» запис для критичного шляху та підтвердьте кроки відкату. У спільних середовищах необхідні обмеження на частоту запитів, перевірки прав власності та чіткий власник для зміни секретів. Віддавайте перевагу надійності перед креативними одноразовими демонстраціями.
Примітка для a5f763eecd03: не зберігайте ключі постачальника у репозиторії, встановіть ліміт токенів на сеанс та зберігайте записи поруч із фікстурами для оцінки, щоб подальша заміна моделей залишалася порівнянною.