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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any