Создана первоначальная структура обращения к языковым моделям
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
API_KEY=
|
||||||
|
BASE_URL=
|
||||||
|
PYDANTIC_AI_NO_BANNER=1
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# My files
|
||||||
|
.config/
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Credentianals
|
||||||
|
.env
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.13
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
# from pydantic import BaseModel
|
||||||
|
from pydantic_ai import Agent
|
||||||
|
|
||||||
|
from llm.config import load_config
|
||||||
|
from llm.factory import LLMModelProvider
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
|
||||||
|
|
||||||
|
config = load_config("config.yml")
|
||||||
|
model = await LLMModelProvider.create_from_config(config.llm)
|
||||||
|
|
||||||
|
|
||||||
|
# model = await LLMModelProvider.create_codex(model_name='gpt-5.6-luna')
|
||||||
|
# model = LLMModelProvider.create_openai_compatible(model_name="qwen/qwen3-8b",
|
||||||
|
# api_key=os.getenv("API_KEY"),
|
||||||
|
# base_url=os.getenv("BASE_URL"))
|
||||||
|
agent = Agent(model=model)
|
||||||
|
result = await agent.run('Привет, дай мне ответ')
|
||||||
|
print(result.output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
llm:
|
||||||
|
provider: openai # openai, codex
|
||||||
|
# model: gpt-5.6-luna
|
||||||
|
model: qwen/qwen3-8b
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated, Literal
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class CodexConfig(BaseModel):
|
||||||
|
provider: Literal["codex"]
|
||||||
|
model: str = "gpt-5.6-luna"
|
||||||
|
|
||||||
|
credential_path: Path | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAiConfig(BaseModel):
|
||||||
|
provider: Literal["openai"]
|
||||||
|
model: str
|
||||||
|
|
||||||
|
api_key_env: str = "API_KEY"
|
||||||
|
base_url_env: str = "BASE_URL"
|
||||||
|
|
||||||
|
|
||||||
|
LLMConfig = Annotated[
|
||||||
|
CodexConfig | OpenAiConfig,
|
||||||
|
Field(discriminator="provider")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class AppConfig(BaseModel):
|
||||||
|
llm: LLMConfig
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(path: str | Path = "config.yaml") -> AppConfig:
|
||||||
|
path = Path(path)
|
||||||
|
|
||||||
|
with path.open("r", encoding="utf-8") as file:
|
||||||
|
data = yaml.safe_load(file)
|
||||||
|
|
||||||
|
return AppConfig.model_validate(data)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import json
|
||||||
|
from dataclasses import asdict
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic_ai.providers.openai_codex import (
|
||||||
|
OpenAICodexCredentials,
|
||||||
|
OpenAICodexCredentialSource,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FileCodexCredentionalSource(OpenAICodexCredentialSource):
|
||||||
|
def __init__(self,
|
||||||
|
path: Path,
|
||||||
|
encoding: str="utf-8",
|
||||||
|
ensure_ascii: bool=False,
|
||||||
|
indent: int=2):
|
||||||
|
super().__init__()
|
||||||
|
self._path = path
|
||||||
|
self._encoding=encoding
|
||||||
|
self._ensure_ascii=ensure_ascii
|
||||||
|
self._indent=indent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def path(self):
|
||||||
|
return self._path
|
||||||
|
|
||||||
|
async def load(self) -> OpenAICodexCredentials:
|
||||||
|
return OpenAICodexCredentials(
|
||||||
|
**json.loads(
|
||||||
|
self._path.read_text(encoding=self._encoding)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def save(self, credentials: OpenAICodexCredentials) -> None:
|
||||||
|
self._path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
asdict(credentials),
|
||||||
|
ensure_ascii=self._ensure_ascii,
|
||||||
|
indent=self._indent
|
||||||
|
),
|
||||||
|
encoding=self._encoding
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import os
|
||||||
|
import webbrowser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pydantic_ai.models.openai import OpenAIChatModel
|
||||||
|
from pydantic_ai.models.openai_codex import OpenAICodexModel
|
||||||
|
from pydantic_ai.providers.openai import OpenAIProvider
|
||||||
|
from pydantic_ai.providers.openai_codex import (
|
||||||
|
OpenAICodexOAuthFlow,
|
||||||
|
OpenAICodexProvider,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .config import CodexConfig, LLMConfig, OpenAiConfig
|
||||||
|
from .credentials import FileCodexCredentionalSource
|
||||||
|
|
||||||
|
|
||||||
|
class LLMModelProvider:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_openai_compatible(model_name: str,
|
||||||
|
api_key: str,
|
||||||
|
base_url: str
|
||||||
|
) -> OpenAICodexModel:
|
||||||
|
|
||||||
|
provider = OpenAIProvider(base_url=base_url,
|
||||||
|
api_key=api_key)
|
||||||
|
|
||||||
|
return OpenAIChatModel(model_name=model_name,
|
||||||
|
provider=provider)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def create_codex(model_name: str='gpt-5.6-luna',
|
||||||
|
credentional_path: str | Path | None=None
|
||||||
|
) -> OpenAICodexModel:
|
||||||
|
|
||||||
|
if credentional_path is None:
|
||||||
|
credentional_path = Path.cwd() / ".config" / "codex-credentials.json"
|
||||||
|
|
||||||
|
|
||||||
|
source = FileCodexCredentionalSource(credentional_path)
|
||||||
|
|
||||||
|
if not source.path.exists():
|
||||||
|
flow = OpenAICodexOAuthFlow()
|
||||||
|
webbrowser.open(flow.authorization_url())
|
||||||
|
credentials = await flow.exchange_code_from_callback()
|
||||||
|
await source.save(credentials)
|
||||||
|
|
||||||
|
provider = OpenAICodexProvider(credential_source=source)
|
||||||
|
|
||||||
|
return OpenAICodexModel(model_name=model_name,
|
||||||
|
provider=provider)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def create_from_config(config: LLMConfig):
|
||||||
|
|
||||||
|
if isinstance(config, CodexConfig):
|
||||||
|
return await LLMModelProvider.create_codex(
|
||||||
|
model_name=config.model,
|
||||||
|
credentional_path=config.credential_path
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(config, OpenAiConfig):
|
||||||
|
api_key = os.getenv(config.api_key_env)
|
||||||
|
base_url=os.getenv(config.base_url_env)
|
||||||
|
|
||||||
|
if not api_key:
|
||||||
|
raise ValueError(
|
||||||
|
f"Environment variable "
|
||||||
|
f"{config.api_key_env!r} is not set"
|
||||||
|
)
|
||||||
|
|
||||||
|
if not base_url:
|
||||||
|
raise ValueError(
|
||||||
|
f"Environment variable "
|
||||||
|
f"{config.base_url_env!r} is not set"
|
||||||
|
)
|
||||||
|
|
||||||
|
return LLMModelProvider.create_openai_compatible(
|
||||||
|
model_name=config.model,
|
||||||
|
api_key=api_key,
|
||||||
|
base_url=base_url
|
||||||
|
)
|
||||||
|
|
||||||
|
raise ValueError(f"Unsupported LLM config: {type(config)}")
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[project]
|
||||||
|
name = "llm-robot-control"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.13"
|
||||||
|
dependencies = [
|
||||||
|
"pydantic>=2.13.5",
|
||||||
|
"pydantic-ai>=2.51.0",
|
||||||
|
"python-dotenv>=1.2.3",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user