vinc-agent-framework 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Vinc for Microsoft Agent Framework.
|
|
2
|
+
|
|
3
|
+
Design and scope: [[vinc.plan.framework-integration-packages-2026-09-28]].
|
|
4
|
+
"""
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
|
|
7
|
+
from .provider import VincContextProvider, create_vinc_tools, record_episode # noqa: E402
|
|
8
|
+
|
|
9
|
+
__all__ = ["__version__", "VincContextProvider", "create_vinc_tools", "record_episode"]
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""A context provider and read tools that connect an Agent Framework agent to Vinc.
|
|
2
|
+
|
|
3
|
+
Reading happens on every run, before the model is called. Writing happens only
|
|
4
|
+
when your code calls :func:`record_episode`; the provider never stores the
|
|
5
|
+
conversation.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Annotated, Any, Iterable, Sequence
|
|
10
|
+
|
|
11
|
+
from agent_framework import AgentSession, ContextProvider, FunctionTool, SessionContext, tool
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
from vinc_client import (AsyncVincClient, VincQuotaExceeded, VincUnavailable, format_brief,
|
|
14
|
+
format_search)
|
|
15
|
+
from vinc_client.context import DEFAULT_MAX_CHARS
|
|
16
|
+
|
|
17
|
+
DEFAULT_SOURCE_ID = "vinc"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _last_user_text(messages: Sequence[Any]) -> str:
|
|
21
|
+
"""The text of the newest user message; the question the run is about."""
|
|
22
|
+
for message in reversed(list(messages or [])):
|
|
23
|
+
if str(getattr(message, "role", "")).lower().endswith("user"):
|
|
24
|
+
return (getattr(message, "text", "") or "").strip()
|
|
25
|
+
return ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def create_vinc_tools(client: AsyncVincClient, *,
|
|
29
|
+
max_chars: int = DEFAULT_MAX_CHARS) -> list[FunctionTool]:
|
|
30
|
+
"""Two read-only tools the model may call: ``vinc_brief`` and ``vinc_search``.
|
|
31
|
+
|
|
32
|
+
Nothing here can write. When Vinc is unavailable or the quota is spent the
|
|
33
|
+
tool answers with a sentence saying so instead of failing the run.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@tool(name="vinc_brief",
|
|
37
|
+
description="Look up one node in the user's Vinc knowledge graph: a decision, record "
|
|
38
|
+
"or concept with its summary, document excerpts and relations. Pass a "
|
|
39
|
+
"question or an exact node id such as decision:some-slug.")
|
|
40
|
+
async def vinc_brief(
|
|
41
|
+
query: Annotated[str, Field(description="A question, or an exact node id.")],
|
|
42
|
+
) -> str:
|
|
43
|
+
try:
|
|
44
|
+
return (format_brief(await client.brief(query), max_chars=max_chars)
|
|
45
|
+
or "Vinc has no single node for that; try vinc_search.")
|
|
46
|
+
except (VincUnavailable, VincQuotaExceeded) as exc:
|
|
47
|
+
return f"Vinc is not available right now ({exc.code}); answer without it."
|
|
48
|
+
|
|
49
|
+
@tool(name="vinc_search",
|
|
50
|
+
description="Search the user's Vinc knowledge graph by text and meaning; returns "
|
|
51
|
+
"matching nodes and document passages with their ids.")
|
|
52
|
+
async def vinc_search(
|
|
53
|
+
query: Annotated[str, Field(description="What to look for.")],
|
|
54
|
+
limit: Annotated[int, Field(description="How many matches of each kind.", ge=1,
|
|
55
|
+
le=20)] = 5,
|
|
56
|
+
) -> str:
|
|
57
|
+
try:
|
|
58
|
+
return (format_search(await client.search(query, limit=limit), max_chars=max_chars,
|
|
59
|
+
limit=limit)
|
|
60
|
+
or "No matches in Vinc.")
|
|
61
|
+
except (VincUnavailable, VincQuotaExceeded) as exc:
|
|
62
|
+
return f"Vinc is not available right now ({exc.code}); answer without it."
|
|
63
|
+
|
|
64
|
+
return [vinc_brief, vinc_search]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class VincContextProvider(ContextProvider):
|
|
68
|
+
"""Adds Vinc context to the instructions of every agent run.
|
|
69
|
+
|
|
70
|
+
``before_run`` briefs the newest user message and, when something matched,
|
|
71
|
+
appends a fenced block marked as data. With ``search_fallback`` (the default)
|
|
72
|
+
a tie or a miss is widened with a search, so a run costs one call, or two.
|
|
73
|
+
``after_run`` does nothing: unlike memory providers that store each turn,
|
|
74
|
+
this one never writes. Use :func:`record_episode` after a person approved
|
|
75
|
+
what happened.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, client: AsyncVincClient | None = None, *, api_key: str | None = None,
|
|
79
|
+
base_url: str | None = None, space: str | None = None,
|
|
80
|
+
source_id: str = DEFAULT_SOURCE_ID, search_fallback: bool = True,
|
|
81
|
+
max_chars: int = DEFAULT_MAX_CHARS, max_excerpts: int = 3,
|
|
82
|
+
min_query_chars: int = 3, expose_tools: bool = False) -> None:
|
|
83
|
+
super().__init__(source_id)
|
|
84
|
+
self.client = client or AsyncVincClient(api_key, base_url=base_url, space=space)
|
|
85
|
+
self._owns_client = client is None
|
|
86
|
+
self.search_fallback = search_fallback
|
|
87
|
+
self.max_chars = max_chars
|
|
88
|
+
self.max_excerpts = max_excerpts
|
|
89
|
+
self.min_query_chars = min_query_chars
|
|
90
|
+
self._tools = create_vinc_tools(self.client, max_chars=max_chars) if expose_tools else []
|
|
91
|
+
|
|
92
|
+
async def before_run(self, *, agent: Any, session: AgentSession, context: SessionContext,
|
|
93
|
+
state: dict[str, Any]) -> None:
|
|
94
|
+
block = await self.client.get_context(
|
|
95
|
+
_last_user_text(context.input_messages), search_fallback=self.search_fallback,
|
|
96
|
+
max_chars=self.max_chars, max_excerpts=self.max_excerpts,
|
|
97
|
+
min_query_chars=self.min_query_chars)
|
|
98
|
+
if block:
|
|
99
|
+
context.extend_instructions(self.source_id, block)
|
|
100
|
+
if self._tools:
|
|
101
|
+
context.extend_tools(self.source_id, self._tools)
|
|
102
|
+
|
|
103
|
+
async def after_run(self, *, agent: Any, session: AgentSession, context: SessionContext,
|
|
104
|
+
state: dict[str, Any]) -> None:
|
|
105
|
+
# 의도적으로 비어 있다. 대화를 그래프에 쓰지 않는다 — 쓰기는 record_episode 뿐.
|
|
106
|
+
# 근거: [[decision:framework-integrations-ship-zep-shaped-knowledge-packages-first-2026-09-28]]
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
async def aclose(self) -> None:
|
|
110
|
+
"""Close the HTTP client if this provider created it."""
|
|
111
|
+
if self._owns_client:
|
|
112
|
+
await self.client.aclose()
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def record_episode(client: AsyncVincClient, title: str, *, summary: str = "",
|
|
116
|
+
about: Iterable[str] = (), date: str | None = None,
|
|
117
|
+
domain: str | None = None, tags: Iterable[str] | None = None,
|
|
118
|
+
space: str | None = None) -> dict[str, Any]:
|
|
119
|
+
"""Write one Episode about existing concepts or decisions. Needs a ``vinc_sk_`` key.
|
|
120
|
+
|
|
121
|
+
Call it after a person approved what the agent did; see
|
|
122
|
+
:meth:`vinc_client.AsyncVincClient.record_episode`.
|
|
123
|
+
"""
|
|
124
|
+
return await client.record_episode(title, summary=summary, about=about, date=date,
|
|
125
|
+
domain=domain, tags=tags, space=space)
|
|
File without changes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: vinc-agent-framework
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Vinc for Microsoft Agent Framework: bring decisions and records from your knowledge graph into each agent run, without writing anything on its own.
|
|
5
|
+
Project-URL: Homepage, https://vincs.io
|
|
6
|
+
Project-URL: Documentation, https://vincs.io/docs/
|
|
7
|
+
Author: Vinculums
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agent-framework,context-provider,knowledge-graph,memory,vinc
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Requires-Dist: agent-framework-core>=1.8.1
|
|
16
|
+
Requires-Dist: vinc-client<0.2,>=0.1
|
|
17
|
+
Provides-Extra: test
|
|
18
|
+
Requires-Dist: pytest>=8; extra == 'test'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# vinc-agent-framework
|
|
22
|
+
|
|
23
|
+
[Vinc](https://vincs.io) for [Microsoft Agent Framework](https://learn.microsoft.com/agent-framework/). Vinc is a knowledge graph you share with AI: the decisions, records and documents your team wrote, with the reasons attached. This package brings the relevant part of it into each agent run.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install vinc-agent-framework
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Add context to every run
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from agent_framework import Agent
|
|
35
|
+
from agent_framework.openai import OpenAIChatClient
|
|
36
|
+
from vinc_agent_framework import VincContextProvider
|
|
37
|
+
|
|
38
|
+
vinc = VincContextProvider() # reads VINC_API_KEY; use a vinc_ro_ key
|
|
39
|
+
agent = Agent(
|
|
40
|
+
client=OpenAIChatClient(),
|
|
41
|
+
instructions="You review design changes.",
|
|
42
|
+
context_providers=[vinc],
|
|
43
|
+
)
|
|
44
|
+
await agent.run("Why are our colour tokens stored as OKLCH?")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Before each run the provider looks up the newest user message in Vinc and, when something matches, adds a block to the instructions. The block is marked as data, not instructions, and it cites node ids the model can quote back.
|
|
48
|
+
|
|
49
|
+
- One call per run; two when the lookup found nothing or several nodes tied, because it then widens with a search (`search_fallback=False` turns that off).
|
|
50
|
+
- If Vinc is unreachable or the daily limit is spent, the run goes on without the block. A wrong key or space raises.
|
|
51
|
+
- For a team's graph pass `space="<team id>"`.
|
|
52
|
+
|
|
53
|
+
## Let the model look things up
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
vinc = VincContextProvider(expose_tools=True)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
adds two read-only tools, `vinc_brief` and `vinc_search`. You can also build them yourself with `create_vinc_tools(client)`.
|
|
60
|
+
|
|
61
|
+
## Writing, on purpose
|
|
62
|
+
|
|
63
|
+
The provider never stores the conversation: `after_run` does nothing. Record what happened when a person approved it, with a `vinc_sk_` key:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from vinc_client import AsyncVincClient
|
|
67
|
+
from vinc_agent_framework import record_episode
|
|
68
|
+
|
|
69
|
+
writer = AsyncVincClient(api_key=WRITE_KEY)
|
|
70
|
+
await record_episode(
|
|
71
|
+
writer,
|
|
72
|
+
"Fixed contrast on the dark secondary button",
|
|
73
|
+
summary="text-secondary now passes 4.5:1 on the dark surface.",
|
|
74
|
+
about=["decision:tokens-are-oklch"],
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## License
|
|
79
|
+
|
|
80
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
vinc_agent_framework/__init__.py,sha256=txJitioVewiJiEYwFF3l6WCwVlcsDa_IoLtl4CuZZLs,322
|
|
2
|
+
vinc_agent_framework/provider.py,sha256=eO6gK5y-xlbYwQ_qbJ7eMcA_6dfQVe6TiFrcM_eLipY,6186
|
|
3
|
+
vinc_agent_framework/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
vinc_agent_framework-0.1.0.dist-info/METADATA,sha256=L2dv4ONa3Cq8-C4fQw7OqWYbrzZyUIlkRck3m08eCtE,2926
|
|
5
|
+
vinc_agent_framework-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
6
|
+
vinc_agent_framework-0.1.0.dist-info/RECORD,,
|