添加provider工厂和Ollama支持
- 实现ProviderFactory用于构建不同类型的provider适配器 - 添加EnvironmentCredentialResolver用于解析环境变量中的凭证 - 实现OllamaProvider支持本地模型调用 - 实现OpenAICompatibleProvider支持OpenAI兼容接口 - 在AgentRuntime中添加对ProviderError的处理 - 更新Message结构体添加tool_calls字段 - 实现provider配置的增删改查API端点 - 添加provider注册表的replace方法 - 添加HTTP基础类和工具参数解码功能 - 更新依赖添加httpx库 - 添加相关单元测试验证provider适配器功能 ```
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import json
|
||||
from collections.abc import AsyncIterator
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from app.contracts import ModelEvent, ModelEventType, ModelRequest
|
||||
from app.providers.base import ProviderError, ProviderTurn
|
||||
|
||||
|
||||
class TurnStreamingMixin:
|
||||
async def complete(self, request: ModelRequest) -> ProviderTurn:
|
||||
raise NotImplementedError
|
||||
|
||||
async def stream(self, request: ModelRequest) -> AsyncIterator[ModelEvent]:
|
||||
try:
|
||||
turn = await self.complete(request)
|
||||
sequence = 0
|
||||
if turn.text:
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.text_delta,
|
||||
sequence=sequence,
|
||||
data={"text": turn.text},
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
sequence += 1
|
||||
for call in turn.tool_calls:
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.tool_call_start,
|
||||
sequence=sequence,
|
||||
data={
|
||||
"tool_call_id": call.tool_call_id,
|
||||
"name": call.name,
|
||||
"arguments": call.arguments,
|
||||
},
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
sequence += 1
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.tool_call_end,
|
||||
sequence=sequence,
|
||||
data={"tool_call_id": call.tool_call_id},
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
sequence += 1
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.usage,
|
||||
sequence=sequence,
|
||||
data={
|
||||
"input_tokens": turn.input_tokens,
|
||||
"output_tokens": turn.output_tokens,
|
||||
},
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.done,
|
||||
sequence=sequence + 1,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
except ProviderError as exc:
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.error,
|
||||
data={"code": exc.code, "message": exc.message},
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
yield ModelEvent(
|
||||
event=ModelEventType.done,
|
||||
sequence=1,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
|
||||
def decode_tool_arguments(value: object) -> dict[str, object]:
|
||||
if isinstance(value, dict):
|
||||
return value
|
||||
if not isinstance(value, str):
|
||||
raise ProviderError("PROVIDER_INVALID_RESPONSE", "Tool arguments are not JSON.")
|
||||
try:
|
||||
decoded = json.loads(value)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise ProviderError("PROVIDER_INVALID_RESPONSE", "Tool arguments are invalid JSON.") from exc
|
||||
if not isinstance(decoded, dict):
|
||||
raise ProviderError("PROVIDER_INVALID_RESPONSE", "Tool arguments must be an object.")
|
||||
return decoded
|
||||
Reference in New Issue
Block a user