Безстановы цыкл чату: ручныя вызывы API OpenAI на Pythonе
Створыце чат з калькольнімі раундамі за дапомогайом Python SDK ад OpenAI, самастаўляючы історыю паведамленняў, і разумейце, чаму той самы цикл ляжыць у основе памяці та агентаў LangChain.
Фрэймворкі, такія як LangChain, ствараюць вражэння, што моделі чату ўсё ж маюць памяць, але API, які стоіць за ними, нічога не запам’ятае. Кожны вызов є незалежным, а „розмова“ — це спіс паведамленняў, які ваш код занова стварае і перадае ў кожны раз. Якщо напісаць такой цыкл вручную за дапамогою Python SDK ад OpenAI, можна точна пазнакоміцца з тым, што автаматызуюць фрэймворкі агентаў, чаму вартасць токенав падвайваецца па ходу розмовы і якія проблемы трэба урахавіць.
Што на самай працэ ёсьць канцэнтр падмоделі, якая розмешчана на сервере
API ад OpenAI — цэла простая структура: прадаўцы запускае модэль на своіх GPU і надае можлівасць аналізу паведамленняў через HTTPS. Вы надаеце текст, модэль генеруе токены ў своем звычным цыклі, і вы плаціце за кожны токен у обох направленнях. З гэтага выліваюцца тры наследкі:
- Ён не мае стану. Нічога з паканальных запыткаў не захоўваецца, таму кожны запыт должен містіць усё, што модэль павінна знати.
Ніколі не кладзіце ключа API ў код выкарыстоўвання. Клічы можуць прасочыцца через історыю Git, скріншоты і спяльныя зошыты, а прасочаны ключ значыць, што хтось іншы будзе витрачаць грошы з вашага аблікавання. SDK автаматычна чытае OPENAI_API_KEY з сераўіснага аптэкту, таму ваш скрыпт зовсім не патрэбуе коду для керавання клічам. Выкарыстоўванне API практыкуецца окрема ад прыўязкі до ChatGPT, і новыя аблікаванні зазвычай патрэбуюць невялікага прадзвоненага балансу.
Ролі: формат спяльных паведамленняў
Запит несе спіс паведамленняў, кожна з якіх мае свою роль:
systemмістіць вашы інструкцыі, якія модэль прыдзеляе большай важнасці.userмістіць тое, што напісаў корыстнік.
assistant зберагае паказанні модэлі з ранейшых разы, а ў агентах — вызовы яго інструментаў.Гэты формат выкарыстоўваецца ў всім экасыстэме. Claude і Gemini выкарыстоўваюць тую ж ідею з незначнымі разніцамі, Ollama яе імітуюць, а SystemMessage, HumanMessage і AIMessage у LangChain являюць ся класамі, якіе представляюць гэтыя ролі. Прадаўець спачатку прыкладзвае список да ўсага ў адну последоўнасць токэнаў пры генераванні, так што ролі насправдзе ўскладнююць структуруванне запрошэнняў.
Адна запрос
У першым прыкладзе ствараецца кліент, який чытае ключ з аблака налашчэнняў, выканае системную інструкцыю плюс адна запитанне, і выводзіць паказанні разам з колькасцю токэнаў запрошэння і завершэння з usage:
from openai import OpenAI
client = OpenAI() # key from env
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": "You are a concise "
"Python assistant.",
},
{
"role": "user",
"content": "Why resend the whole "
"chat history each call?",
},
],
temperature=0,
)
print(resp.choices[0].message.content)
u = resp.usage
print(u.prompt_tokens, u.completion_tokens)
gpt-4o-mini — это недорагі чыннік, падходзячы для навучэння; пераход на большы чыннік адбываецца проста, хоць называнні чыннікаў і ўсё цэна зменяюцца, таму трэба пераканацца ў актуальным списку. Значэнне temperature=0 мінімізуе варыябельнасць у выборы датак, што ўзято за звычай для адпаведзення на запытанні і пазней для агентаў, якія выкарыстоўваюць інструменты. Запісвайте usage пасля кожнага вызову; гэта ваш метраж витак.
Ведзенне дыялогу самі
Паколькі сервер забывае ўсё, історыю керуе ваш код: пасля кожнага вызову ён зберагае адпаведзь, дадае наступнае запытанне і зноў адправляе всё. Наводзімы выкладчык робіць гэта за дапамогою списока msgs на рэвэлі модуля, які пачынаецца з системнага запытання:
msgs = [{
"role": "system",
"content": "You are a concise assistant.",
}]
def ask(text: str) -> str:
msgs.append(
{"role": "user", "content": text}
)
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=msgs, # full history
temperature=0,
)
reply = resp.choices[0].message.content
msgs.append({
"role": "assistant",
"content": reply,
})
return reply
print(ask("Define a context window."))
print(ask("Now for a five-year-old."))
print(ask("Which answer was shorter?"))
Трэція запытка падтверджае гэты моман. Модель можа пораўняваць толькі два адпаведзенні, калі ўбыточна яны аднаходзяцца ў msgs і практычна перадаюцца занова. Адключыце рэченне, якое дадае адпаведзенне асістэнта, і ён не будзе разумець, пра што йдзе мова.
Гэтая функцыя ask() практычна з’яўляецца пад разнымі маскамі. Веб-дапытка ChatGPT ў сутнасці являе сабою тую ж функцыю з інтэрфейсам для корыстувальца. Клас RunnableWithMessageHistory з бібліятэкі LangChain — это адмініструемая версія процэса дадавання і перадачы даных занова. Внутрэшній цикл агента таксама выконваецца за тым жа принцыпам, з дадаваннем вызоў і рэзультатаў ад інструментаў. Зважайце на выклікы: тры перадачы даных заменяюцца адной, а другой, таму колькасць токенав увходных дадзенаў зрастае з кожным абменам.
Адкліканне прыклада
Установіце неабходныя залежнасці, выэкспортуйце ключ у сваёй шэлі і запустыце скрыпт. Паказаны ключ є толькі заменніком; выкарыстоўваеце свой ключ через шэлю або менеджер секретаў, ніколі не захоўвайце яго ў файле, які буў зберажаны:
pip install -r requirements.txt
export OPENAI_API_KEY="sk-..."
python examples/part02_chat.py
Скрыпт выкалівае адзін вызов на адно пераходжэнне, пасля чаго запускае размову на тры пераходжэння, і пасля кожнага запиту аднарачоўвае выкарыстоўванне токенаў і адпрацоўваны час. Ён негайна зупіняецца, якщо ключ не існуе, і ўмышлена не включаецца ў процес CI, таму што ён витрачае рэальныя грошы і патрабуе справжні ключ. Якщо вы хочаце тэсты для такога коду, створыце мак-кліент.
Проблемы, якія трэба врачыць з самага пачатку
- Відсутны ключ:
AuthenticationErrorз кодам HTTP 401, як правіло, таму што зменная не задана ў гэтым сераўере, напісана некоректна або містіць зайвыя прабелы. Пераканаўцеся ў момент запуску і негайна збійце працэс. - Ліміты частоты:
RateLimitErrorз кодам HTTP 429 означае занадта большы колькісць запитоў або порожній баланс прадплацы. Агенты, якія работають у цикле, страждзяць ад гэтага, таму зараз жа дадзіце можлівасць перапрыбутку з адкладэнням.
Аднойчыны формат у всіх прадаўцах
Клауд прыёмляе спіс паведамленняў ад корыстніка і асистента, пры чым запрошэнне системы перакладаецца ў аднальны параметр вышэйшага рангу. Gemini выкарыстоўвае той самы модель на адной структурэ дыялога, пры чым ролі пазначаюцца як user і model. Ollama адказвае аб’ектам, сумэжным з OpenAI, таму гэты код можа нацелівацца на локальны модель, зменіўшы базовую адресу і назву модэлю; дакладней — выклік Клауда, GPT і Gemini через аб’екты, сумэжныя з OpenAI. Саме гэта з’еднанне дазволяе LangChain запрошваць абстракцыю над многама прадавцамі.
Ключовыя выводы
- API для чату ўсунуты з можлівасцю зберагчання стану; ваш код керуе дыялогам і перасылае яго занова.
- Спіс паведамленняў з пазначэнням ролей фактычна ёсць стандартам для разных прадавцаў.
- Фіксуйце
usageпасля кожнага выкліку, адтакуль як колькасць токенав увайшанняў зростае ў кожны раз.