Галоўная / Артыкулы / Безстановы цыкл чату: ручныя вызывы API OpenAI на Pythonе

Безстановы цыкл чату: ручныя вызывы API OpenAI на Pythonе

Створыце чат з калькольнімі раундамі за дапомогайом Python SDK ад OpenAI, самастаўляючы історыю паведамленняў, і разумейце, чаму той самы цикл ляжыць у основе памяці та агентаў LangChain.

1167 слоў

Фрэймворкі, такія як LangChain, ствараюць вражэння, што моделі чату ўсё ж маюць памяць, але API, які стоіць за ними, нічога не запам’ятае. Кожны вызов є незалежным, а „розмова“ — це спіс паведамленняў, які ваш код занова стварае і перадае ў кожны раз. Якщо напісаць такой цыкл вручную за дапамогою Python SDK ад OpenAI, можна точна пазнакоміцца з тым, што автаматызуюць фрэймворкі агентаў, чаму вартасць токенав падвайваецца па ходу розмовы і якія проблемы трэба урахавіць.

Што на самай працэ ёсьць канцэнтр падмоделі, якая розмешчана на сервере

API ад OpenAI — цэла простая структура: прадаўцы запускае модэль на своіх GPU і надае можлівасць аналізу паведамленняў через HTTPS. Вы надаеце текст, модэль генеруе токены ў своем звычным цыклі, і вы плаціце за кожны токен у обох направленнях. З гэтага выліваюцца тры наследкі:

  1. Ён не мае стану. Нічога з паканальных запыткаў не захоўваецца, таму кожны запыт должен містіць усё, што модэль павінна знати.
  • Это звычайны HTTP. SDK абгружае запит типу POST, таму калі ўтварыцца проблема, вы можете пераглядзець первісны трафік.
  • Вы купуеце токены, а не адпаведныя адказы. Довгі запиты коштаюць грошай на кожны вызов, які іх уключае.
  • Ніколі не кладзіце ключа 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, таму што ён витрачае рэальныя грошы і патрабуе справжні ключ. Якщо вы хочаце тэсты для такога коду, створыце мак-кліент.

    Проблемы, якія трэба врачыць з самага пачатку

    1. Відсутны ключ: AuthenticationError з кодам HTTP 401, як правіло, таму што зменная не задана ў гэтым сераўере, напісана некоректна або містіць зайвыя прабелы. Пераканаўцеся ў момент запуску і негайна збійце працэс.
    2. Ліміты частоты: RateLimitError з кодам HTTP 429 означае занадта большы колькісць запитоў або порожній баланс прадплацы. Агенты, якія работають у цикле, страждзяць ад гэтага, таму зараз жа дадзіце можлівасць перапрыбутку з адкладэнням.
  • Історыя не працюе ні в якім разе. Якщо забыць дадзіць новыя даны, модель забывае все; якщо дадзіць іх без канца, выкарыстоўванне токенав зрастае квадратычна, пакуль не выйдзе наяўнасць контэкста. Рэальныя системы выкарыстоўваюць фільтрацыю, стасумараванне або выявленне толькі значыцьных дадзеных з історыі, і самэ гэта є сутнёю тэхналогіі RAG.
  • Значэнне Temperature 0 дае стабільны выхад, але не правільны. Пераканаўцеся ў правядзамасці всьго, што мае значэнне.
  • Аднойчыны формат у всіх прадаўцах

    Клауд прыёмляе спіс паведамленняў ад корыстніка і асистента, пры чым запрошэнне системы перакладаецца ў аднальны параметр вышэйшага рангу. Gemini выкарыстоўвае той самы модель на адной структурэ дыялога, пры чым ролі пазначаюцца як user і model. Ollama адказвае аб’ектам, сумэжным з OpenAI, таму гэты код можа нацелівацца на локальны модель, зменіўшы базовую адресу і назву модэлю; дакладней — выклік Клауда, GPT і Gemini через аб’екты, сумэжныя з OpenAI. Саме гэта з’еднанне дазволяе LangChain запрошваць абстракцыю над многама прадавцамі.

    Ключовыя выводы

    • API для чату ўсунуты з можлівасцю зберагчання стану; ваш код керуе дыялогам і перасылае яго занова.
    • Спіс паведамленняў з пазначэнням ролей фактычна ёсць стандартам для разных прадавцаў.
    • Фіксуйце usage пасля кожнага выкліку, адтакуль як колькасць токенав увайшанняў зростае ў кожны раз.
  • Зберагаеце кантроллеры ў сэрвісе і выкарыстоўваеце вызывы жывых API парадульна ў процесе CI.
  • Раскідваеце проблемы 401, 429 і неактуальную історію вызываў пры стварэнні агентаў, таму што цыклы агентаў паўтараюць усе тры прычыны.